{
  "markdown": "# 🏎️ Chassis\n\n**A lightweight, decorator-driven Express + TypeScript backend starter. Clone, run, ship.**\n\n**[📖 Documentation](https://dvd90.github.io/chassis/)** · [Getting started](https://dvd90.github.io/chassis/#getting-started) · [create-chassis on npm](https://www.npmjs.com/package/create-chassis)\n\nChassis 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.\n\n```ts\nexport class UserController extends Routable {\n  constructor() {\n    super('/users');\n  }\n\n  @route('get', '/:id')\n  async show(req: Request) {\n    const user = await findUser(req.params.id);\n    if (!user) throw new AppError(ERROR_CODES.NOT_FOUND, 'User not found');\n    return req.resHandler.ok(user);\n  }\n\n  @protectedRoute('post', '/', [validate({ body: createUserSchema })])\n  async create(req: Request) {\n    return req.resHandler.created(await createUser(req.body));\n  }\n}\n```\n\nExport the class from `src/controllers/index.ts` — that's the whole wiring.\n\n## Quick start\n\n```bash\nnpm create chassis my-api -- --yes                      # zero prompts: Postgres + JWT + Sentry + Docker\nnpm create chassis my-app -- --preset fullstack --yes   # the same, plus a Next.js front end\nnpm create chassis my-api                               # interactive — pick a preset\nnpm create chassis my-api -- --db postgres --auth jwt --mcp   # à la carte\nnpm create chassis my-api -- --bare                     # nothing — standalone build\n```\n\nOr use the template directly:\n\n```bash\ngit clone https://github.com/dvd90/chassis.git my-api\ncd my-api && npm install && npm run dev\n```\n\nThat's it — no database, no env file, no accounts needed. Open http://localhost:8000/status.\n\nNew here? Follow the **[step-by-step getting-started guide](docs/getting-started.md)** — zero to a tested API in ~10 minutes.\n\n## For AI agents\n\nEvery path is non-interactive: `--yes` and `--bare` never prompt, and the CLI\nskips prompts automatically whenever stdin isn't a TTY. One command produces a\nproject that already typechecks, lints and tests green.\n\n- **[llms.txt](https://dvd90.github.io/chassis/llms.txt)** — the project, its\n  conventions and its docs index, in one fetch\n- **[llms-full.txt](https://dvd90.github.io/chassis/llms-full.txt)** — every\n  documentation page, concatenated\n- **[AGENTS.md](AGENTS.md)** — the conventions to follow when writing code in a\n  Chassis project, and the definition of done\n\nGenerated projects carry `AGENTS.md`, `CLAUDE.md`, `llms.txt` and an\n`add-resource` skill, so whichever agent opens one writes code that matches the\nrest of the codebase rather than fighting it.\n\n## Features\n\n- **TypeScript 6 + Express 5** — strict types, async errors caught automatically\n- **Decorator routing** — `@route` / `@protectedRoute` on controller methods, controllers auto-mount\n- **Consistent responses** — `req.resHandler.ok() / .notFound() / .validation()` with structured logging\n- **Request correlation** — every request gets a `callId` (or propagates `x-call-id`), echoed in responses and logs\n- **Typed, validated config** — zod-checked environment via `src/config`; the app refuses to boot on bad config\n- **Zod input validation** — `validate({ body, query, params })` middleware with structured 400s\n- **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\n- **Opt-in integrations** — every module enables by env var, never required\n- **Payment-gated routes** — `@paidRoute('get', '/report', '$0.01')` via the x402 protocol (opt-in)\n- **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\n- **MCP server** — expose your API to AI agents as MCP tools (`npm run mcp`, opt-in)\n- **Health endpoints** — `/healthz` (liveness) and `/readyz` (readiness, checks enabled integrations)\n- **Graceful shutdown** — drains connections and closes integrations on SIGTERM/SIGINT\n- **Vitest + supertest** — fast tests against the pure app factory, no server or DB needed\n- **DB-aware code generator** — `npm run gen user` scaffolds a controller + test wired to your ORM (Drizzle or Mongoose)\n- **Production Docker** — multi-stage build, non-root user, plus docker-compose with your database for dev\n- **CI + Renovate** — GitHub Actions verify pipeline and automated dependency updates\n- **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)\n\n## AI-agent ready\n\nMost 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:\n\n- **`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).\n- **One obvious place for everything** means agent output converges on the same shape a maintainer would write — that's what keeps it readable.\n- **`npm run verify`** (strict TypeScript + ESLint + tests) is a deterministic quality gate agents iterate against until green.\n- **`.claude/skills/add-resource`** turns \"add a books resource\" into one consistent, checklisted operation.\n- **`llms.txt`** gives doc-fetching tools a compact map of the conventions.\n\nNothing to install — it's all in the scaffold. See [AGENTS.md](AGENTS.md).\n\n## Scripts\n\n| Command                           | What it does                                |\n| --------------------------------- | ------------------------------------------- |\n| `npm run dev`                     | Start with hot reload (tsx watch)           |\n| `npm test` / `npm run test:watch` | Run the vitest suite                        |\n| `npm run verify`                  | Typecheck + lint + test (CI runs this)      |\n| `npm run build` / `npm start`     | Compile to `dist/` and run production build |\n| `npm run gen <Name>`              | Generate a controller + test                |\n| `npm run lint` / `npm run format` | ESLint / Prettier                           |\n\n## Enabling integrations\n\nCopy `.env.example` to `.env`. Each integration turns on when its variables are set — and stays completely dormant otherwise:\n\n| Integration | Enable by setting                 | What you get                                              |\n| ----------- | --------------------------------- | --------------------------------------------------------- |\n| MongoDB     | `MONGODB_URI`                     | Mongoose connection, readiness check, graceful disconnect |\n| Auth0       | `AUTH0_DOMAIN` + `AUTH0_AUDIENCE` | JWT verification on every `@protectedRoute`               |\n| Sentry      | `SENTRY_DSN`                      | Automatic error reporting from the central error handler  |\n\nUsing a different IdP? Call `setAuthProvider([...yourMiddleware])` at boot and `@protectedRoute` uses it — see `src/core/auth.ts`.\n\n### Sign in without a third party\n\nLocal sign-in ships in three variants — emailed link, the classic credential\nform, or both. Run `npm create chassis --help` to see the `--auth` values, or\nread [Authentication](docs/guides/authentication.md). Whichever you pick, they\nshare one session layer.\n\n```\nPOST /auth/magic/request  {email, returnTo?}   → 202, identical for every address\nGET  /auth/magic/:token                        → confirm page — consumes nothing\nPOST /auth/magic/redeem   {token}              → session + redirect\nPOST /auth/magic/code     {email, code}        → same, from the other device\nPOST /auth/refresh | /auth/logout | /auth/revoke-all\n```\n\nFour things worth knowing about the emailed-link flow:\n\n- **`GET` never spends a token.** Mail security scanners prefetch links, and a\n  single-use token burned by a scanner is how this feature usually breaks in\n  production. Redemption is a `POST`, on a click.\n- **Every email carries a six-digit code too**, so someone who asks on a laptop\n  and reads their mail on a phone can still finish on the laptop.\n- **The request endpoint will not tell you who has an account** — same body,\n  same timing, every address.\n- **Refresh tokens rotate on every use**, and replaying a spent one revokes the\n  whole session family. Sliding `SESSION_IDLE`, hard `SESSION_ABSOLUTE` cap.\n\n| Variable                                  | Default                 |\n| ----------------------------------------- | ----------------------- |\n| `JWT_SECRET`                              | _(required)_            |\n| `SESSION_IDLE` / `SESSION_ABSOLUTE`       | `30d` / `90d`           |\n| `MAGIC_TOKEN_TTL` / `MAGIC_CODE_ATTEMPTS` | `15m` / `5`             |\n| `MAGIC_LINK_BASE_URL`                     | `http://localhost:8000` |\n| `SMTP_URL`                                | unset → logs the email  |\n\nChassis binds no email or SMS provider — bind yours through `setMailTransport()`\nor `setSmsTransport()`. Proving an address fires one hook, `setOnVerified()`,\nand that is the whole extension surface: consent and onboarding are yours.\n\nGuides: [magic link](docs/guides/magic-link.md) ·\n[sessions](docs/guides/sessions.md) ·\n[transports](docs/guides/transports.md)\n\n## Project structure\n\n```\nsrc/\n├── config/          # zod-validated env → typed config + feature flags\n├── core/            # the framework: Routable, decorators, responses, errors, validation\n├── middleware/      # callId correlation, dev request logging\n├── integrations/    # opt-in modules: mongo, auth0, sentry\n├── controllers/     # your endpoints — exported classes auto-mount\n├── __tests__/       # vitest + supertest\n├── app.ts           # pure app factory (no I/O — trivially testable)\n└── server.ts        # boot: integrations → listen → graceful shutdown\n```\n\n## Documentation\n\nRead them at **[dvd90.github.io/chassis](https://dvd90.github.io/chassis/)** —\nsearchable, one page. The source lives in [`docs/`](docs/README.md) and the site\nis generated from it, so the two can never disagree:\n\n- **[Getting started](docs/getting-started.md)** — step-by-step tutorial\n- **Guides** — [building an API](docs/guides/building-an-api.md) · [authentication](docs/guides/authentication.md) · [deployment](docs/guides/deployment.md)\n- **Concepts** — [architecture](docs/architecture.md) · [modules & integrations](docs/modules.md)\n- **Reference** — [configuration](docs/reference/configuration.md) · [core API](docs/reference/core-api.md) · [CLI & scripts](docs/reference/cli.md)\n- **[Maintainers guide](docs/maintainers.md)** — publishing, releases, keeping the template fresh\n\n## Docker\n\n```bash\ndocker compose up --build     # API + MongoDB\ndocker build -t my-api .      # production image only\n```\n\n## License\n\n[MIT](LICENSE)\n",
  "bytes": 11380,
  "sha": "fd205d9b0f31f0b31cae1b96dd40209e1e182aa300cf72af7a52c6e75b3d7941",
  "repo_slug": "dvd90/chassis",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_dvd90_chassis_mcp_980df337/readme"
}