Skip to Content
deploymentCloudflare Workers

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-app

Select cloudflare-workers when prompted.

Setup

1. Create Project

Initialize your project:

::: code-group

npm create hono@latest my-app cd my-app npm install
yarn create hono my-app cd my-app yarn
pnpm create hono@latest my-app cd my-app pnpm install
bun 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 app

3. Run Locally

Start the development server with Wrangler:

::: code-group

npm run dev
yarn dev
pnpm dev
bun run dev

:::

Access http://localhost:8787 in your browser.

4. Deploy

Deploy to Cloudflare:

::: code-group

npm run deploy
yarn deploy
pnpm run deploy
bun 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 = 8787

Or via CLI:

npx wrangler dev --port 3000

See Wrangler Configuration  for more options.

Environment Variables

For local development, create .dev.vars:

API_KEY=your-secret-key DATABASE_URL=https://example.com/db

Access 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.toml

Files 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-workers

Configure 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 = true

Push to GitHub and your app will deploy automatically!

TypeScript Types

Install Cloudflare Workers types:

::: code-group

npm install -D @cloudflare/workers-types
yarn add -D @cloudflare/workers-types
pnpm add -D @cloudflare/workers-types
bun 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