Hono is an ultrafast web framework that runs on any JavaScript runtime — Node.js, Bun, Deno, and Cloudflare Workers. With a tiny footprint (14KB), Express-like API, and built-in TypeScript support, it is becoming the framework of choice for modern JavaScript APIs. This guide covers deploying Hono across different runtimes on your VPS.
Project Setup
# Create with Hono CLI
npm create hono@latest my-api
cd my-api
# Or manually
mkdir hono-api && cd hono-api
npm init -y
npm install hono
npm install -D typescript @types/node tsx
Application Code
// src/index.ts
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { compress } from "hono/compress";
import { prettyJSON } from "hono/pretty-json";
import { HTTPException } from "hono/http-exception";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
const app = new Hono();
// Middleware
app.use("*", logger());
app.use("*", cors());
app.use("*", compress());
app.use("*", prettyJSON());
// Error handling
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status);
}
console.error(err);
return c.json({ error: "Internal server error" }, 500);
});
// Routes
app.get("/health", (c) => c.json({ status: "healthy" }));
// Validated routes
const createUserSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
});
app.get("/api/users", async (c) => {
const page = parseInt(c.req.query("page") || "1");
const limit = parseInt(c.req.query("limit") || "20");
// Fetch from database...
return c.json({ data: [], page, limit });
});
app.post("/api/users", zValidator("json", createUserSchema), async (c) => {
const data = c.req.valid("json");
// Insert into database...
return c.json({ id: 1, ...data }, 201);
});
app.get("/api/users/:id", async (c) => {
const id = c.req.param("id");
return c.json({ id, name: "Alice", email: "alice@example.com" });
});
export default app;
Running on Node.js
// src/node.ts
import { serve } from "@hono/node-server";
import app from "./index";
const port = parseInt(process.env.PORT || "3000");
console.log(`Server running on port ${port}`);
serve({ fetch: app.fetch, port });
# package.json scripts
{
"scripts": {
"dev": "tsx watch src/node.ts",
"build": "tsc",
"start": "node dist/node.js"
}
}
Running on Bun
// src/bun.ts
import app from "./index";
export default {
port: parseInt(process.env.PORT || "3000"),
fetch: app.fetch,
};
// Run with: bun run src/bun.ts
Running on Deno
// src/deno.ts
import { Hono } from "https://deno.land/x/hono/mod.ts";
import app from "./index.ts";
Deno.serve({ port: 3000 }, app.fetch);
// Run with: deno run --allow-net --allow-env src/deno.ts
Cloudflare Workers
// src/index.ts — works directly as a Worker
// wrangler.toml
// name = "my-api"
// main = "src/index.ts"
// compatibility_date = "2024-01-01"
// Deploy with: wrangler deploy
Docker Deployment (Node.js)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
USER node
EXPOSE 3000
CMD ["node", "dist/node.js"]
Docker Deployment (Bun)
FROM oven/bun:1 AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
FROM oven/bun:1-slim
WORKDIR /app
COPY --from=builder /app .
USER bun
EXPOSE 3000
CMD ["bun", "run", "src/bun.ts"]
RPC for Type-Safe Client-Server
// Hono RPC creates type-safe API clients
// Server
const routes = app
.get("/api/users", async (c) => {
return c.json({ users: [{ id: 1, name: "Alice" }] });
})
.post("/api/users", zValidator("json", createUserSchema), async (c) => {
const data = c.req.valid("json");
return c.json({ id: 1, ...data }, 201);
});
export type AppType = typeof routes;
// Client (fully typed!)
import { hc } from "hono/client";
import type { AppType } from "./server";
const client = hc<AppType>("http://localhost:3000");
const res = await client.api.users.$get();
const data = await res.json(); // Fully typed!
Performance Comparison
# Hono benchmarks (requests/sec on single core):
# Bun runtime: ~120,000 req/s
# Node.js: ~50,000 req/s
# Deno: ~60,000 req/s
# CF Workers: Depends on edge location
# Express.js comparison: ~15,000 req/s
# Fastify comparison: ~45,000 req/s
Systemd Service
[Unit]
Description=Hono API
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/hono-api
ExecStart=/usr/bin/node dist/node.js
EnvironmentFile=/opt/hono-api/.env
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
Summary
Hono delivers Express-like developer experience with significantly better performance across every JavaScript runtime. Its multi-runtime support means you can develop locally with Bun for speed, deploy to Cloudflare Workers for edge performance, or use Node.js for traditional VPS deployments — all with the same codebase. The RPC feature provides end-to-end type safety between client and server, eliminating an entire class of API integration bugs.