Skip to main content

ts-rest Express example

The apps/example-ts-rest-express directory contains a working Express 5 API built contract-first with ts-rest, via @rtorcato/api-ts-rest-express.

Where the Express example wires routes and the OpenAPI spec by hand, here a single contract is the source of truth: ts-rest validates requests against it and mountTsRest generates the OpenAPI 3.1 doc from it — so the API, its validation, and its docs can't drift.

What it demonstrates

PackageUsage in the example
api-ts-rest-expressinitContract / initServer define the contract + handlers; mountTsRest serves the routes and the generated Scalar docs in one call
api-ts-rest-expresswithDefaultErrorsAttaches the shared 400/404/500 error envelope to every route's responses
api-configloadEnv() validates PORT + LOG_LEVEL from .env at startup
api-loggercreateLogger() gives a pino logger, pretty-printed in dev
api-errorsNotFoundError + toErrorResponse() return the shared error envelope from handlers
api-errors-expresserrorHandler / notFoundHandler catch unexpected throws and off-contract routes
api-responseok() success envelope + successSchema() in the contract
api-express-utilslogRoutes() prints routes on start

Routes

GET /items
POST /items { "name": string }
GET /items/:id
DELETE /items/:id
GET /docs Scalar API reference (generated from the contract)
GET /openapi.json OpenAPI 3.1 spec

Items are stored in-memory — no database required.

Run locally

cd apps/example-ts-rest-express
cp .env.example .env
pnpm dev

The server starts on http://localhost:3003 with pretty-printed logs.

  • Scalar API reference → http://localhost:3003/docs
  • OpenAPI spec → http://localhost:3003/openapi.json

Run with Docker

cd apps/example-ts-rest-express
docker compose up

The image builds from the monorepo root, installs workspace packages, and starts the server on port 3003.

Quick smoke test

# List items (empty)
curl http://localhost:3003/items

# Create
curl -X POST http://localhost:3003/items \
-H 'Content-Type: application/json' \
-d '{"name":"hello"}'

# 404 for a bad ID — shared error envelope
curl http://localhost:3003/items/bad-id

# 400 validation error — ts-rest contract validation
curl -X POST http://localhost:3003/items \
-H 'Content-Type: application/json' \
-d '{}'

Source

apps/example-ts-rest-express/src/index.ts