Last Updated: 3/9/2026
Cloudflare Workers
Cloudflare Workers is a JavaScript edge runtime running on Cloudflare’s global CDN with 300+ data centers worldwide. It provides ultra-fast cold starts and integrates with Cloudflare’s storage and compute services.
Quick Start
Create a new Hono app for Cloudflare Workers:
npm create hono@latest my-appSelect cloudflare-workers when prompted.
Setup
1. Create Project
Initialize your project:
::: code-group
npm create hono@latest my-app
cd my-app
npm installyarn create hono my-app
cd my-app
yarnpnpm create hono@latest my-app
cd my-app
pnpm installbun create hono@latest my-app
cd my-app
bun install:::
2. Write Your Application
Edit src/index.ts:
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Cloudflare Workers!')
})
app.get('/api/hello', (c) => {
return c.json({
message: 'Hello from the edge!',
location: c.req.header('cf-ray') // Cloudflare-specific header
})
})
export default app3. Run Locally
Start the development server with Wrangler:
::: code-group
npm run devyarn devpnpm devbun run dev:::
Access http://localhost:8787 in your browser.
4. Deploy
Deploy to Cloudflare:
::: code-group
npm run deployyarn deploypnpm run deploybun run deploy:::
Your app will be live at https://my-app.<your-subdomain>.workers.dev.
Configuration
Change Port Number
Update wrangler.toml or use CLI options:
[dev]
port = 8787Or via CLI:
npx wrangler dev --port 3000See Wrangler Configuration for more options.
Environment Variables
For local development, create .dev.vars:
API_KEY=your-secret-key
DATABASE_URL=https://example.com/dbAccess in your code:
type Bindings = {
API_KEY: string
DATABASE_URL: string
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/api/data', async (c) => {
const apiKey = c.env.API_KEY
const dbUrl = c.env.DATABASE_URL
// Use the variables
return c.json({ success: true })
})Important: Set production environment variables in the Cloudflare dashboard under Settings → Variables before deploying.
Bindings
Cloudflare Workers can bind to various services. Define types for type safety:
type Bindings = {
// KV Namespace
MY_KV: KVNamespace
// D1 Database
DB: D1Database
// R2 Bucket
MY_BUCKET: R2Bucket
// Environment variables
API_SECRET: string
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/data/:key', async (c) => {
const key = c.req.param('key')
const value = await c.env.MY_KV.get(key)
return c.json({ key, value })
})
app.put('/upload/:key', async (c) => {
const key = c.req.param('key')
await c.env.MY_BUCKET.put(key, c.req.body)
return c.text(`Uploaded ${key} successfully`)
})Using Middleware with Bindings
For middleware that needs environment variables:
import { basicAuth } from 'hono/basic-auth'
type Bindings = {
USERNAME: string
PASSWORD: string
}
const app = new Hono<{ Bindings: Bindings }>()
app.use('/admin/*', async (c, next) => {
const auth = basicAuth({
username: c.env.USERNAME,
password: c.env.PASSWORD,
})
return auth(c, next)
})
app.get('/admin/dashboard', (c) => {
return c.text('Admin Dashboard')
})Static Files
Serve static files using Static Assets :
Configure wrangler.toml
assets = { directory = "public" }Directory Structure
.
├── package.json
├── public
│ ├── favicon.ico
│ ├── styles.css
│ └── static
│ └── image.png
├── src
│ └── index.ts
└── wrangler.tomlFiles in public/ are served automatically:
./public/favicon.ico→/favicon.ico./public/static/image.png→/static/image.png
Multiple Event Handlers
Integrate Hono with other Workers event handlers (e.g., scheduled, queue):
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello!'))
export default {
// HTTP requests
fetch: app.fetch,
// Cron triggers
scheduled: async (event, env, ctx) => {
console.log('Cron job triggered:', event.cron)
// Run scheduled tasks
},
// Queue consumers
queue: async (batch, env, ctx) => {
for (const message of batch.messages) {
console.log('Processing message:', message.body)
}
},
}Testing
Use @cloudflare/vitest-pool-workers for accurate testing:
Install
npm install -D vitest @cloudflare/vitest-pool-workersConfigure vitest.config.ts
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config'
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.toml' },
},
},
},
})Write Tests
import { describe, it, expect } from 'vitest'
import app from './index'
describe('Hono app', () => {
it('returns 200 OK', async () => {
const res = await app.request('http://localhost/')
expect(res.status).toBe(200)
expect(await res.text()).toBe('Hello Cloudflare Workers!')
})
it('returns JSON', async () => {
const res = await app.request('http://localhost/api/hello')
expect(res.status).toBe(200)
const data = await res.json()
expect(data.message).toBe('Hello from the edge!')
})
})See Vitest integration for details.
Deploy from GitHub Actions
1. Get Cloudflare API Token
Create a token at User API Tokens with Edit Cloudflare Workers permissions.
2. Add Secret to GitHub
Go to Settings → Secrets and variables → Actions → New repository secret:
- Name:
CLOUDFLARE_API_TOKEN - Value: Your API token
3. Create Workflow
Create .github/workflows/deploy.yml:
name: Deploy to Cloudflare Workers
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy
steps:
- uses: actions/checkout@v4
- name: Deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}4. Update wrangler.toml
Add after compatibility_date:
main = "src/index.ts"
minify = truePush to GitHub and your app will deploy automatically!
TypeScript Types
Install Cloudflare Workers types:
::: code-group
npm install -D @cloudflare/workers-typesyarn add -D @cloudflare/workers-typespnpm add -D @cloudflare/workers-typesbun add --dev @cloudflare/workers-types:::
Add to tsconfig.json:
{
"compilerOptions": {
"types": ["@cloudflare/workers-types"]
}
}Best Practices
Use Environment Variables for Secrets
Never hardcode secrets:
// ❌ Don't do this
const API_KEY = 'secret-key-123'
// ✅ Do this
type Bindings = {
API_KEY: string
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/api', (c) => {
const apiKey = c.env.API_KEY
// ...
})Handle Errors Gracefully
app.onError((err, c) => {
console.error(`Error: ${err.message}`)
return c.json(
{ error: 'Internal Server Error' },
500
)
})Optimize Cold Starts
Keep your worker small:
- Use tree-shaking
- Lazy-load heavy dependencies
- Minimize imports
What’s Next
- Cloudflare Pages - Deploy static sites with edge functions
- Bindings Documentation - Learn about KV, D1, R2
- Best Practices - Hono app architecture patterns