CrabStack / NestJS and Next.js boilerplate

NestJS and Next.js boilerplate: two apps, one repo, one API contract

CrabStack pairs a NestJS 12 backend with a Next.js 16 frontend in one repository. Each half is its own application with its own package.json. An OpenAPI spec connects them, and the frontend's API types are generated from it.

This page explains how the two halves connect and what keeps them in step when an AI agent changes one of them.

Why two applications

Next.js can serve an API on its own. A separate NestJS backend is worth the extra process when the backend has rules of its own: billing, background workers, admin tools, account deletion. Those run on port 4000 whether the frontend is up or not. The frontend on port 3000 renders pages and calls the backend over REST.

The split also gives an agent a clear boundary. Backend conventions live in backend/CLAUDE.md, frontend conventions in frontend/CLAUDE.md, and the agent reads the one for the half it touches.

How the halves connect

  1. NestJS controllers and zod schemas produce the API description, through @nestjs/swagger and nestjs-zod.
  2. That description is written to openapi.json at the repo root. It is the single source of truth for the API.
  3. npm run generate-types in frontend/ turns the spec into types/api.d.ts with openapi-typescript.
  4. The frontend calls the backend through openapi-fetch, typed by those paths. A wrong path, a missing field or a renamed property is a type error before it is a runtime error.

Nobody edits openapi.json or types/api.d.ts by hand. Both are regenerated after a backend change and committed with it.

What CI checks

CI builds the spec from the backend code as it stands, and diffs it against the committed openapi.json. Any difference fails the build. A second step regenerates the frontend types and fails if they differ from the committed files. So a backend change that skipped regeneration cannot merge, and neither can frontend types that drifted from the spec.

How the frontend calls the backend

Pages are React Server Components. They fetch with serverApi(), which reads the session cookie on the server and attaches the bearer token. Forms submit to Server Actions in a colocated actions.ts. The bearer token never reaches the browser.

lib/api.ts imports server-only. A client component that imports from it fails the build. An agent that tries to call the API from the browser with a token gets a build error.

There is no TanStack Query or SWR. Server Components and Server Actions cover the data flow.

Running both halves

./setup.sh installs both halves and writes both .env files. docker compose up -d frontend backend maildev runs the app on port 3000, the API on 4000 and a local mail inbox. Postgres runs on 5433, so it does not collide with a Postgres you already run on 5432.

On the word monorepo

The repository holds both applications, so it answers searches for a NestJS Next.js monorepo. It has no monorepo tool: no Turborepo, Nx or package-manager workspaces. The halves share no TypeScript package; the OpenAPI spec is what they share. npm is the only supported package manager, with a committed package-lock.json in each half.

Who it fits

  • You want the backend to be its own deployable service from day one.
  • You want frontend types that cannot silently fall out of step with the API.
  • You build with an agent and want each half's rules written down where it will read them.

Who it does not fit

  • You want one Next.js app with API routes and no second process.
  • You want a Turborepo with shared packages between apps.
  • You want Nuxt, Remix or TanStack Start. The frontend is Next.js.
  • You want GraphQL or tRPC. The API is REST with OpenAPI.

Related

  • NestJS SaaS boilerplate: everything that ships
  • NestJS Postgres boilerplate: the data layer behind the API
  • Claude Code SaaS boilerplate: the agent files for each half
  • Vibe coding SaaS template: the checks that catch agent mistakes
  • Docs: How the two projects connect, Auth flow, Adding a Module
  • Compare: vs ShipFast, vs Makerkit, vs supastarter

FAQ

Is this a NestJS Next.js monorepo? It is one repository with two applications, backend/ and frontend/, each with its own package.json. It uses no monorepo tool and no shared package.

How do frontend types stay in sync with the backend? The backend produces openapi.json, and npm run generate-types turns it into types/api.d.ts. CI fails if either file differs from what the code produces.

Can the browser call the NestJS API directly? Not in the shipped pattern. Server Components and Server Actions call the backend on the server, and the bearer token stays there. Importing the server API client into a client component fails the build.

Which versions are used? NestJS 12, Next.js 16 with the App Router, React 19, TypeScript 6, zod 4, Knex 3, openapi-fetch and openapi-typescript 7. Node 24 and Node 26 are both supported, and CI runs both.

Can I deploy the two halves separately? Yes. They are separate applications with their own Docker images. The deploy/ kit runs both behind Caddy on one server.

Get the repo

$199 once, before tax: the repo, the tests, lifetime updates. Stripe is the merchant of record: checkout adds VAT or sales tax where it applies, and a local-currency price carries Stripe's 2-4% conversion fee. The full docs are public at /docs.