Project: Type-Safe REST API (Node)
Introduction
This project builds a small REST API with Express and TypeScript: typed request handlers, DTO shapes, consistent error JSON, and optional Request augmentation for authenticated routes. You connect HTTP layers to domain types without losing compile-time safety.
Prerequisites
Project Goals
- CRUD for in-memory
Itemresources - Typed JSON bodies and responses
- Central error middleware
- Optional
userIdonRequestafter fake auth middleware
Setup
mkdir ts-api
cd ts-api
npm init -y
npm install express
npm install --save-dev typescript @types/express @types/node tsxts-api/
├── src/
│ ├── index.ts
│ ├── types.ts
│ ├── items.router.ts
│ └── middleware/
│ ├── error.ts
│ └── auth.ts
├── package.json
└── tsconfig.jsonUse the same NodeNext tsconfig pattern as the Node integration chapter.
Domain Types
src/types.ts:
export interface Item {
id: string;
name: string;
quantity: number;
}
export type CreateItemDto = Pick<Item, "name" | "quantity">;
export type UpdateItemDto = Partial<CreateItemDto>;
export interface ApiErrorBody {
error: string;
}In-Memory Store
src/items.router.ts (excerpt pattern):
Tip
Validate Bodies in Production
Casting req.body teaches structure; use Zod or similar to parse unknown before trusting input.
Error Middleware
src/middleware/error.ts:
import type { NextFunction, Request, Response } from "express";
export function errorHandler(
err: unknown,
_req: Request,
res: Response,
_next: NextFunction
): void {
const message = err instanceof Error ? err.message : "Internal error";
res.status(500).json({ error: message });
}Augment Request (Optional)
src/middleware/auth.ts:
src/index.ts:
Test with curl
npm run dev
curl -X POST http://localhost:4000/items -H "Content-Type: application/json" \
-d '{"name":"Pen","quantity":3}'
curl http://localhost:4000/itemsExtensions
- Replace
Mapwith a database and shared row types - Add Fastify with schema-based validation
- OpenAPI types via
openapi-typescript
FAQ
Fastify vs Express?
Fastify encourages JSON schemas; Express is ubiquitous—types work with both.
Share types with frontend?
Publish a shared package or generate types from OpenAPI.
Why global Express namespace?
Standard pattern for req.user—keep augmentation in one .ts file.