n4nAI

Deploying a Cloudflare Worker chatbot with Hono and n4n.ai

Hands-on tutorial for building a cloudflare workers hono chatbot with Hono and an OpenAI-compatible LLM endpoint, then deploying to the edge with Wrangler.

n4n Team2 min read424 words

Audio narration

Coming soon — every post will get a voice note here.

Building a cloudflare workers hono chatbot is the fastest way to put a serverless LLM front end on the edge. This tutorial scaffolds a Hono app, wires it to an OpenAI-compatible inference gateway, and ships it to Cloudflare with Wrangler.

Prerequisites

  • Node.js 20+ (for local dev)
  • Wrangler 3.78+ (npm i -g wrangler)
  • TypeScript familiarity
  • An API key from n4n.ai (or any OpenAI-compatible gateway)
  • A Cloudflare account

If you haven’t used Wrangler before, run wrangler login first.

Scaffold the project

mkdir hono-chatbot && cd hono-chatbot
npm init -y
npm install hono
npm install -D wrangler typescript @cloudflare/workers-types

Create a tsconfig.json tuned for Workers:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "types": ["@cloudflare/workers-types"],
    "lib": ["ES2022"],
    "strict": true
  },
  "include": ["src"]
}

Configure Wrangler

Create wrangler.toml. Keep secrets out of version control.

name = "hono-chatbot"
main = "src/index.ts"
compatibility_date = "2024-09-23"

Set the API key as a secret:

wrangler secret put N4N_API_KEY
# paste your key when prompted

Build the Hono API

Create src/index.ts. The cloudflare workers hono chatbot needs a POST endpoint that forwards messages. We target n4n.ai’s OpenAI-compatible endpoint, which addresses 240+ models and provides automatic fallback when a provider is degraded.

import { Hono } from 'hono'

type Bindings = {
  N4N_API_KEY: string
}

const app = new Hono<{ Bindings: Bindings }>()

app.post('/api/chat', async (c) => {
  const body = await c.req.json<{ messages: { role: string; content: string }[] }>()
  if (!body.messages || !Array.isArray(body.messages)) {
    return c.json({ error: 'messages array required' }, 400)
  }

  const upstream = await fetch('https://api.n4n.ai/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${c.env.N4N_API_KEY}`,
    },
    body: JSON.stringify({
      model: 'anthropic/claude-3.5-sonnet',
      messages: body.messages,
      temperature: 0.7,
    }),
  })

  if (!upstream.ok) {
    const text = await upstream.text()
    return c.json({ error: 'upstream failed', detail: text }, 502)
  }

  const data = await upstream.json()
  return c.json(data)
})

The Bindings type gives you typed access to c.env.N4N_API_KEY. Choose any model ID the gateway supports; the request shape is plain OpenAI.

Serve a minimal chat UI

A chatbot needs a surface. Add a GET route that returns a single-file HTML page with a textarea and fetch call.

app.get('/', (c) => {
  return c.html(`<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Worker Chatbot</title>
<style>body{font:16px sans-serif;max-width:640px;margin:2rem auto}#log{white-space:pre-wrap;border:1px solid #ccc;padding:1rem;min-height:200px}button{margin-top:.5rem}</style>
</head>
<body>
<h1>Cloudflare Workers Hono Chatbot</h1>
<div id="log"></div>
<input id="msg" placeholder="Type a message" style="width:80%" />
<button onclick="send()">Send</button>
<script>
let history = [];
async function send() {
  const input = document.getElementById('msg');
  const text = input.value.trim();
  if (!text) return;
  history.push({role:'user', content:text});
  document.getElementById('log').textContent += '\\nYou: ' + text + '\\n';
  input.value = '';
  const res = await fetch('/api/chat', {
    method:'POST',
    headers:{'Content-Type':'application/json'},
    body: JSON.stringify({messages: history})
  });
  const data = await res.json();
  const reply = data.choices?.[0]?.message?.content ?? 'No response';
  history.push({role:'assistant', content:reply});
  document.getElementById('log').textContent += 'Bot: ' + reply + '\\n';
}
</script>
</body></html>`)
})

export default app

Run locally

Start the dev server:

wrangler dev

In another shell, test the API directly:

curl -X POST http://localhost:8787/api/chat \
  -H 'Content-Type: application/json' \
  -d '{"messages":[{"role":"user","content":"Say hello in one word"}]}'

Expected output (truncated):

{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "Hello"
      },
      "finish_reason": "stop"
    }
  ]
}

Open http://localhost:8787/ in a browser, type a message, and see the reply rendered. The cloudflare workers hono chatbot now works end-to-end on your machine.

Deploy

Ship it:

wrangler deploy

Wrangler prints the *.workers.dev subdomain. Visit it; the cloudflare workers hono chatbot is live on the edge.

Operational notes

The pattern above blocks on the upstream HTTP call. For production, add streaming to avoid timeout on long generations:

app.post('/api/chat/stream', async (c) => {
  const body = await c.req.json<{ messages: any[] }>()
  const upstream = await fetch('https://api.n4n.ai/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${c.env.N4N_API_KEY}`,
    },
    body: JSON.stringify({ model: 'anthropic/claude-3.5-sonnet', messages: body.messages, stream: true }),
  })
  return new Response(upstream.body, {
    headers: { 'Content-Type': 'text/event-stream' },
  })
})

Wire the frontend to read the SSE stream. That cuts time-to-first-token from seconds to sub-second.

Cache static HTML at the edge with the Cache API if you serve many users. The chatbot route itself should never be cached.

Honor client routing directives: if you pass model from the browser, validate it server-side to avoid abuse. The gateway forwards provider cache-control hints, so repeated identical prompts may hit provider prompt caches.

Why Hono

Hono’s tiny footprint (≈15 KB) and built-in helpers (c.json, c.html) fit the Cloudflare Workers CPU limit. Express would blow the bundle. The cloudflare workers hono chatbot stack compiles to a single ES module with no native deps.

Troubleshooting

  • 500 on deploy: check wrangler secret list to confirm N4N_API_KEY exists in the target environment.
  • CORS: if you later split frontend/backend, add app.use('/api/*', cors()) from hono/cors.
  • Model not found: the gateway returns 400 with a list of available model IDs; adjust the model field.

That’s the full path from npm init to a running edge chatbot.

Tagscloudflare-workershonochatbotn4n-ai

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All cloudflare workers llm integration posts →