Introduction
A code-first headless CMS with Postgres, Hono, and a React admin UI.
CMS is a headless content management system designed around a single principle: your schema lives in code.
Entities, fields, roles, permissions, and hooks are all defined in TypeScript. There’s no runtime schema editing, no visual schema builder — just code that you version control, review, and deploy like any other part of your application.
Why code-first?
- Version controlled — your content model is in git, not a database you can’t diff
- Type-safe — TypeScript types are inferred from your schema definition
- Reviewable — schema changes go through pull requests, not admin clicks
- Reproducible — any environment can be recreated from code + migrations
Architecture
CMS is a single deployment: one server serves both the REST API and the admin UI.
┌─────────────────────────────────────┐
│ Hono Server │
│ │
│ /api/* /admin/* │
│ REST API React SPA │
│ │
│ Drizzle ORM + Postgres │
└─────────────────────────────────────┘
- Backend: Hono (HTTP) + Drizzle ORM (Postgres)
- Admin UI: React SPA served as static files at
/admin - Database: Postgres only — uses JSONB, GIN indexes, array types directly
- Config: A single
cms.config.tsfile defines everything
The admin UI can be disabled with ADMIN_UI=false for headless-only deployments.
Core concepts
- Entities — define your content types with typed fields
- Relations — link entities with single or many-to-many relations
- Permissions — role-based access control, defined in code
- Hooks — lifecycle callbacks for business logic
- Querying — filter, sort, paginate, and resolve relations via query strings
- i18n — entity-level translations with locale linking
- Media — centralized asset management with deduplication
- Globals — singleton entities for site-wide settings, navigation, and other one-row content
- API Keys — static tokens for SSGs, CI/CD, and server-to-server access
- Admin UI — extensible React admin with 4 tiers of customization
Next
Installation