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 Item resources
  • Typed JSON bodies and responses
  • Central error middleware
  • Optional userId on Request after fake auth middleware

Setup

bash
mkdir ts-api
cd ts-api
npm init -y
npm install express
npm install --save-dev typescript @types/express @types/node tsx
text
ts-api/
├── src/
│   ├── index.ts
│   ├── types.ts
│   ├── items.router.ts
│   └── middleware/
│       ├── error.ts
│       └── auth.ts
├── package.json
└── tsconfig.json

Use the same NodeNext tsconfig pattern as the Node integration chapter.

Domain Types

src/types.ts:

typescript
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:

typescript
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

bash
npm run dev
curl -X POST http://localhost:4000/items -H "Content-Type: application/json" \
  -d '{"name":"Pen","quantity":3}'
curl http://localhost:4000/items

Extensions

  • Replace Map with 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.