Chassis

Scaffold an Express 5 + TypeScript backend: database, auth, optional Next.js front end.

DatabasesTypeScriptv0.1.2

🏎️ Chassis

A lightweight, decorator-driven Express + TypeScript backend starter. Clone, run, ship.

πŸ“– Documentation Β· Getting started Β· create-chassis on npm

Chassis gives you NestJS-style controller ergonomics on plain Express 5 β€” in a handful of small files you can actually read. Zero configuration required: the server boots standalone, and every integration switches on only when you add its environment variable. Scaffold with a preset or pick Γ  la carte β€” a database (Mongo, Postgres, or SQLite, ORM included), an auth provider (Auth0, Clerk, or built-in local sign-in), an optional Next.js front end, Sentry, an MCP server, and x402 payments β€” and the CLI ships only what you chose.

export class UserController extends Routable {
  constructor() {
    super('/users');
  }

  @route('get', '/:id')
  async show(req: Request) {
    const user = await findUser(req.params.id);
    if (!user) throw new AppError(ERROR_CODES.NOT_FOUND, 'User not found');
    return req.resHandler.ok(user);
  }

  @protectedRoute('post', '/', [validate({ body: createUserSchema })])
  async create(req: Request) {
    return req.resHandler.created(await createUser(req.body));
  }
}

Export the class from src/controllers/index.ts β€” that's the whole wiring.

Quick start

npm create chassis my-api -- --yes                      # zero prompts: Postgres + JWT + Sentry + Docker
npm create chassis my-app -- --preset fullstack --yes   # the same, plus a Next.js front end
npm create chassis my-api                               # interactive β€” pick a preset
npm create chassis my-api -- --db postgres --auth jwt --mcp   # Γ  la carte
npm create chassis my-api -- --bare                     # nothing β€” standalone build

Or use the template directly:

git clone https://github.com/dvd90/chassis.git my-api
cd my-api && npm install && npm run dev

That's it β€” no database, no env file, no accounts needed. Open http://localhost:8000/status.

New here? Follow the step-by-step getting-started guide β€” zero to a tested API in ~10 minutes.

For AI agents

Every path is non-interactive: --yes and --bare never prompt, and the CLI skips prompts automatically whenever stdin isn't a TTY. One command produces a project that already typechecks, lints and tests green.

  • llms.txt β€” the project, its conventions and its docs index, in one fetch
  • llms-full.txt β€” every documentation page, concatenated
  • AGENTS.md β€” the conventions to follow when writing code in a Chassis project, and the definition of done

Generated projects carry AGENTS.md, CLAUDE.md, llms.txt and an add-resource skill, so whichever agent opens one writes code that matches the rest of the codebase rather than fighting it.

Features

  • TypeScript 6 + Express 5 β€” strict types, async errors caught automatically
  • Decorator routing β€” @route / @protectedRoute on controller methods, controllers auto-mount
  • Consistent responses β€” req.resHandler.ok() / .notFound() / .validation() with structured logging
  • Request correlation β€” every request gets a callId (or propagates x-call-id), echoed in responses and logs
  • Typed, validated config β€” zod-checked environment via src/config; the app refuses to boot on bad config
  • Zod input validation β€” validate({ body, query, params }) middleware with structured 400s
  • Pick-your-stack scaffolder β€” presets or Γ  la carte: database + ORM (Mongo/Postgres/SQLite), auth (Auth0/Clerk/local), a Next.js front end, Sentry, MCP, x402 β€” the CLI prunes everything else so package.json carries only what you chose
  • Opt-in integrations β€” every module enables by env var, never required
  • Payment-gated routes β€” @paidRoute('get', '/report', '$0.01') via the x402 protocol (opt-in)
  • Optional Next.js front end β€” --web adds an App Router app and makes the project an npm-workspaces monorepo (apps/api + apps/web); the auth provider you picked is wired on both sides
  • MCP server β€” expose your API to AI agents as MCP tools (npm run mcp, opt-in)
  • Health endpoints β€” /healthz (liveness) and /readyz (readiness, checks enabled integrations)
  • Graceful shutdown β€” drains connections and closes integrations on SIGTERM/SIGINT
  • Vitest + supertest β€” fast tests against the pure app factory, no server or DB needed
  • DB-aware code generator β€” npm run gen user scaffolds a controller + test wired to your ORM (Drizzle or Mongoose)
  • Production Docker β€” multi-stage build, non-root user, plus docker-compose with your database for dev
  • CI + Renovate β€” GitHub Actions verify pipeline and automated dependency updates
  • AI-agent ready β€” ships AGENTS.md, CLAUDE.md, llms.txt, and an add-resource skill so agents write code that matches the conventions (see below)

AI-agent ready

