Hono is a small, simple, and ultrafast web framework that works on any JavaScript runtime. It’s designed specifically for edge computing but works anywhere. This comprehensive guide covers everything you need to know.
What is Hono?
Hono is a web framework built for the edge with a unique “Write Once, Run Anywhere” philosophy.
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => c.text('Hello Hono!'));
export default app;
Key Features
- Multi-runtime - Cloudflare Workers, Deno, Bun, Node.js
- Ultrafast - Minimal overhead, maximum performance
-
- Middleware - Rich ecosystem of middleware
-
- TypeScript - First-class TypeScript support
-
- Tiny - Only ~12KB
-
- RESTful - Full HTTP router included
Basic Usage
Cloudflare Workers
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => c.text('Hello Cloudflare!'));
app.get('/api/users', (c) => {
return c.json({
users: [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]
});
});
export default app;
Deno
import { Hono } from 'https://deno.land/x/hono/mod.ts';
const app = new Hono();
app.get('/', (c) => c.text('Hello Deno!'));
Deno.serve(app.fetch);
Bun
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => c.text('Hello Bun!'));
export default {
port: 3000,
fetch: app.fetch
};
Node.js
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
const app = new Hono();
app.get('/', (c) => c.text('Hello Node!'));
serve(app);
Routing
Basic Routes
const app = new Hono();
// HTTP methods
app.get('/users', (c) => c.text('GET users'));
app.post('/users', (c) => c.text('POST users'));
app.put('/users/:id', (c) => c.text('PUT user'));
app.delete('/users/:id', (c) => c.text('DELETE user'));
// All methods
app.all('/api', (c) => c.text('Any method'));
Route Parameters
app.get('/users/:id', (c) => {
const id = c.req.param('id');
return c.text(`User ID: ${id}`);
});
// Multiple params
app.get('/posts/:postId/comments/:commentId', (c) => {
const { postId, commentId } = c.req.param();
return c.json({ postId, commentId });
});
Query Parameters
app.get('/search', (c) => {
const query = c.req.query('q');
const page = c.req.query('page') || '1';
const limit = c.req.query('limit') || '10';
return c.json({ query, page, limit });
});
// Get all queries as object
app.get('/filter', (c) => {
const queries = c.req.query();
return c.json(queries);
});
Nested Routes
const app = new Hono();
const api = app.route('/api');
const v1 = api.route('/v1');
v1.get('/users', (c) => c.json({ users: [] }));
v1.get('/posts', (c) => c.json({ posts: [] }));
Context Object
Request Information
app.get('/info', (c) => {
// Path and method
c.req.path; // '/info'
c.req.method; // 'GET'
// URL
c.req.url; // 'http://localhost:3000/info?q=hello'
c.req.query(); // { q: 'hello' }
c.req.param(); // { id: '123' }
// Headers
c.req.header('Authorization');
c.req.headers(); // All headers
// Body
c.req.parseBody(); // Parse JSON/form data
c.req.text(); // Raw text
c.req.json(); // Parse JSON
// Raw Request (runtime-specific)
c.req.raw; // Request object
return c.text('Hello');
});
Response Methods
app.get('/response', (c) => {
// Text response
return c.text('Plain text');
// JSON response
return c.json({ message: 'JSON' });
// HTML response
return c.html('<h1>HTML</h1>');
// Custom status
return c.text('Created', 201);
// or
return c.json({ error: 'Not found' }, 404);
// Headers
return c.text('With header', 200, {
'X-Custom': 'value'
});
// Redirect
return c.redirect('/other');
// Cookie
c.cookie('token', 'abc123');
return c.text('Cookie set');
});
Middleware
Built-in Middleware
import {
cors,
logger,
poweredBy,
timeout,
compress
} from 'hono/middleware';
const app = new Hono();
// CORS
app.use('/*', cors());
// Add logger
app.use('/*', logger());
// Add powered by header
app.use('/*', poweredBy());
// Response compression
app.use('/*', compress());
// Timeout (in milliseconds)
app.use('/*', timeout(5000));
Custom Middleware
// Method 1: Using app.use()
app.use('/*', async (c, next) => {
const start = Date.now();
await next();
const end = Date.now();
c.res.headers.set('X-Response-Time', `${end - start}ms`);
});
// Method 2: Using function
const timing = () => async (c, next) => {
const start = Date.now();
await next();
c.res.headers.set('X-Process-Time', `${Date.now() - start}ms`);
};
app.use('/*', timing());
Third-party Middleware
import { jwt } from 'hono/jwt';
import { rateLimit } from 'hono/rate-limit';
import { secureHeaders } from 'hono/secure-headers';
// JWT Authentication
app.use('/api/*', jwt({
secret: 'my-secret-key'
}));
// Rate Limiting
app.use('/*', rateLimit({
limit: 100,
window: 60000
}));
// Security Headers
app.use('/*', secureHeaders());
Working with JSON
Parsing Request Body
app.post('/users', async (c) => {
const body = await c.req.json();
// With type safety
// const body = await c.req.json<{ name: string; email: string }>();
return c.json({ received: body }, 201);
});
Returning Responses
app.get('/data', (c) => {
const data = {
users: [
{ id: 1, name: 'John' }
],
meta: {
total: 1,
page: 1
}
};
return c.json(data);
});
Error Handling
Handling Errors
app.get('/error', (c) => {
throw new Error('Something went wrong');
});
// Global error handler
app.onError((err, c) => {
console.error(err);
return c.json({
error: err.message
}, 500);
});
// 404 handler
app.notFound((c) => {
return c.json({
error: 'Not Found'
}, 404);
});
Building an API
Complete Example
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
const app = new Hono();
// Middleware
app.use('/*', cors());
app.use('/*', logger());
// In-memory database
const db = {
users: [
{ id: '1', name: 'John', email: '[email protected]' },
{ id: '2', name: 'Jane', email: '[email protected]' }
]
};
// Routes
app.get('/', (c) => c.json({
message: 'Welcome to the API',
version: '1.0'
}));
app.get('/users', (c) => {
const { limit, offset } = c.req.query();
const limitNum = parseInt(limit) || 10;
const offsetNum = parseInt(offset) || 0;
return c.json({
users: db.users.slice(offsetNum, offsetNum + limitNum),
total: db.users.length
});
});
app.get('/users/:id', (c) => {
const id = c.req.param('id');
const user = db.users.find(u => u.id === id);
if (!user) {
return c.json({ error: 'User not found' }, 404);
}
return c.json(user);
});
app.post('/users', async (c) => {
const body = await c.req.json();
if (!body.name || !body.email) {
return c.json({ error: 'Name and email required' }, 400);
}
const newUser = {
id: String(db.users.length + 1),
name: body.name,
email: body.email
};
db.users.push(newUser);
return c.json(newUser, 201);
});
app.put('/users/:id', async (c) => {
const id = c.req.param('id');
const body = await c.req.json();
const index = db.users.findIndex(u => u.id === id);
if (index === -1) {
return c.json({ error: 'User not found' }, 404);
}
db.users[index] = { ...db.users[index], ...body };
return c.json(db.users[index]);
});
app.delete('/users/:id', (c) => {
const id = c.req.param('id');
const index = db.users.findIndex(u => u.id === id);
if (index === -1) {
return c.json({ error: 'User not found' }, 404);
}
db.users.splice(index, 1);
return c.text('', 204);
});
export default app;
With TypeScript
Type Safety
import { Hono } from 'hono';
import type { Context, Next } from 'hono';
type Env = {
DB: D1Database;
};
type Variables = {
userId: string;
};
const app = new Hono<{
Variables: Variables;
Bindings: Env;
}>();
// Access variables with type safety
app.get('/api', (c) => {
const userId = c.get('userId');
return c.json({ userId });
});
// Type-safe request parsing
app.post('/users', async (c) => {
const body = await c.req.json<{
name: string;
email: string;
}>();
return c.json({ name: body.name });
});
Integration Examples
With Cloudflare Workers
import { Hono } from 'hono';
import { cors } from 'hono/cors';
const app = new Hono();
app.use('/*', cors());
app.get('/kv/:key', async (c) => {
const key = c.req.param('key');
const value = await c.env.KV.get(key);
return c.json({ key, value });
});
app.post('/kv/:key', async (c) => {
const key = c.req.param('key');
const { value } = await c.req.json();
await c.env.KV.put(key, value);
return c.text('Created', 201);
});
export default app;
With Deno Deploy
import { Hono } from 'https://deno.land/x/hono/mod.ts';
import { cors } from 'https://deno.land/x/hono/middleware.ts';
const app = new Hono();
app.use('/*', cors());
app.get('/deno', (c) => c.text('Hello Deno Deploy!'));
Deno.serve(app.fetch);
With React Frontend
// api.js - Hono backend
import { Hono } from 'hono';
const app = new Hono();
app.post('/api/contact', async (c) => {
const body = await c.req.json();
// Process form submission
return c.json({ success: true });
});
export default app;
// React frontend
function ContactForm() {
const [submitted, setSubmitted] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
setSubmitted(true);
};
return submitted
? <p>Thank you!</p>
: <form onSubmit={handleSubmit}>...</form>;
}
Performance
Hono is designed for maximum performance on edge runtimes.
| Runtime | Requests/sec |
|---|---|
| Cloudflare Workers | ~180K |
| Bun | ~200K |
| Deno | ~150K |
| Node.js | ~120K |
External Resources
Conclusion
Hono is an excellent choice for building fast web applications, especially for edge deployment. Key points:
- Works on any JavaScript runtime (Cloudflare, Deno, Bun, Node)
- Simple and intuitive API
- Rich middleware ecosystem
- Excellent TypeScript support
- Tiny footprint with minimal overhead
For edge-first applications or multi-runtime projects, Hono provides a consistent development experience.
Comments