A server-first showcase/proof app built with Kujo + SQLite, plus a minimal Next.js playground UI.
It boots from main.kujo at the repo root; there is no standalone CLI wrapper to validate.
This project is intentionally small so you can clone it and extend it quickly.
- A working CRUD backend for
itemsandprojects - Sample data seeded on startup
- Seed fixtures are data-driven and idempotent (restarts do not duplicate demo rows)
- Simple, practical request validation
- Pluggable write-route auth strategies (static bearer or custom header value)
- A single-page frontend playground that exercises the API
- Not a full CMS
- Not a plugin/theme framework
- Not a turnkey deployment or production-certified platform
Use it as a clean starting point.
If you intentionally need a broader listener, set KUJO_CRUD_API_HOST explicitly and keep the reviewed showcase path loopback-local by default.
cd /path/to/crud-api
kujo run main.kujoOr run the interpreter path directly:
kujo run main.kujo --interpreterDefault API URL: http://127.0.0.1:4100
Default bind host: 127.0.0.1
Expected startup banner:
Kujo CRUD API Showcase
Server: http://127.0.0.1:4100
Database: data/kujo_crud_api.db
Verify the API:
curl -s http://127.0.0.1:4100/healthFresh database expected shape:
{"status":"ok","database":"connected","items":5}cd frontend
cp .env.example .env.local
npm install
npm run devThe frontend reads NEXT_PUBLIC_API_BASE_URL from .env.local (default: http://127.0.0.1:4100) for both API requests and the topbar API root link.
Default frontend URL: http://localhost:3000
From repo root:
make run-api
make run-frontend
make lint
make testThe Makefile defaults to the installed kujo command. Set a custom runtime binary only when needed:
make test KUJO_BIN=/path/to/kujo- GitHub Actions workflow:
.github/workflows/ci.yml - Triggered on pushes to
mainand pull requests targetingmain. - CI uses pinned runner (
ubuntu-24.04) and Node (20.17.0) for reproducible frontend and backend test runs. The backend suite expects akujobinary onPATH. - Contribution guide:
CONTRIBUTING.md - Security policy:
SECURITY.md - Release checklist:
RELEASE.md - DR runbook:
docs/DR_RUNBOOK.md - Required quality gates:
- Frontend lint and production build.
- Backend regression suite (
make test) with Kujo runtime installation in CI. - Security gates (
npm audit --audit-level=high, secret pattern scan, and frontend SBOM artifact generation). - CodeQL static analysis on JavaScript/TypeScript code.
- Backend contract tests, smoke API checks, compatibility startup, frontend lint/build, and the repo test suite all pass on the verified local smoke path.
- Performance baseline and backup/restore drill checks are present as dedicated tests and pass in the suite.
- This repository is a showcase/proof app, not a production-certified CMS platform.
Run frontend lint directly:
cd frontend
npm run lintRun the frontend production build directly:
cd frontend
npm run buildOr run the repo-level quality script:
./scripts/quality-check.shOr from root:
make lintRun the API smoke suite (health, CRUD flow, and auth-mode write checks):
./scripts/run-smoke-tests.shRun the full backend regression suite from root:
make testRun the targeted performance baseline and DR restore drills directly:
bash tests/t5_01_performance_baseline.sh
bash tests/t5_02_backup_restore_drill.sh| Variable | Default | Description |
|---|---|---|
KUJO_CRUD_PORT |
4100 |
API port |
KUJO_CRUD_API_HOST |
127.0.0.1 |
API bind host; override explicitly for a broader listener |
KUJO_CRUD_DB_PATH |
data/kujo_crud_api.db |
SQLite DB path |
KUJO_CRUD_CORS_ORIGIN |
* |
Allowed CORS origin |
KUJO_CRUD_AUTH_TOKEN |
(empty) | If set, write routes require Authorization: Bearer <token> |
KUJO_CRUD_AUTH_STRATEGY |
static_bearer |
Write-route auth strategy: static_bearer or header_value |
KUJO_CRUD_AUTH_HEADER_NAME |
X-API-Key |
Header name used when KUJO_CRUD_AUTH_STRATEGY=header_value |
KUJO_CRUD_AUTH_HEADER_VALUE |
(empty) | Required header value when KUJO_CRUD_AUTH_STRATEGY=header_value |
KUJO_CRUD_MIN_AUTH_SECRET_LENGTH |
16 |
Minimum production auth secret length |
KUJO_CRUD_MAX_BODY_BYTES |
65536 |
Max JSON body size |
KUJO_CRUD_SECURITY_PROFILE |
demo |
Security mode: demo or production |
KUJO_CRUD_RATE_LIMIT_MAX_WRITES |
30 |
Max write requests per client key within one window |
KUJO_CRUD_RATE_LIMIT_WINDOW_SECONDS |
60 |
Window size (seconds) for write rate limiting |
KUJO_CRUD_TRUST_PROXY_HEADERS |
false |
If true, write rate limiting uses X-Forwarded-For; leave false unless a trusted proxy controls that header |
demois the default and prioritizes local developer convenience.productionenforces stricter startup validation.
When KUJO_CRUD_SECURITY_PROFILE=production, startup requires:
- If
KUJO_CRUD_AUTH_STRATEGY=static_bearer, thenKUJO_CRUD_AUTH_TOKENmust be set - If
KUJO_CRUD_AUTH_STRATEGY=header_value, thenKUJO_CRUD_AUTH_HEADER_VALUEmust be set - Configured auth secrets must be at least
KUJO_CRUD_MIN_AUTH_SECRET_LENGTHcharacters KUJO_CRUD_CORS_ORIGINto be an explicit origin (not*)
Write routes (POST, PUT, PATCH, DELETE) are rate-limited and return 429 with a Retry-After header when the limit is exceeded. By default, the limiter ignores client-supplied proxy headers so callers cannot rotate X-Forwarded-For to bypass local limits. Set KUJO_CRUD_TRUST_PROXY_HEADERS=true only behind a trusted reverse proxy that overwrites inbound forwarding headers.
- The API accepts optional inbound
X-Request-Idand propagates it to responses. - If no request ID is provided, the server generates one and includes it in
X-Request-Id. - Runtime logs are structured JSON lines with
event,request_id,method,path, and status/security metadata. - Security events (auth denied and write-rate-limit denied) are logged without sensitive header/token values.
| Method | Path | Description |
|---|---|---|
GET |
/ |
API metadata |
GET |
/health |
Health + record count |
GET |
/items |
List items (limit, offset, status, project_id, sort_by, sort_dir, q) |
POST |
/items |
Create item |
GET |
/items/:id |
Get one item |
PUT |
/items/:id |
Replace one item |
PATCH |
/items/:id |
Patch one item |
DELETE |
/items/:id |
Delete one item |
GET |
/projects |
List projects with related item counts |
POST |
/projects |
Create project |
GET |
/projects/:id |
Get one project |
GET |
/projects/:id/items |
List items for one project |
Write requests (POST, PUT, PATCH) must use Content-Type: application/json.
Creating a project with a duplicate name returns 409 with an error payload.
PUT, PATCH, and DELETE on /items/:id require If-Unmodified-Since set to the current item updated_at value. Missing headers return 428; stale values return 409 with conflict details.
{
"id": 1,
"title": "First Sample Item",
"content": "Use this record to test read and update flows.",
"status": "published",
"project_id": 1,
"project_name": "Documentation",
"created_at": "1777930859.0",
"updated_at": "1777930859.0"
}Allowed status values:
draftpublishedarchived
{
"id": 1,
"name": "Documentation",
"description": "API and developer documentation roadmap and examples.",
"items_count": 2,
"created_at": "1777930859.0",
"updated_at": "1777930859.0"
}curl -s http://127.0.0.1:4100/healthcurl -s -X POST http://127.0.0.1:4100/items \
-H "Content-Type: application/json" \
-d '{"title":"My item","content":"Hello world","status":"draft"}'curl -s 'http://127.0.0.1:4100/items?limit=20&offset=0'curl -s 'http://127.0.0.1:4100/items?sort_by=updated_at&sort_dir=desc&q=draft'curl -s 'http://127.0.0.1:4100/items?limit=20&sort_by=id&sort_dir=desc&cursor=id:120'Cursor mode requires sort_by=id and sort_dir=desc. Response payloads include next_cursor when another page is available.
curl -s 'http://127.0.0.1:4100/projects'curl -s 'http://127.0.0.1:4100/projects/1/items'curl -s -X PATCH http://127.0.0.1:4100/items/1 \
-H "Content-Type: application/json" \
-H "If-Unmodified-Since: 1777930859.0" \
-d '{"content":"Updated content"}'- Clients read an item and keep its
updated_attoken. - Update/delete calls send
If-Unmodified-Since: <updated_at-token>. - Server responses:
200when token matches current record state.409when token is stale (response includescurrent_updated_atandprovided_updated_at).428when the precondition header is omitted.
main.kujothin API bootstrap entrypointsrc/config.kujoenvironment and runtime configsrc/db.kujoSQLite connection, schema, and seed setupsrc/validation.kujorequest/auth/field validation helperssrc/handlers.kujoendpoint business logicsrc/server/runtime.kujoroute registration and server runtimefrontend/Next.js playground that calls the APIdata/default runtime SQLite location
The root main.kujo file intentionally remains as the public bootstrap. Runtime behavior lives under src/; generated SQLite and smoke-test artifacts are ignored under data/ and .tmp/.
- OpenAPI contract:
docs/openapi.yaml - Canonical copyable endpoint examples:
docs/api-examples.md - Next-session hardening plan:
docs/next-session-hardening-plan.md
Use the contract as the source of truth for integration clients. The validation script tests/t4_03_contract_validation.sh verifies that documented endpoints and live API behavior stay aligned.
- Canonical examples live in
README.mdanddocs/api-examples.md. - Tests under
tests/are executable contracts, not style examples to shorten aggressively. docs/openapi.yamlis generated-style API contract material; update it for endpoint behavior changes, but exclude it from readability-only sweeps.- Exclude generated/bulk paths from broad searches unless they are the target:
frontend/package-lock.json,frontend/node_modules/,frontend/.next/,.tmp/, anddata/*.db*.
For the full, prioritized roadmap (security, refactors, testing, and DX), use:
docs/implementation-checklist.md
The checklist is designed so an AI agent or developer can complete one item at a time, mark it done, update this README when behavior changes, and then continue to the next task.
Good first extensions:
- Add a second resource (
projects,users, etc.) - Add relation tables and joins
- Add test coverage for each endpoint
- Add CLI commands for seed/reset/export