Basilic
Architecture

API Architecture

Fastify 5 + TypeBox routes generate OpenAPI; @hey-api/openapi-ts generates @repo/core. React Query hooks in @repo/react are handwritten.

Fastify routes and TypeBox schemas are the source of truth for validation and for the OpenAPI spec. @hey-api/openapi-ts generates the TypeScript client in @repo/core. @repo/react wraps that client with handwritten TanStack Query 5 hooks — Hey API does not generate them.

Flow: TypeBox → OpenAPI → @repo/core (client + types) → handwritten @repo/react hooks. Commands: OpenAPI Generation.

Stack

  • Runtime: Node.js 24.x (LTS Krypton)
  • Framework: Fastify 5, TypeBox, ESM
  • Database: PostgreSQL via DATABASE_URL (PGLite when PGLITE=true)
  • ORM: Drizzle
  • Spec: OpenAPI 3 at apps/api/openapi/openapi.json, UI at /reference

How it works

  1. Author a route with TypeBox in apps/api/src/routes/ (one file per endpoint).
  2. Generate OpenAPI from those routes.
  3. Generate @repo/core. Add or update hooks in @repo/react by hand when the UI needs them.

Runtime: request → Fastify validation → handler → Drizzle → JSON.

import { Type } from '@sinclair/typebox'
import { dbHealth } from '../db/probe.js'

fastify.get('/health', {
  schema: {
    response: {
      200: Type.Object({ ok: Type.Boolean(), dbReady: Type.Boolean() }),
      503: Type.Object({ ok: Type.Boolean(), dbReady: Type.Boolean() }),
    },
  },
}, async (_request, reply) => {
  const dbReady = await dbHealth.probe()
  if (!dbReady) return reply.code(503).send({ ok: false, dbReady: false })
  return reply.code(200).send({ ok: true, dbReady: true })
})

GET /health is readiness: 200 when SELECT 1 succeeds, 503 when the store cannot answer. It does not probe Resend, AI, or IdPs.

createClient from @repo/core has three auth modes (see Authentication): no-auth, JWT (getAuthToken / refresh), API key.

import { createClient } from '@repo/core'
import { useHealthCheck } from '@repo/react'

const client = createClient({ baseUrl: process.env.NEXT_PUBLIC_API_URL! })
const { data } = useHealthCheck()

Other languages can use any OpenAPI generator against the same spec. Local DB: pnpm reset (Supabase reset + Drizzle migrate + seed). See ADR 008.

Security

Security headers, CORS via ALLOWED_ORIGINS, per-IP rate limiting, TypeBox validation, trustProxy behind a reverse proxy. Details: Security.

On this page