pnpm + uv monorepo for AI-powered fullstack apps. Next.js frontends, FastAPI backend, one typed contract between them.
apps/
web/ Next.js (App Router, Tailwind v4, vitest) — proxies /api/* to the API
api/ FastAPI + Anthropic SDK (uv)
src/api/
core/ config (UPPER_CASE settings), cross-cutting infra
db/ engine / session dependency
models/ SQLAlchemy ORM models (tables)
schemas/ Pydantic request/response models (camelCase on the wire)
services/ business logic (no FastAPI imports)
routers/ HTTP layer: thin, declares operation_id + response_model
scripts/ export_openapi.py
packages/
api-client/ openapi.json + generated types + typed fetch client & operations
ui/ shared shadcn components + theme contract (no design values)
tsconfig/ shared tsconfig presets
Option A: CLI (needs Node 22+, pnpm, uv). Scaffolds, installs, renames and makes the first commit:
npm create @cvrlnolan/fullstack my-app # or: npx @cvrlnolan/create-fullstack my-appFlags: --no-install (copy only), --no-git.
Option B: GitHub template
- On GitHub: Use this template → Create a new repository, then clone it.
cp .env.example .env,pnpm install,uv sync.pnpm init:project <your-name>— renames the project (root package, Python project, API title, app title), refreshesuv.lockand the OpenAPI schema, then deletes itself,tools/create-fullstackand this section. Commit the result.pnpm dev.- When you start real features, delete the example chat code (list in
AGENTS.md→ "Template example code").
The @app/* workspace scope and the app database name are intentionally generic; keep them.
(Maintainers: enable Settings → Template repository on the template repo. To publish the CLI: cd tools/create-fullstack && npm publish; it snapshots the git-tracked repo into the package on prepack, so commit first.)
Requires Node 22+, pnpm 10+, uv, Docker.
cp .env.example .env # add ANTHROPIC_API_KEY
pnpm install # also installs git hooks (lefthook)
uv sync
pnpm dev # docker compose: web :3000, api :8000, db :5432Without Docker: pnpm dev:api and pnpm dev:web in separate terminals.
| Command | What it does |
|---|---|
pnpm lint / lint:fix |
biome + ruff |
pnpm typecheck |
tsc + pyright (strict) |
pnpm test |
vitest (JS) + pytest (Python) |
pnpm api:schema |
re-export OpenAPI from FastAPI and regenerate TS types |
pnpm api:schema:check |
fail if the committed schema/types are stale (pre-commit + CI) |
pnpm check:pins |
fail if any dependency is not pinned to an exact version |
pnpm check |
everything above |
FastAPI is the source of truth. The flow is:
- Define Pydantic models in
schemas/extendingApiModel(snake_case in Python, camelCase on the wire). - Give each route an
operation_idandresponse_model. pnpm api:schema→ writespackages/api-client/openapi.jsonandsrc/schema.d.ts.- Add a typed function to
packages/api-client/src/operations.ts(or callclient.GET/POSTdirectly — paths, params and bodies are type-checked against the schema).
The pre-commit hook runs pnpm api:schema:check whenever API code or the generated files change, and CI runs it too, so the contract can't drift.
packages/ui holds the shadcn components. They only use semantic utilities (bg-primary, text-muted-foreground, rounded-lg…), and @app/ui/styles.css maps those to CSS variables without defining values. Each app owns the values:
/* apps/<app>/src/app/globals.css */
@import "tailwindcss";
@import "@app/ui/styles.css";
@source "../../../../packages/ui/src";
@import "@app/ui/tokens/neutral.css"; /* or your own token file */
:root { --primary: oklch(0.55 0.2 260); --radius: 0.75rem; }Same components, different look per app. Add components with the shadcn CLI (components.json is set up for the monorepo):
pnpm dlx shadcn@latest add dialog -c apps/web.
All dependencies are exact (.npmrc has save-exact=true; Python uses ==; pnpm check:pins enforces it in hooks and CI). For Python add with uv add --bounds exact <pkg>. Upgrade deliberately and review the diff.
- FE app:
apps/<name>with@app/<name>; depend on@app/uiand@app/api-client, add aglobals.cssas above. - Python app/lib: own
pyproject.toml, add to[tool.uv.workspace].members, and as a root dependency +[tool.uv.sources]entry. - Endpoint: schema → service → router →
pnpm api:schema→ operation.