Public API
The Cloudcore Public API is a separate, read-only Cloudflare Worker that serves published content from your CMS database. It is designed to be the only internet-facing endpoint while the CMS admin stays behind Cloudflare Access or a VPN.
Why a separate API?
The CMS has authentication, user management, media uploads, and write operations. By splitting the public-facing read API into its own worker, you can:
- Lock down the CMS behind Cloudflare Access (zero trust) or restrict it to a private network
- Expose only reads — this API has zero write operations, zero auth endpoints, zero user data
- Cache aggressively — read-only responses with configurable TTL for CDN edge caching
- Rate limit independently — different limits for public traffic vs admin usage
- Deploy and update separately — update the CMS without touching the public API, and vice versa
Architecture
Internet -> cloudcore-api (read-only) -> D1/R2 <- cloudcore-cms (locked down)
api.example.com cms.example.com (CF Access) Both workers share the same D1 database and R2 bucket. The CMS writes content, the Public API reads it. They deploy as independent workers so you can restrict access to each independently.
Security guarantees
- Zero write operations — no INSERT, UPDATE, or DELETE anywhere in the codebase
- Zero auth/user data — never reads users, sessions, passwords, audit logs, or settings
- Zero admin endpoints — no login, no setup, no management
- Method enforcement — rejects all POST/PUT/PATCH/DELETE at the middleware level
- Rate limited — 120 requests/minute per IP
- CORS configurable — restrict to your frontend domains
- Single dependency — only Hono, zero npm vulnerabilities
Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET | / | Health check and endpoint list |
GET | /content | List published content. Query: ?type=page&limit=20&offset=0 |
GET | /content/:type/:slug | Get published content by type and slug |
GET | /categories | List all categories |
GET | /tags | List all tags |
GET | /media/:id | Serve a media file |
All other HTTP methods return 405 Method Not Allowed.
Deploy
cd packages/api
npm install
# Update wrangler.toml with your D1 database_id (same as the CMS)
npx wrangler deploy
# Live at https://cloudcore-api.your-subdomain.workers.dev Configuration
| Variable | Required | Description |
|---|---|---|
ALLOWED_ORIGINS | Recommended | Comma-separated allowed origins for CORS. Leave empty to allow all (dev only). |
CACHE_TTL | Optional | Cache TTL in seconds for content responses (default: 60, max: 86400). |
Connect your frontend
Point your frontend at the Public API instead of the CMS directly:
# React (Vite)
VITE_CMS_URL=https://api.yourdomain.com
# Next.js
NEXT_PUBLIC_CMS_URL=https://api.yourdomain.com
# Astro
PUBLIC_CMS_URL=https://api.yourdomain.com Alternative: CMS built-in public routes
If you prefer not to deploy a separate worker, the CMS itself includes public read-only routes at /api/v1/public/*. These work the same way but share the worker with the admin endpoints. The separate Public API is recommended for production to reduce attack surface.