API proxying
How server routes are intercepted and forwarded.
Interception
In development, bionicjs dev exposes one public origin: localhost:3000. Any request starting with /api is intercepted by the Vite dev server and forwarded to the internal Nitro server running on port 3001:
1server: {
2 port: publicPort,
3 strictPort: true,
4 proxy: {
5 "/api": `http://localhost:${nitroPort}`,
6 },
7},
The developer sees only one origin, and the browser sees only one origin. Requests to /api/... never trigger CORS, never carry a different host header, and never need the frontend to know where the backend actually lives.
1browser --fetch("/api/auth/login")--
2 Vite (:3000)
3 --proxy--> Nitro (:3001)
4 --handler--> h3 handler
5 --rpc--> Python (optional)
Dev vs production
The application code never needs to know whether it is running in dev or prod - it simply fetches /api. The difference is only in the routing layer:
| Concern | Development | Production |
|---|---|---|
| API serving | Vite proxy to Nitro | Nitro serves /api directly |
| Static assets | Served by Vite with HMR | Served by Nitro from public/ |
| Unknown routes | Vite SPA fallback | Nitro SPA fallback |
| Origins | One public origin (:3000) | One origin (your domain) |
In production, the Nitro server itself handles the /api routes natively, while serving static files and the React application fallback for everything else. There is no proxy, no second process, and no extra hop - the same artifact that serves your HTML also serves your API.
17 / 25