A production-grade, platform-agnostic full-stack skeleton. Next.js + FastAPI · built to scale on Kubernetes · zero business logic.
Fork it, delete the example feature, and ship your product on day one instead of spending the first month wiring config, logging, auth scaffolding, migrations, containers, CI, and Kubernetes manifests.
Quickstart · Architecture · Add a feature · Deploy · ARCHITECTURE.md · docs/
Every new product starts with the same three weeks of undifferentiated plumbing. This repo is that plumbing, done once and done well, so you never write it again.
It is deliberately empty of features. There is exactly one trivial example
endpoint (/api/v1/items) wired end-to-end — frontend to API to service to
repository to database — purely to show you the seams. Delete it and the
skeleton still stands.
What you get instead of features:
- A real architecture, not a single file that grows into a swamp. Clear layers (transport, service, repository, data) with dependency rules that keep a 200-file codebase as navigable as a 20-file one.
- Horizontal scalability baked in. Stateless services, externalized session/cache, health/readiness probes, graceful shutdown, and a Kubernetes HPA so the answer to "we got traffic" is "it already scaled."
- Platform-agnostic. Run it with
docker compose upon a laptop, orkubectl apply -kon any conformant cluster (EKS, GKE, AKS, k3s, kind). No managed-service lock-in is assumed. - Opinionated defaults, swappable parts. Postgres + Redis today; the repository and cache interfaces mean you can swap them without touching business code.
- The boring, critical stuff is finished: typed config, structured logging with request-ID correlation, RFC-9457 error responses, CORS, rate-limit hook, OpenTelemetry tracing, Prometheus metrics, migrations, tests, multi-stage non-root Docker images, and CI that runs the same checks you run locally.
If you are a startup or a "vibe coder," the goal is simple: git clone,
make up, start building the thing that actually makes you different.
| Layer | Choice | Why |
|---|---|---|
| Frontend | Next.js 16 (App Router) + React 19 + TS | SSR/RSC, huge ecosystem, the default for fast product work |
| Backend | FastAPI + Python 3.13 | Async, type-driven, auto OpenAPI, gentle learning curve |
| Validation/DTOs | Pydantic v2 | Fast, strict, the FastAPI-native contract layer |
| Data access | SQLAlchemy 2.0 (async) + Alembic | Mature ORM + first-class migrations |
| Database | PostgreSQL | The dependable default for 99% of products |
| Cache / broker | Redis | Cache, rate-limit store, lightweight queue |
| Monorepo | pnpm workspaces + Turborepo | One repo, fast cached builds, shared types |
| Containers | Multi-stage Docker (non-root) | Small, reproducible, secure images |
| Orchestration | Kubernetes + Kustomize | Cloud-agnostic, base + per-env overlays, HPA autoscaling |
| Observability | OpenTelemetry + Prometheus + structured logs | Traces, metrics, logs correlated by request ID |
| CI/CD | GitHub Actions | Lint, typecheck, test, build, image — mirrors make check |
See ARCHITECTURE.md for the full design rationale and
docs/adr/ for the decision records behind each choice.
Most code in a fork like this won't be typed by you — it'll be written by Claude, Cursor, Copilot, or whatever agent comes next. So this starter keeps the rules in the repo, machine-readable, and every fork inherits them: any agent that opens your code is told how to work here before it writes a line.
That's not only the system design (stateless, layered, migration-owned schema) — it's a full code of conduct: understand the code and find the right place before changing it, no hardcoding, find the best solution rather than the first, keep changes minimal and finished, verify before claiming done, and report honestly.
| File | Read by |
|---|---|
AGENTS.md |
the cross-tool standard — Claude, Cursor, Codex, Gemini CLI, … |
CLAUDE.md |
Claude Code (auto-loaded) |
.cursor/rules/ · .github/copilot-instructions.md |
Cursor · GitHub Copilot |
.claude/skills/add-feature/ |
an invokable skill for the highest-drift task (adding a resource) |
These are guidance, not gates — on purpose. When you fork this repo they come
with it, so every agent working in your codebase reads and follows them by
default, and a reviewer (human or AI) can cite them to raise concerns at review
time. They never hard-block: your product might genuinely need to break a rule,
and that's your call — just write it down (an ADR in docs/adr/). The only things
that must pass are the ordinary quality gates you already run — make check:
lint, types, tests, formatting, and contract sync.
flowchart TB
subgraph client["Client"]
B["Browser / Mobile"]
end
subgraph edge["Edge"]
IG["Ingress / Load Balancer<br/>(TLS, routing)"]
end
subgraph cluster["Kubernetes cluster"]
subgraph webd["web Deployment (N pods, HPA)"]
W["Next.js<br/>SSR + RSC"]
end
subgraph apid["api Deployment (N pods, HPA)"]
A["FastAPI<br/>stateless"]
end
WK["worker Deployment<br/>(background jobs)"]
end
subgraph data["Stateful backing services"]
PG[("PostgreSQL<br/>primary + replicas")]
RD[("Redis<br/>cache / broker")]
end
OTEL["OpenTelemetry Collector"]
PROM["Prometheus / Grafana"]
B --> IG
IG -->|"/"| W
IG -->|"/api"| A
W -->|"server-side fetch"| A
A --> PG
A --> RD
A -. "enqueue" .-> RD
RD -. "dequeue" .-> WK
WK --> PG
A -. "traces/metrics" .-> OTEL
WK -. "traces/metrics" .-> OTEL
A -. "/metrics scrape" .-> PROM
The backend follows a layered (ports-and-adapters) architecture. Dependencies point inward only — transport knows about services, services know about repositories, nothing in the core knows about HTTP or SQL specifics:
flowchart LR
HTTP["API layer<br/>routers, deps, middleware"]
SVC["Service layer<br/>use-cases / business rules"]
REPO["Repository layer<br/>data-access interfaces"]
DB[("DB / Redis<br/>adapters")]
HTTP --> SVC --> REPO --> DB
Full request lifecycle, scaling model, and failure handling live in
ARCHITECTURE.md.
scalable-starter/
├── apps/
│ ├── web/ # Next.js frontend (App Router, TS strict)
│ │ ├── app/ # routes, layouts, route handlers
│ │ ├── components/ # presentational components
│ │ └── lib/ # api-client, typed env, utils
│ └── api/ # FastAPI backend (layered)
│ └── app/
│ ├── core/ # config, logging, security, lifespan
│ ├── api/v1/ # routers + dependencies (transport)
│ ├── services/ # use-cases (business rules) <-- your logic
│ ├── repositories/ # data-access interfaces + impls
│ ├── schemas/ # Pydantic DTOs (the API contract)
│ ├── db/ # async engine, session, models
│ ├── middleware/ # request-id, timing, error handling
│ ├── observability/# tracing + metrics
│ └── workers/ # background job consumer skeleton
├── packages/ # shared TS packages (tsconfig, eslint, api-contract)
├── infra/
│ ├── docker/ # multi-stage Dockerfiles
│ └── k8s/ # kustomize: base/ + overlays/{dev,staging,prod}
├── docs/ # guides, ADRs, improvement log
├── .github/workflows/ # CI/CD
├── docker-compose.yml # local dev stack
├── Makefile # every command you need (`make help`)
└── ARCHITECTURE.md # the system design, in depth
Option A — Docker (recommended, nothing to install but Docker):
cp .env.example .env
make up # builds & starts web + api + postgres + redis- Web: http://localhost:3000
- API docs (Swagger): http://localhost:8000/docs
- Health: http://localhost:8000/healthz
Option B — Local dev (hot reload, requires Node 20+ and Python 3.12+):
make setup # installs JS + Python deps, creates .env
make migrate # apply database migrations
make dev # runs web and api in watch modeRun make help to see every available command.
The skeleton's whole point is that adding a feature is mechanical. To add a
projects resource you touch one file per layer — the example items
feature is your copy-paste template:
- Model —
app/db/models/project.py, exported frommodels/__init__.pyso Alembic sees it. - Schema —
app/schemas/project.py: PydanticProjectCreate/ProjectRead. - Repository —
app/repositories/project.py: subclass the generic async repo. - Service —
app/services/project.py: your business rules. - Router —
app/api/v1/routes/projects.py: thin HTTP layer, calls the service. - Register the router in
app/api/v1/router.py, runmake migration m="add projects", and you're done.
The frontend mirrors this: add a typed client call in lib/api-client.ts and a
route under app/. See docs/guides/adding-a-feature.md.
Anywhere Docker runs — build the images directly:
docker build -f infra/docker/api.Dockerfile -t myreg/app-api:tag .
docker build -f infra/docker/web.Dockerfile -t myreg/app-web:tag .Kubernetes (any conformant cluster — EKS, GKE, AKS, k3s, kind):
kubectl kustomize infra/k8s/overlays/prod # inspect what will be applied
kubectl apply -k infra/k8s/overlays/prod # apply itEach overlay (dev / staging / prod) patches replica counts, resource
limits, image tags, and env without duplicating the base manifests. Autoscaling
(HPA), pod-disruption budgets, network policies, and liveness/readiness probes
are part of the base. See docs/guides/deployment.md
and docs/guides/scaling.md.
Nothing here is load-bearing if you don't want it to be:
- Different database? Implement the repository interface against your store; services never see SQL.
- Different frontend (mobile, another SPA)? The API is a clean, documented OpenAPI surface — point any client at it.
- Different Python framework or even language? The layered boundaries and the
infra/+docs/scaffolding transfer; onlyapps/api/appchanges. - Serverless instead of K8s? The stateless API and typed config port cleanly;
drop
infra/k8sand add your platform's adapter.
MIT — do whatever you want. Attribution appreciated, not required.