A production-ready monorepo template for full-stack applications with FastAPI (Python) + Vike/React (TypeScript), featuring complete authentication, SSR, background workers, and one-click Render deployment.
| Layer | Technology |
|---|---|
| Backend | Python 3.13, FastAPI, SQLAlchemy 2.0 (async), Alembic, arq + Redis |
| Frontend | Vike (SSR on Vite), React 19, TypeScript, Tailwind CSS v4, shadcn/ui |
| Database | PostgreSQL (asyncpg driver) |
| Cache/Queue | Redis (arq broker + rate limiting) |
| Deployment | Render Blueprint (API + SSR + Worker + Postgres + Redis) |
| CI | GitHub Actions (lint + format + type check + test + build) |
- User registration with email verification
- Login with JWT (access + refresh tokens in cookies for SSR)
- Forgot password / reset password with email links
- Role-based access control (ADMIN, USER)
- Route guards (server-side + client-side)
- Seed script with default admin user
BaseRepositorygeneric CRUD (get, list, create, update, delete, soft_delete)- Consistent error envelope (
{ "error": { "code", "message", "detail" } }) - Pagination (
PaginationParamsdependency +PaginatedResponse) - Cron job decorator (
@cron("name", hour=2)— auto-registered) - Pluggable email (console for dev, Resend or SMTP for prod)
- Structured logging (structlog with request IDs)
- Redis-backed rate limiting
- Sentry integration
- 90% test coverage enforced
- File-based routing with Vike
- SSR with Express production server
- React Query for auth state (
useCurrentUser()hook) - Axios client with token refresh interceptor
- Isomorphic cookie helpers (works in SSR + browser)
- Dark-themed dashboard layout with sidebar
- 80% test coverage target
git clone https://github.com/laxcoders/fastapi-vike-starter.git myapp
cd myapp
rm -rf .git && git init
# Run setup — replaces all template placeholders
./setup.sh myapp "My App"createdb myapp
createdb myapp_testcd server
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
python -m alembic upgrade head
python scripts/seed.py # creates admin@example.com / admin123cd web
npm install# Terminal 1 — API
cd server && source .venv/bin/activate
uvicorn app.main:app --reload --port 8000
# Terminal 2 — Frontend
cd web && npm run devOpen http://localhost:3000. Login with admin@example.com / admin123.
API docs (Swagger): http://localhost:8000/api/docs (only when DEBUG=true).
# Start Redis first
brew services start redis # or: docker run -d -p 6379:6379 redis
# Terminal 3 — arq worker (runs on-demand jobs AND cron schedules)
cd server && source .venv/bin/activate
arq app.worker.WorkerSettingsOne process runs both queue jobs and cron. No separate Beat scheduler.
├── server/ # Python backend
│ ├── app/
│ │ ├── api/ # Route handlers (thin controllers)
│ │ ├── models/ # SQLAlchemy ORM models
│ │ ├── schemas/ # Pydantic request/response schemas
│ │ ├── services/ # Business logic + BaseRepository
│ │ ├── middleware/ # Logging, rate limiting, errors
│ │ ├── worker.py # arq WorkerSettings (jobs + cron schedule)
│ │ ├── workers/ # Job implementations (monitoring, etc.)
│ │ ├── templates/ # Email templates (Jinja2)
│ │ └── utils/ # Exceptions, pagination
│ ├── tests/ # pytest (unit + integration)
│ ├── alembic/ # Database migrations
│ └── scripts/ # seed.py
│
├── web/ # Vike frontend
│ ├── pages/ # File-based routing (+Page, +Layout, +guard)
│ ├── components/ # Shared UI components (shadcn/ui)
│ ├── services/ # Axios client + API service functions
│ ├── hooks/ # React hooks (useCurrentUser, useLogout)
│ ├── lib/ # App config, types, cookie helpers, utilities
│ └── tests/ # Vitest + React Testing Library
│
├── .github/workflows/ # CI pipeline
├── render.yaml # Render deployment Blueprint
├── setup.sh # Project initialization script
└── CLAUDE.md # AI assistant context
POST /api/auth/register— creates user, sends verification email- User clicks email link →
GET /verify-email?token=... POST /api/auth/verify-email— marks user as verified- User can now log in
POST /api/auth/login— returns{ access_token, refresh_token }in the response body- Web client stores them in cookies so Vike
+guard.tsfiles can read them during SSR POST /api/auth/refresh— accepts{ refresh_token }in the body, returns new tokens
The backend never sets or reads auth cookies. get_current_user uses FastAPI's HTTPBearer, login returns tokens in the body, and refresh reads the token from the request body. The same API supports:
- Web (the Vike frontend in this repo) — Axios interceptor stores tokens in cookies so SSR guards can read them from request headers.
- Mobile (React Native, native iOS/Android — lives in its own repo) — client stores tokens in SecureStore / Keychain and attaches
Authorization: Bearer <token>on every request. No cookies involved.
server/tests/integration/test_mobile_client_flow.py locks this contract in — it runs the full register → verify → login → protected call → refresh → protected call flow using only Authorization headers and asserts no Set-Cookie headers ever appear. If anyone adds a cookie-only code path, CI fails.
POST /api/auth/forgot-password— sends reset email- User clicks link →
GET /reset-password?token=... POST /api/auth/reset-password— updates password
- Cookies, not localStorage (web only) — required for Vike SSR guards to read tokens from request headers
- Guards are
+guard.ts, not+guard.client.ts— enforced on server render - Isomorphic cookie helpers —
getCookie(name, cookieStr?)works server + client - React Query for auth state —
useCurrentUser()hook, no Zustand
| Mode | When | Config |
|---|---|---|
| Console | Local dev (default) | EMAIL_BACKEND=console — prints to stdout |
| Resend | Production | EMAIL_BACKEND=resend, set RESEND_API_KEY |
| SMTP | Self-hosted | EMAIL_BACKEND=smtp, set SMTP_HOST, SMTP_PORT, etc. |
cd server && source .venv/bin/activate
# Create test database (once)
createdb {{APP_SLUG_UNDERSCORE}}_test
# Run all tests
pytest tests/ -v
# Single file
pytest tests/unit/test_auth_service.py -v
# By name pattern
pytest tests/ -k "test_login"Coverage enforced at 90% minimum.
cd web
npm test # run once
npm run test:watch # watch mode
npm run test:coverage # with coverage report- Create model in
server/app/models/new_table.py - Import it in
server/app/models/__init__.py - Generate migration:
cd server && alembic revision --autogenerate -m "add new_table" - Review the generated migration in
alembic/versions/ - Apply:
python -m alembic upgrade head - Test rollback:
alembic downgrade -1thenpython -m alembic upgrade head
- Push to GitHub
- In Render Dashboard → New → Blueprint → connect repo
- Render reads
render.yamland creates all services automatically - Set additional env vars in Render Dashboard (on the API service):
EMAIL_BACKEND=resendRESEND_API_KEY= your keyEMAIL_FROM={{APP_DISPLAY_NAME}} <noreply@yourdomain.com>SENTRY_DSN= your DSN (optional)
- Check health:
curl https://{{APP_SLUG}}-api.onrender.com/health— should return{"status": "healthy", "postgres": "connected", "redis": "connected"} - Open
https://{{APP_SLUG}}-web.onrender.com - Run seed script via Render Shell:
cd server && python scripts/seed.py - Login with
admin@example.com/admin123
The API service runs python -m alembic upgrade head before each deploy. This ensures migrations run automatically.
CORS errors / API calls failing: Verify these env vars match your actual Render service URLs:
VITE_API_URL(on the web service) — must behttps://<slug>-api.onrender.comCORS_ORIGINS(on the API service) — must behttps://<slug>-web.onrender.comFRONTEND_URL(on the API service) — must behttps://<slug>-web.onrender.com
If you used underscores in your app slug (e.g. starter_app instead of starter-app), the Render URLs will be wrong. The slug must use hyphens — setup.sh enforces this.
Email verification/password reset links point to localhost: Set FRONTEND_URL on the API service to your web service URL.
Login works but page shows "Loading...": The frontend can't reach the API. Check VITE_API_URL on the web service and CORS_ORIGINS on the API service.
GitHub Actions runs on every push/PR to main:
Backend job:
ruff check app/ tests/— lintruff format --check app/ tests/— format verificationmypy app/ --ignore-missing-imports— type checkpytest tests/ -v— tests (90% coverage enforced)
Frontend job:
npm run lint— ESLintnpm run typecheck— tscnpm test -- --coverage— Vitestnpm run build— production build
Run all checks before committing:
# Backend (from server/)
ruff check app/ tests/
ruff format --check app/ tests/
mypy app/ --ignore-missing-imports
pytest tests/ -v
# Frontend (from web/)
npm run lint
npm run typecheck
npm test- Python 3.13 and Node.js 22 (pinned in
.tool-versions— install with asdf) - PostgreSQL 15+
- Redis 7+ (for workers and rate limiting)
MIT