This project is a full-stack app with a Next.js (React) frontend using the App Router, Tailwind CSS v4 + shadcn/ui, and a FastAPI backend.
- Server Actions first: Next.js Server Actions call FastAPI directly from the server without exposing backend URLs to the browser.
- Typed contracts: FastAPI responses are validated on the frontend with
zodso regressions are caught immediately. - Modern styling: Tailwind v4
@themetokens power shadcn/ui components, theme switching, and animation primitives. - Tests built-in: Vitest covers the shared contracts, while pytest + httpx validate the FastAPI routers.
If you are new to this stack, think of it as:
- Next.js handles routing, rendering, and UI.
- FastAPI exposes a typed JSON API.
- Zod bridges the two by validating everything that crosses the boundary.
-- frontend/ — Next.js 16 (App Router) + React 19 + shadcn/ui + Tailwind CSS v4 (TypeScript)
app/— App Router pages, layouts, and API routesapp/page.tsx— Home page that calls Server Actionsapp/actions.ts— Server Actions that talk to FastAPIapp/api/health/route.ts— Health check proxy for external monitors
components/— React components including shadcn/uicomponents/hello-form.tsx— Client component with a form that calls a Server Actioncomponents/health-status.tsx— Small status indicator for backend healthcomponents/ui/— shadcn/ui primitives (button, card, input, etc.)
lib/— Shared frontend utilities and contractslib/backend-client.ts— Thinfetchwrapper used by Server Actionslib/contracts.ts— Zod schemas mirroring FastAPI responseslib/greeting-state.ts— State machine for the greeting form --backend/— FastAPI + Uvicorn (Python 3.11)
main.py— FastAPI app with lifespan + loggingconfig.py— Pydantic settings (ports, URLs, log level, etc.)api/— Versioned routers and schemasapi/v1/greetings.py—GET /api/v1/hellogreeting endpointapi/v1/health.py—GET /api/v1/healthhealth endpointapi/schemas.py— Pydantic models shared across routers
tests/— pytest suite usinghttpx.AsyncClient
- Node.js 20+ (in your PATH)
- Python 3.11+ (in your PATH)
- pnpm (in your PATH)
cd frontend
pnpm install
# (optional) Add more shadcn components:
pnpm dlx shadcn@latest add [component-name]
pnpm devVisit http://localhost:3000 to see the Server Action-powered greeting page.
Server Actions call FastAPI via the shared backendJson helper. If you still
need a browser-accessible endpoint, the /app/api/* routes proxy requests with
response validation before returning data to the client.
At a high level, a greeting request flows like this:
- User types a name into
HelloForm(components/hello-form.tsx) and submits. - The form calls the
requestGreetingServer Action inapp/actions.ts. requestGreetingcallsbackendJsonfromlib/backend-client.tswith the FastAPI URL and the relevant Zod schema fromlib/contracts.ts.- FastAPI serves the request from
backend/api/v1/greetings.pyand returns a JSON payload. backendJsonvalidates the JSON with Zod and returns a typed object to the Server Action, which updates the greeting state.
The health check uses a slightly different path:
Home(app/page.tsx) calls thefetchHealthServer Action.fetchHealthcalls the FastAPI/api/v1/healthendpoint usingbackendJson.- The
/app/api/health/route.tsAPI Route exists only for external monitors that need a simple unauthenticatedGET /api/healthendpoint.
cd backend
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
# For development (linting, testing):
pip install -r requirements-dev.txtStart the server:
uvicorn main:app --reloadVisit http://localhost:8000 to see the FastAPI Hello World endpoint, and try
the example query endpoint:
http://localhost:8000/hello?name=YourName=> { "message": "Hello, YourName from FastAPI!" }
Copy .env.example to .env and customize as needed:
cp .env.example .envThe app will work with defaults, but you can customize:
FRONTEND_PORT- Frontend dev server port (default: 3000)BACKEND_PORT- Backend server port (default: 8000)BACKEND_URL- Full backend URL (optional, overrides BACKEND_PORT)LOG_LEVEL- Backend logging level (defaultINFO).
To ensure code quality, run the following commands in the frontend/ directory:
- Linting:
pnpm lint(Checks for errors) - Formatting:
pnpm format(Fixes formatting issues) - Unit tests:
pnpm test
In the backend/ directory run:
- Unit tests:
pytest
The run-dev.sh script automatically runs linting and the relevant unit
tests (frontend Vitest + backend pytest) before starting the development
servers. If any check fails, the script reruns it with full output and aborts
the startup.
- Frontend: set
FRONTEND_PORTenvironment variable to choose the dev port (default3000). Example:
FRONTEND_PORT=4000 pnpm dev- Backend: set
BACKEND_PORTenvironment variable (default8000). Example:
BACKEND_PORT=9000 ./run_uvicorn.shRun both services together
You can run both the frontend and backend dev servers together with run-dev.sh
at the repository root. The script starts both servers, writes logs to
./logs/, and stops both cleanly if it is interrupted (SIGINT/SIGTERM).
# default ports (frontend:3000, backend:8000)
./run-dev.sh
# custom ports
./run-dev.sh -f 4000 -b 9000Logs are written to ./logs/frontend.log and ./logs/backend.log.
Follow them with: tail -F logs/frontend.log logs/backend.log.
To update dependencies:
cd frontend && pnpm update --latest
cd ..
source backend/.venv/bin/activate && pip install --upgrade pip && pip install --upgrade -r backend/requirements.txt -r backend/requirements-dev.txtcd frontend
pnpm build
pnpm startcd backend
source .venv/bin/activate
uvicorn main:app --host 0.0.0.0 --port 8000External monitoring services (e.g., AWS ALB, UptimeRobot) should check the frontend's health endpoint:
- URL:
GET /api/health - Success:
200 OK{"status": "healthy"} - Failure:
503 Service Unavailable{"status": "unhealthy"}
While most of the application uses Server Actions to communicate with the
backend, the health check requires a dedicated API Route
(/app/api/health/route.ts) for specific reasons:
- Protocol Compatibility: Server Actions use a specialized POST protocol
internal to Next.js. Standard load balancers and monitoring tools expect a
simple HTTP
GETrequest returning a 200 OK status code. - Network Isolation: In production, the FastAPI backend is often deployed in a private network, inaccessible to the public internet. The frontend acts as a gateway.
- Status Codes: The proxy route explicitly translates backend connectivity issues into standard HTTP 503 status codes, which automated monitors rely on to detect failures.
Note: Other application features do not use proxy routes. They use Server Actions to communicate directly from the Next.js server to the FastAPI backend. This keeps the backend API private, reduces the public attack surface, and maintains type safety without manually defining API routes for every feature.
- Each developer should create their own
.venvinbackend/and install dependencies there. - Do not share virtual environments between users.
- The
pnpmbinary can be shared, but each user’s global packages/cache are separate by default.