API Routes
API routes live in src/api/ and can be written in TypeScript/JavaScript or Go. Both compile into the same /api/* namespace and can be mixed per route. When a .go and a .ts/.js file map to the same /api path, the compiled Go route takes precedence.
Route mapping follows the page convention:
| File | Route |
|---|---|
src/api/hello.ts | /api/hello |
src/api/users/index.go | /api/users |
src/api/users/[id].go | /api/users/{id} |
JS/TS routes
JS/TS routes are compiled to JS and served via a Node.js sidecar (port 3001) or the embedded QuickJS runtime.
Modern pattern (recommended): named method exports
// src/api/users.ts export async function GET(request: Request) { const users = await db.getUsers(); return Response.json(users); } export async function POST(request: Request) { const body = await request.json(); const user = await db.createUser(body); return Response.json(user, { status: 201 }); } Legacy pattern: default export
// src/api/legacy.ts export default function handler(req, res) { res.json({ message: 'legacy pattern' }); } Go routes
Go routes (src/api/*.go) are compiled into a dedicated Go sidecar binary (.krate/goapi-server[.exe], port +2) for maximum performance and true concurrency.
// src/api/hello.go — GET /api/hello package api import ( "net/http" "krate-goapi/runtime" ) func GET(w http.ResponseWriter, r *http.Request) { runtime.WriteJSON(w, 200, map[string]interface{}{"hello": "world"}) } Each file defines a Handler(w, r) function for all methods, and/or named GET/POST/PUT/DELETE/PATCH/OPTIONS/HEAD functions for per-method dispatch. Dynamic segments use [param] file names and are read with r.PathValue("param"):
// src/api/users/[id].go — all methods on /api/users/{id} func Handler(w http.ResponseWriter, r *http.Request) { runtime.WriteJSON(w, 200, map[string]interface{}{"id": r.PathValue("id")}) } Go routes require the Go toolchain at build time and should stick to the stdlib plus the krate-goapi/runtime helper package.