A production-ready API template built with Cloudflare Workers, Hono, and Drizzle ORM β designed for building fast, type-safe APIs on the edge with minimal setup.
This is ideal for developers who want to skip boilerplate setup and start building APIs for dashboards, storefronts, or internal tools β using the same stack that powers modern edge apps.
This boilerplate includes:
- Local Docker integration for Postgres
- Built-in OpenAPI generation and Swagger UI
- JWT authentication (RSA)
- CSRF protection (HMAC)
- CORS protection
- A fully working example schema (
Items,Tags,Statuses) - Seed data and local migration setup
- Secure environment variable handling
This API provides:
- A data interface for the Storefront, fetching product information.
- An Admin Panel (Product Management Portal) backend for creating, updating, and deleting products, tags, and statuses.
It's designed for projects where you want a typed, OpenAPI-compliant, and serverless backend that can scale to production.
-
π§± Prebuilt CRUD endpoints (items, tags, statuses)
-
π§© Typed OpenAPI schemas with @hono/zod-openapi
-
π JWT, CSRF and CORS middleware
-
π Local Postgres via Docker
-
π§ͺ Vitest setup with Workers runtime
-
π§° Utility scripts for seamless onboarding
| Component | Purpose |
|---|---|
| Cloudflare Workers | Global edge hosting environment |
| Hono | Lightweight web framework optimized for Workers |
| Drizzle ORM | Type-safe SQL builder and migration management |
| Neon Serverless Postgres | Cloud Postgres with free-tier support |
| Zod | Runtime schema validation |
| Swagger UI | Interactive API documentation |
---
config:
look: classic
layout: elk
theme: 'base'
themeVariables:
primaryColor: '#d0ec99ff'
primaryTextColor: '#000000'
primaryBorderColor: '#004806ff'
secondaryColor: '#e1e1e1ff'
lineColor: '#8b8b8bff'
---
flowchart TD
ClientBrowser["π Browser Client<br>(JavaScript Frontend)"]
ClientServer["π₯οΈ Server Client<br>(Trusted API Consumer)"]
subgraph Worker["Cloudflare Worker"]
Worker_Middleware["π Middleware Layer<br>JWT Auth Β· CSRF Β· CORS Β· API Key"]
Worker_API["βοΈ API Routes<br>@hono/zod-openapi"]
Worker_DrizzleORM["π§± Drizzle ORM<br>Schema Β· Queries Β· Migrations"]
Worker_SwaggerUI["π Swagger UI<br>/doc Β· /doc/ui"]
end
DB[("ποΈ Neon Serverless Postgres Database")]
%% Data Flow
ClientBrowser -->|HTTPS JSON Requests| Worker
ClientServer -->|"HTTPS JSON Requests \(x-api-key\)"| Worker
Worker_Middleware --> Worker_API
Worker_API -->|Type-safe Queries| Worker_DrizzleORM
Worker_DrizzleORM -->|SQL Queries| DB
%% Styling
classDef local fill:#f2f2f2,stroke:#666,stroke-width:1px;
classDef cloud fill:#e3f2fd,stroke:#2196f3,stroke-width:1px;
classDef worker fill:#FFE0B2,stroke:#FF6D00,stroke-width:1px;
class ClientBrowser local
class ClientServer cloud
class Worker worker;
class DB cloud;
| Type | URL |
|---|---|
| OpenAPI Spec | [base_url]/doc |
| Swagger UI | [base_url]/doc/ui |
All routes automatically register with the OpenAPI spec using @hono/zod-openapi.
git clone https://github.com/yourusername/cf-workers-api-starter.git
cd cf-workers-api-starternpm installnpm run init- Sets the project name
- Creates a
.dev.varsfile in the root with the variables shown above.
npm run docker:up # Start Docker + Postgres
npm run db:migrate # Apply migrations
npm run dev # Start local Cloudflare workernpm run docker:downAll available scripts are defined in your package.json.
| Command | Description |
|---|---|
npm run docker:up |
Start Postgres container |
npm run dev |
Start Cloudflare Worker locally |
npm run docker:down |
Stop Postgres container |
| Command | Description |
|---|---|
npm run db:generate |
Generate Drizzle migrations |
npm run db:migrate |
Apply migrations |
npm run custom-mig-step |
Create a custom migration step |
| Command | Description |
|---|---|
npm run deploy |
Deploy Worker to Cloudflare |
npm run cf:types |
Generate Worker type definitions |
npm run cf:size |
Check bundle size before deploy |
| Command | Description |
|---|---|
npm run lint |
Check for linting issues |
npm run lint:fix |
Fix linting issues automatically |
npm run format |
Format all files |
npm run typecheck |
Run TypeScript checks |
| Command | Description |
|---|---|
npm run test |
Run Vitest tests |
npm run github-actions:local-test |
Run GitHub Actions locally (act) |
| Command | Description |
|---|---|
npm run init |
Initialize project configuration |
npm run gen:dir_tree |
Generate a directory tree view |
| Command | Description |
|---|---|
npm run gen:rsa_json |
Generate JWT RSA keys for all environments (dev/testing/prod) |
npm run gen:rsa_keys <env> |
Generate RSA key pair for JWT (development, production, etc.) |
npm run gen:hmac_key <env> |
Generate HMAC key for CSRF (development, production, etc.) |
npm run gen:api_key <env> |
Generate API key for Server Consumers (development, production, etc.) |
Examples:
npm run gen:rsa_keys development
npm run gen:hmac_key production
npm run gen:rsa_json# 1) Setup project name, and dev environment variables
npm run init
# 2) Start Docker + local Postgres
npm run docker:up
# 3) Run database migrations
npm run db:migrate
# 4) Start local Cloudflare Worker
npm run devOnce running, open https://127.0.0.1:9080/doc/ui to access Swagger UI.
See deployment documentation here
-
Configure your production environment in
wrangler.toml:name = "your-project-name" # change this to whatever you'd like the worker to be called in cloudflare. Defaults to the name you gave with `npm run init` main = "src/index.ts" compatibility_date = "2024-04-05" # don't change this unless you know what you're doing account_id = "your_account_id_here" # replace with your actual account ID # Environment Variables for Production [env.production] main = "src/index.ts" [env.production.vars] NODE_ENV = "production" JWT_RSA_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----..."
-
Deploy to Cloudflare:
npm run deploy
-
Use Neon for your database:
- Copy the connection string from Neon.
- Set
DATABASE_URLin your Cloudflare secrets.
Not safe to show the public. (stored for development in .dev.vars, for production in the CfWorker Secrets)
# Database connection
DATABASE_URL=postgres://user:password@localhost:5432/localdb
# JWT signing
JWT_RSA_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----..."
# CSRF protection
HMAC_KEY="your-hmac-key"
# CSRF protection
API_KEY="your-api-key"You can upload secrets from your local terminal by executing the command: npx wrangler secret put [secret_name] [secret_value]
for example
npx wrangler secret put DATABASE_URL "postgres://user:password@db.myserver.net:5432/mydb"
Deployed with the worker, safe to expose. (stored for development and production in wrangler.toml. Development has [env.development.vars] and production has [env.production.vars])
[env.production.vars]
NODE_ENV = "production"
JWT_RSA_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----..."β Rule of thumb for local development: Secrets β
.dev.varsNon-secrets βwrangler.toml
src/
ββ api/ # Hono routes
β ββ middleware/ # CSRF, JWT, CORS, DB connectors
β ββ routes/ # Resource endpoints
ββ db/ # Drizzle ORM queries & schema
ββ lib/ # Utilities (security, errors, etc.)
ββ schema/ # Zod & OpenAPI schemas
scripts/ # Dev scripts (docker, keygen, etc.)
docker/ # Docker Compose setup
docs/ # Further documents to help you use this template
- Ensure Docker is installed and running before using local Postgres.
- Neon can replace Docker for remote Postgres hosting.
- Use
.dev.varsfor secrets and local environment values. - The included
check-and-start-docker.tsscript ensures smooth onboarding on all platforms (macOS, Windows, Linux, WSL).
This project is open-source software licensed under the MIT License.
Feel free to use it as the foundation for your own private, commercial, or client projects. For more details, see the LICENSE file.
Steven Santos π§ programmerstevie@gmail.com