Most people scaffolding a backend today have an AI agent in the loop. Chassis is built so that agent-written code reads like hand-written code β€” because the framework gives agents rails and a verifiable finish line:

  • AGENTS.md + CLAUDE.md ship in every project β€” Claude Code, Cursor, Copilot, and Codex pick them up automatically and follow the conventions (thin controllers, resHandler responses, throw AppError, config in one place).
  • One obvious place for everything means agent output converges on the same shape a maintainer would write β€” that's what keeps it readable.
  • npm run verify (strict TypeScript + ESLint + tests) is a deterministic quality gate agents iterate against until green.
  • .claude/skills/add-resource turns "add a books resource" into one consistent, checklisted operation.
  • llms.txt gives doc-fetching tools a compact map of the conventions.

Nothing to install β€” it's all in the scaffold. See AGENTS.md.

Scripts

CommandWhat it does
npm run devStart with hot reload (tsx watch)
npm test / npm run test:watchRun the vitest suite
npm run verifyTypecheck + lint + test (CI runs this)
npm run build / npm startCompile to dist/ and run production build
npm run gen <Name>Generate a controller + test
npm run lint / npm run formatESLint / Prettier

Enabling integrations

Copy .env.example to .env. Each integration turns on when its variables are set β€” and stays completely dormant otherwise:

IntegrationEnable by settingWhat you get
MongoDBMONGODB_URIMongoose connection, readiness check, graceful disconnect
Auth0AUTH0_DOMAIN + AUTH0_AUDIENCEJWT verification on every @protectedRoute
SentrySENTRY_DSNAutomatic error reporting from the central error handler

Using a different IdP? Call setAuthProvider([...yourMiddleware]) at boot and @protectedRoute uses it β€” see src/core/auth.ts.

Sign in without a third party

Local sign-in ships in three variants β€” emailed link, the classic credential form, or both. Run npm create chassis --help to see the --auth values, or read Authentication. Whichever you pick, they share one session layer.

POST /auth/magic/request  {email, returnTo?}   β†’ 202, identical for every address
GET  /auth/magic/:token                        β†’ confirm page β€” consumes nothing
POST /auth/magic/redeem   {token}              β†’ session + redirect
POST /auth/magic/code     {email, code}        β†’ same, from the other device
POST /auth/refresh | /auth/logout | /auth/revoke-all

Four things worth knowing about the emailed-link flow:

  • GET never spends a token. Mail security scanners prefetch links, and a single-use token burned by a scanner is how this feature usually breaks in production. Redemption is a POST, on a click.
  • Every email carries a six-digit code too, so someone who asks on a laptop and reads their mail on a phone can still finish on the laptop.
  • The request endpoint will not tell you who has an account β€” same body, same timing, every address.
  • Refresh tokens rotate on every use, and replaying a spent one revokes the whole session family. Sliding SESSION_IDLE, hard SESSION_ABSOLUTE cap.
VariableDefault
JWT_SECRET(required)
SESSION_IDLE / SESSION_ABSOLUTE30d / 90d
MAGIC_TOKEN_TTL / MAGIC_CODE_ATTEMPTS15m / 5
MAGIC_LINK_BASE_URLhttp://localhost:8000
SMTP_URLunset β†’ logs the email

Chassis binds no email or SMS provider β€” bind yours through setMailTransport() or setSmsTransport(). Proving an address fires one hook, setOnVerified(), and that is the whole extension surface: consent and onboarding are yours.

Guides: magic link Β· sessions Β· transports

Project structure

src/
β”œβ”€β”€ config/          # zod-validated env β†’ typed config + feature flags
β”œβ”€β”€ core/            # the framework: Routable, decorators, responses, errors, validation
β”œβ”€β”€ middleware/      # callId correlation, dev request logging
β”œβ”€β”€ integrations/    # opt-in modules: mongo, auth0, sentry
β”œβ”€β”€ controllers/     # your endpoints β€” exported classes auto-mount
β”œβ”€β”€ __tests__/       # vitest + supertest
β”œβ”€β”€ app.ts           # pure app factory (no I/O β€” trivially testable)
└── server.ts        # boot: integrations β†’ listen β†’ graceful shutdown

Documentation

Read them at dvd90.github.io/chassis β€” searchable, one page. The source lives in docs/ and the site is generated from it, so the two can never disagree:

Docker

docker compose up --build     # API + MongoDB
docker build -t my-api .      # production image only

License

MIT

Installation

Source-derived launch command. Check the maintainer’s required arguments and credentials before running:

bash
npx -y chassis-mcp

Set up in your AI client

Merge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.

json
{
  "mcpServers": {
    "io-github-dvd90-chassis-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "chassis-mcp"
      ]
    }
  }
}

Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.

Claude Desktop setup reference

Package

chassis-mcpnpm

Compatible MCP Clients

Chassis works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.

  • Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.
  • Cursor~/.cursor/mcp.jsonRestart Cursor for changes to take effect.
  • VS Code.vscode/mcp.jsonReload VS Code window for changes to take effect.
  • Windsurf~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect.
  • Claude Code.mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.

Learn More