Back to the catalog

Chassis

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

Open source Open in the app JSON README (API)

About

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

Details

Kind
MCP servers
Topic
Developer tools
Publisher
dvd90
Origin
official
Category
ferramentas
Transport
local
Version
0.1.2
Stars
6
Last push
2026-08-07T12:13:52Z
Repository state
ativo
Language
TypeScript
License
MIT
Added
2026-08-29 03:02:44
Updated
2026-08-29 03:02:44
Origin id
io.github.dvd90/chassis-mcp

README

# ๐ŸŽ๏ธ Chassis

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

**[๐Ÿ“– 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)

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.

```ts
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

```bash
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:

```bash
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](docs/getting-started.md)** โ€” 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](https://dvd90.github.io/chassis/llms.txt)** โ€” the project, its
  conventions and its docs index, in one fetch
- **[llms-full.txt](https://dvd90.github.io/chassis/llms-full.txt)** โ€” every
  documentation page, concatenated
- **[AGENTS.md](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](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](docs/guides/authentication.md). 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.

| 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](docs/guides/magic-link.md) ยท
[sessions](docs/guides/sessions.md) ยท
[transports](docs/guides/transports.md)

## 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](https://dvd90.github.io/chassis/)** โ€”
searchable, one page. The source lives in [`docs/`](docs/README.md) and the site
is generated from it, so the two can never disagree:

- **[Getting started](docs/getting-started.md)** โ€” step-by-step tutorial
- **Guides** โ€” [building an API](docs/guides/building-an-api.md) ยท [authentication](docs/guides/authentication.md) ยท [deployment](docs/guides/deployment.md)
- **Concepts** โ€” [architecture](docs/architecture.md) ยท [modules & integrations](docs/modules.md)
- **Reference** โ€” [configuration](docs/reference/configuration.md) ยท [core API](docs/reference/core-api.md) ยท [CLI & scripts](docs/reference/cli.md)
- **[Maintainers guide](docs/maintainers.md)** โ€” publishing, releases, keeping the template fresh

## Docker

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

## License

[MIT](LICENSE)

More