Heb je ooit een Node.js project geërfd waar geen enkele functie aangeeft wat hij terugstuurt? Dan weet je precies waarom TypeScript in Node.js projecten inmiddels de standaard is geworden. Je krijgt type safety, betere autocomplete en refactors die niet meer aanvoelen als Russisch roulette.
In deze gids lopen we door de volledige stack: setup, tsconfig, types voor libraries, build pipelines en valkuilen die je in de praktijk tegenkomt. Je leert zowel hoe je een nieuw project opzet als hoe je een bestaand JavaScript-project stap voor stap migreert.
Waarom TypeScript in Node.js?
Node.js draait van nature op JavaScript, een dynamisch getypeerde taal. Dat is lekker soepel voor scripts, maar in een backend met tientallen endpoints, database queries en achtergrondtaken wordt het snel een risico.
TypeScript lost dat op door een type-laag bovenop JavaScript te leggen. Je schrijft .ts-bestanden, de compiler controleert ze en output is gewone JavaScript die Node kan draaien.
Concrete voordelen in backend-context:
- Vroege foutdetectie: typos in property-namen of verkeerde argumenten vang je tijdens het schrijven.
- Betere DX: autocomplete, go-to-definition en refactor tools werken pas echt goed met types.
- Zelfdocumenterende code: een functie-signature vertelt direct wat er in en uit komt.
- Veiliger refactoren: je weet zeker welke call sites breken als je een interface wijzigt.
Zeker in combinatie met frameworks zoals Express, Fastify of NestJS, en met validators zoals Zod, levert TypeScript een sterke basis voor betrouwbare APIs.
Een nieuw project opzetten
Beginnen is gelukkig niet moeilijk. Start met een lege map en initialiseer een Node.js project.
mkdir mijn-api && cd mijn-api
npm init -y
npm install --save-dev typescript @types/node
npx tsc --init
De laatste regel maakt een tsconfig.json met standaardwaarden. Die gaan we zo aanpassen. Installeer verder de runtime-dependencies die je nodig hebt:
npm install express
npm install --save-dev @types/express tsx
@types/express bevat de type-definities voor Express, het pakket zelf is nog in JavaScript geschreven. tsx gebruiken we straks om TypeScript direct te draaien tijdens development.
Minimale projectstructuur
Een gangbare opzet ziet er zo uit:
mijn-api/
├── src/
│ ├── index.ts
│ ├── routes/
│ └── lib/
├── dist/ (output, niet committen)
├── package.json
└── tsconfig.json
Voor meer over mapstructuur en modules, lees onze gids over modules en project structuur.
tsconfig.json: de knoppen die ertoe doen
Je tsconfig.json bepaalt hoe streng TypeScript is en hoe de output eruitziet. Voor een moderne Node.js backend (Node 20+) ziet een solide basis er zo uit:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": false,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Een paar belangrijke opties om te begrijpen:
target: de JavaScript-versie waarnaar gecompileerd wordt. ES2022 is veilig vanaf Node 18.module/moduleResolution:NodeNextzorgt dat ES modules en CommonJS goed samenwerken zoals Node ze verwacht.strict: zet alle strikte checks aan (noImplicitAny, strictNullChecks, etc.). Altijd doen in nieuwe projecten.esModuleInterop: maakt default imports vanuit CommonJS-modules mogelijk.skipLibCheck: slaat type checks in node_modules over. Scheelt bouwtijd en voorkomt ruis van third-party types.sourceMap: genereert.map-bestanden zodat stack traces naar je TypeScript-regels wijzen.
Voor de officiële referentie van alle opties, zie de TypeScript tsconfig documentatie.
TypeScript draaien in development
In development wil je niet na elke wijziging handmatig tsc runnen. Er zijn drie populaire opties.
Optie 1: tsx (aanrader)
tsx is snel (gebruikt esbuild onder de motorkap), ondersteunt ES modules en heeft een watch mode:
{
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}
Optie 2: ts-node
Langer bestaand en iets zwaarder, maar integreert goed met debuggers. Combineer met nodemon voor watch mode.
Optie 3: native Node.js type stripping
Vanaf Node.js 22 kun je .ts-bestanden direct draaien met node --experimental-strip-types. Node strippt de types en voert de JavaScript uit. Handig voor kleine scripts, maar voor grotere projecten is tsx nog altijd prettiger.
Types voor third-party libraries
Niet elke npm-package heeft ingebouwde types. Er zijn drie scenario's.
1. De library levert zelf types.
Bijvoorbeeld zod of fastify. Je hoeft niets extra's te installeren.
2. Types staan op DefinitelyTyped.
Herkenbaar aan @types/<package>. Installeer als devDependency:
npm install --save-dev @types/express @types/node @types/lodash
3. Geen types beschikbaar.
Maak een bestand src/types/globals.d.ts met een module-declaratie:
declare module 'obscure-legacy-lib' {
export function doSomething(input: string): number;
}
Voor de zekerheid: @types/node is voor ieder Node.js project verplicht. Zonder dat kent TypeScript geen process, Buffer of fs.
Praktisch voorbeeld: een getypeerde Express route
Met @types/express geïnstalleerd kun je request- en response-bodies typeren. Dit voorkomt een hoop subtiele bugs:
import express, { Request, Response } from 'express';
interface CreateUserBody {
email: string;
name: string;
}
interface UserResponse {
id: string;
email: string;
name: string;
}
const app = express();
app.use(express.json());
app.post(
'/users',
async (
req: Request<{}, UserResponse, CreateUserBody>,
res: Response<UserResponse>
) => {
const { email, name } = req.body;
// TypeScript weet dat email en name strings zijn
const user = await createUser({ email, name });
res.status(201).json(user);
}
);
In de praktijk combineer je dit vrijwel altijd met runtime-validatie. TypeScript checkt immers alleen op compile-time, een client kan alsnog rommel sturen. Validatie met Zod sluit dat gat.
Build en deployment
Voor productie compileer je de TypeScript naar JavaScript en draai je die met Node. De basisflow:
npm run build # tsc → dist/
node dist/index.js
tsc of bundler?
Voor een backend heb je vaak geen bundler nodig. tsc is prima: het vertaalt 1-op-1 per bestand. Kies voor esbuild of swc als:
- Je build te traag wordt (tsc kan bij grote projecten secondenlang duren).
- Je een monorepo hebt waar je snelheid nodig hebt. Zie onze gids over monorepo architectuur.
- Je wilt bundlen naar één bestand voor Lambda of vergelijkbare serverless platforms.
Let op: esbuild en swc doen geen type checking. Ze strippen types en compileren. Draai daarom tsc --noEmit als aparte stap in je CI om type-fouten te vangen.
Dockerfile met multi-stage build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/index.js"]
Zo stuur je alleen de gecompileerde output naar productie, zonder TypeScript-dependencies of broncode.
Migreren van JavaScript naar TypeScript
Heb je een bestaand JavaScript project? Je hoeft niet in één keer alles om te gooien.
- Installeer TypeScript en voeg een
tsconfig.jsontoe met"allowJs": trueen"checkJs": false. - Hernoem files incrementeel van
.jsnaar.ts, beginnend bij bladeren (utils, helpers) en eindigend bij entry points. - Zet
stricteerst uit, migreer, en zet strict flags daarna één voor één aan (noImplicitAny, danstrictNullChecks, etc.). - Gebruik
anytijdelijk waar je vastloopt, en markeer met een// TODO: type-comment.
Een migratie-PR per module werkt vaak beter dan één gigantische PR waar iedereen bang van wordt.
Veelgemaakte fouten
Een paar valkuilen die we vaak zien:
anyals vluchtroute gebruiken. Het schakelt type checking volledig uit. Gebruikunknownals je echt niet weet wat iets is, en narrow vervolgens.- Types en runtime verwarren. Een interface bestaat niet tijdens runtime. Je kunt er niet op
instanceofchecken. Gebruik Zod of een class voor runtime-garanties. - Strict mode later proberen aan te zetten. Dit geeft honderden fouten tegelijk. Begin direct strict.
- Geen type checking in CI. Als alleen je bundler draait, miss je type-fouten. Voeg
tsc --noEmittoe aan je pipeline. Zie ook onze production readiness checklist. - Over-engineering met generics. TypeScript laat je complexe types bouwen, maar leesbaarheid gaat voor. Als je drie geneste generics nodig hebt, is er meestal een simpeler ontwerp.
TypeScript en andere tooling
TypeScript speelt goed samen met de rest van het Node.js ecosysteem.
- Testing: Jest, Vitest en Node's ingebouwde test runner draaien allemaal met TypeScript. Zie testing strategieën voor setup-tips.
- Linting: combineer ESLint met
typescript-eslintvoor type-bewuste regels. - Logging en observability: typeer je log-context zodat je weet welke velden een log-event heeft. Handig voor error handling op schaal.
- Performance: types hebben geen runtime-impact. Lees performance tuning Node.js voor de echte knoppen.
Voor dieper duiken in typings loont het om de TypeScript handbook door te nemen, die is gratis en erg grondig.
Veelgestelde vragen
Waarom zou je TypeScript gebruiken in een Node.js project?
TypeScript voegt statische types toe aan JavaScript, waardoor je bugs eerder vangt, betere autocomplete hebt en veiliger refactort. In grotere Node.js projecten met meerdere developers betaalt dat zich vrijwel altijd terug.
Hoe draai je TypeScript in Node.js?
Je compileert TypeScript met tsc naar JavaScript en draait de output met node. Voor development gebruiken veel teams tsx of ts-node om .ts-bestanden direct uit te voeren, en sinds Node 22 kan Node zelf ook TypeScript direct draaien via type stripping.
Wat is het verschil tussen tsc, tsx en ts-node?
tsc is de officiële TypeScript compiler die .ts omzet naar .js. tsx en ts-node draaien TypeScript direct zonder een aparte buildstap, handig in development. Voor productie gebruik je meestal tsc of een bundler zoals esbuild of swc.
Moet ik strict mode aanzetten in tsconfig?
Ja, start nieuwe projecten altijd met strict: true. Je vangt er null-fouten, impliciete any's en subtiele type-issues mee die anders pas in productie boven water komen. Het is lastiger om het later aan te zetten dan meteen.
Hoe ga ik om met libraries zonder types?
Kijk eerst of er een @types-package bestaat op DefinitelyTyped. Zo niet, dan schrijf je zelf een klein declaratiebestand (.d.ts) met de types die je gebruikt, of je typeert de import met any als tijdelijke workaround.
Afsluitend
TypeScript in Node.js is geen hype meer, het is de standaard voor serieuze backends. Begin strict, houd je tsconfig simpel, en combineer compile-time types met runtime-validatie. Dan krijg je de robuuste basis waar je team jaren op kan doorbouwen.