๐๏ธ 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/@protectedRouteon controller methods, controllers auto-mount - Consistent responses โ
req.resHandler.ok() / .notFound() / .validation()with structured logging - Request correlation โ every request gets a
callId(or propagatesx-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.jsoncarries 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 โ
--webadds 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 userscaffolds 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 anadd-resourceskill 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.mdship in every project โ Claude Code, Cursor, Copilot, and Codex pick them up automatically and follow the conventions (thin controllers,resHandlerresponses,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-resourceturns "add a books resource" into one consistent, checklisted operation.llms.txtgives doc-fetching tools a compact map of the conventions.
Nothing to install โ it's all in the scaffold. See AGENTS.md.
Scripts
| Command | What it does |
|---|---|
npm run dev | Start with hot reload (tsx watch) |
npm test / npm run test:watch | Run the vitest suite |
npm run verify | Typecheck + lint + test (CI runs this) |
npm run build / npm start | Compile to dist/ and run production build |
npm run gen <Name> | Generate a controller + test |
npm run lint / npm run format | ESLint / Prettier |
Enabling integrations
Copy .env.example to .env. Each integration turns on when its variables are set โ and stays completely dormant otherwise:
| Integration | Enable by setting | What you get |
|---|---|---|
| MongoDB | MONGODB_URI | Mongoose connection, readiness check, graceful disconnect |
| Auth0 | AUTH0_DOMAIN + AUTH0_AUDIENCE | JWT verification on every @protectedRoute |
| Sentry | SENTRY_DSN | Automatic 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:
GETnever 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 aPOST, 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, hardSESSION_ABSOLUTEcap.
| Variable | Default |
|---|---|
JWT_SECRET | (required) |
SESSION_IDLE / SESSION_ABSOLUTE | 30d / 90d |
MAGIC_TOKEN_TTL / MAGIC_CODE_ATTEMPTS | 15m / 5 |
MAGIC_LINK_BASE_URL | http://localhost:8000 |
SMTP_URL | unset โ 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:
- Getting started โ step-by-step tutorial
- Guides โ building an API ยท authentication ยท deployment
- Concepts โ architecture ยท modules & integrations
- Reference โ configuration ยท core API ยท CLI & scripts
- Maintainers guide โ publishing, releases, keeping the template fresh
Docker
docker compose up --build # API + MongoDB
docker build -t my-api . # production image only