I/O#
The I/O surface is JavaScript’s (see
I/O). TypeScript adds typed
file handles, typed JSON.parse structures (via runtime parsing
with zod), typed Buffer and Uint8Array, and typed
streams.
This page covers the TypeScript-specific patterns. For the
underlying fs/promises, streams, and JSON APIs, see
the JavaScript page.
File reads with structure validation#
The raw readFile returns Buffer (no encoding) or
string (with encoding). JSON.parse returns any; the
operator narrows with zod so the rest of the program holds
a typed value.
import {readFile} from "node:fs/promises";
import {z} from "zod";
const Config = z.object({
host: z.string(),
port: z.number().int().positive(),
});
type Config = z.infer<typeof Config>;
async function loadConfig(path: string): Promise<Config> {
const text = await readFile(path, "utf8");
return Config.parse(JSON.parse(text)); // throws ZodError on mismatch
}
For untrusted input use safeParse and returns
a Result (see Errors).
Writes#
writeFile accepts string or Uint8Array. For typed
serialisation, wrap JSON.stringify with the
expected type.
import {writeFile} from "node:fs/promises";
async function saveConfig(path: string, config: Config): Promise<void> {
const text = JSON.stringify(config, null, 2);
await writeFile(path, text + "\n");
}
Streams#
Node streams are typed; ReadableStream<T>, WritableStream<T>,
and Transform carry their chunk types.
import {createReadStream} from "node:fs";
import {createInterface} from "node:readline";
const rl = createInterface({input: createReadStream("/var/log/syslog")});
for await (const line of rl) {
// line: string
if (line.includes("error")) console.log(line);
}
import {pipeline} from "node:stream/promises";
import {createGzip} from "node:zlib";
import {createWriteStream} from "node:fs";
await pipeline(
createReadStream("input.txt"),
createGzip(),
createWriteStream("input.txt.gz"),
);
JSON with reviver#
JSON.parse’s reviver lets the operator coerce values
in-place; pair with a zod schema to validate the structure and
then narrow the type.
const Event = z.object({
ts: z.coerce.date(),
level: z.enum(["info", "warn", "error"]),
message: z.string(),
});
type Event = z.infer<typeof Event>;
function parseEvent(s: string): Event {
return Event.parse(JSON.parse(s));
}
Buffers and typed arrays#
Buffer is Uint8Array plus Node helpers. For portable
code, the operator works with Uint8Array and
TextEncoder / TextDecoder.
const bytes: Uint8Array = new TextEncoder().encode("hello");
const back: string = new TextDecoder("utf-8").decode(bytes);
const hex = Buffer.from(bytes).toString("hex");
const back2 = Uint8Array.from(Buffer.from(hex, "hex"));
For binary protocols, DataView reads typed values out of an
ArrayBuffer with explicit endianness.
const buf = new Uint8Array([0x00, 0x50, 0x00]);
const view = new DataView(buf.buffer);
const port: number = view.getUint16(0, false); // 80 (big-endian)
Stdin and stdout#
process.stdin and process.stdout are typed as
ReadableStream and WritableStream (Node’s stream
classes). Run them through readline,
readline/promises, or for await.
import {createInterface} from "node:readline/promises";
const rl = createInterface({input: process.stdin});
for await (const line of rl) {
process.stdout.write(line.toUpperCase() + "\n");
}
fetch (Node 18+, browser, Deno, Bun)#
fetch returns Promise<Response>; the operator narrows
the body type with zod (or generates a typed client from
OpenAPI).
const UserSchema = z.object({id: z.string(), name: z.string()});
type User = z.infer<typeof UserSchema>;
async function getUser(id: string): Promise<User> {
const r = await fetch(`/users/${id}`);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return UserSchema.parse(await r.json());
}
References#
I/O for the I/O API in full.
Types for
z.inferand runtime-to-type bridging.Networking for typed HTTP / sockets.
zod, parse-don’t-validate library.