TypeScript in Node Basics
8 examples to get you started with TypeScript in Node - 6 basic and 2 intermediate.
Prerequisites
- Node.js 24.18.0, TypeScript 5.6+,
npm install -D typescript tsx @types/node. "type": "module"inpackage.jsonfor ESM examples below.
Basic Examples
1. Minimal tsconfig for Node 24 ESM
NodeNext aligns TypeScript with Node's module resolver.
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}strictenablesstrictNullChecks,noImplicitAny, and related flags together.skipLibCheckspeeds builds - still typecheck yoursrc.outDir/rootDirkeepdist/mirroringsrc/.
Related: tsx vs tsc vs ts-node - dev vs prod pipelines
2. Typed process.env (Unsafe Default)
process.env values are string | undefined - narrow before use.
const port = Number(process.env.PORT ?? 3000);
if (Number.isNaN(port)) throw new Error('Invalid PORT');- Environment variables are always strings when present.
??handles undefined; empty string needs explicit handling.- Validate required vars at startup - see Zod example in Zod at Boundaries.
3. Import Built-ins with Types
@types/node ships with DefinitelyTyped-quality typings for Node 24 APIs.
import { readFile } from 'node:fs/promises';
import type { Stats } from 'node:fs';
const stat: Stats = await readFile('package.json').then(() => import('node:fs')).then(fs => fs.promises.stat('package.json'));- Use
import typefor type-only imports withverbatimModuleSyntax. @types/nodeversion should match your Node major (npm i -D @types/node@24).- Prefer
node:prefix in application code.
4. ESM Import Paths with .js Extension
TypeScript source imports .js because emit keeps specifiers unchanged.
// src/user.ts
export interface User { id: string; name: string }// src/main.ts
import { type User } from './user.js';- This looks odd in
.tsfiles but matches Node ESM runtime resolution. moduleResolution: NodeNextenforces this pattern.- Bundlers may rewrite extensions - unbundled Node apps cannot.
Related: ES Modules (import) - ESM rules
5. async Handler Return Type
Express/Fastify handlers should return void or Promise<void> explicitly.
import type { Request, Response } from 'express';
export async function getHealth(_req: Request, res: Response): Promise<void> {
res.json({ status: 'ok' });
}- Async handlers return Promises - ensure errors propagate to error middleware.
- Use framework-specific types for request augmentation - see Typing Express/Fastify Handlers.
- Avoid
anyonreqandres- define generics forbodyandparams.
6. node:test with TypeScript
Native tests with types - no Jest required for unit tests.
import { test } from 'node:test';
import assert from 'node:assert/strict';
function slugify(input: string): string {
return input.toLowerCase().replace(/\s+/g, '-');
}
test('slugify', () => {
assert.equal(slugify('Hello World'), 'hello-world');
});node --import tsx --test src/**/*.test.ts- Colocate
*.test.tsnext to source or undersrc/__tests__. assert/strictthrows on failure with clear diffs.- CI can run compiled
dist/**/*.test.jsaftertscfor parity.
Intermediate Examples
7. Strict null narrowing
strictNullChecks forces handling null and undefined.
function findUser(id: string, users: Map<string, { name: string }>): string {
const user = users.get(id);
if (!user) throw new Error(`User ${id} not found`);
return user.name;
}- Optional chaining
user?.namereturnsstring | undefined- still narrow for business logic. - Avoid non-null assertion
!except in tests or after explicit guards. - Database layers returning
nullshould map toResulttypes or exceptions at boundaries.
Related: Zod at Boundaries - runtime validation
8. Shared types package (Monorepo)
Publish DTO types without server internals.
// packages/types/src/user.ts
export interface UserDto {
id: string;
displayName: string;
}// apps/api/src/handlers/user.ts
import type { UserDto } from '@acme/types/user';
export function toDto(user: { id: string; name: string }): UserDto {
return { id: user.id, displayName: user.name };
}- Export types-only modules - no server secrets in shared package.
- Use
exportsin@acme/types/package.jsonfor subpaths. - See Sharing Types with Frontend for full monorepo patterns.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.