Docs / Programming & Development / Deploy Hono.js

Deploy Hono.js

By Admin · Mar 15, 2026 · Updated Apr 24, 2026 · 96 views · 4 min read

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.

Was this article helpful?