Modern Node.js APIs with Hono, Bun, and Edge Runtime
The 2026 Node.js landscape
| Framework | Year | Runtime | Philosophy |
|---|---|---|---|
| Express | 2010 | Node.js only | Mature, minimal, large ecosystem |
| Fastify | 2016 | Node.js | 2× faster than Express, schema-first |
| Koa | 2013 | Node.js | Express successor by same team, async-native |
| Hono | 2022 | Node/Deno/Bun/Edge | Multi-runtime, Web-Fetch-API-based |
| ElysiaJS | 2023 | Bun only | Bun-native, type safety |
Pick Hono for new greenfield APIs — it runs identically on Node.js (DomainIndia VPS), Bun, Deno, Cloudflare Workers, Vercel Functions, AWS Lambda. Deploy flexibility without code changes.
Why Bun runtime
| Metric | Node.js 20 | Bun 1.1 | Deno 1.46 |
|---|---|---|---|
| Install speed | Baseline | 25-30× faster | Similar to Node |
| Startup time | ~200ms | ~20ms | ~100ms |
| Built-in APIs | Requires packages | Built-in (SQLite, WS, password hashing) | Requires packages |
| Package mgmt | npm/yarn/pnpm | bun install (lockfile-compatible) | JSR / npm |
| Production-ready 2026? | Yes | Yes | Yes (for greenfield) |
Bun wins for dev experience + install speed. Node.js is still the most proven in production.
Step 1 — Install Bun on DomainIndia VPS
curl -fsSL https://bun.sh/install | bash
source ~/.bashrc
bun --versionOr stick with Node.js 20+:
# AlmaLinux
sudo dnf install -y nodejs npm
# Ubuntu
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
sudo apt install -y nodejsStep 2 — Scaffold Hono API
mkdir myapi && cd myapi
bun init # or: npm init -y
bun add hono
# or: npm install honosrc/index.ts:
import { Hono } from 'hono';
import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { jwt } from 'hono/jwt';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
const app = new Hono();
app.use('*', logger());
app.use('*', cors({ origin: ['https://yourcompany.com'] }));
// Public routes
app.get('/health', (c) => c.json({ status: 'ok', version: '1.0.0' }));
// Protected routes
app.use('/v1/*', jwt({ secret: Bun.env.JWT_SECRET! }));
const userSchema = z.object({
email: z.string().email(),
name: z.string().min(1),
});
app.post('/v1/users', zValidator('json', userSchema), async (c) => {
const { email, name } = c.req.valid('json');
const user = await createUser(email, name); // your DB call
return c.json(user, 201);
});
app.get('/v1/users/:id', async (c) => {
const user = await getUser(c.req.param('id'));
if (!user) return c.json({ error: 'Not found' }, 404);
return c.json(user);
});
// Error handler
app.onError((err, c) => {
console.error(err);
return c.json({ error: err.message }, 500);
});
export default {
port: Number(Bun.env.PORT) || 3000,
fetch: app.fetch,
};Run with Bun:
bun run --watch src/index.tsOr Node.js (via @hono/node-server):
bun add @hono/node-server
# src/server.ts:
# import { serve } from '@hono/node-server';
# serve({ fetch: app.fetch, port: 3000 });
node src/server.tsSame code. Swap runtime.
Step 3 — Database with Bun's built-in SQLite or Drizzle
SQLite (Bun native, no dep):
import { Database } from 'bun:sqlite';
const db = new Database('data.db', { create: true });
db.run(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE,
name TEXT
)
`);
const insertUser = db.prepare('INSERT INTO users (id, email, name) VALUES (?, ?, ?)');
insertUser.run(crypto.randomUUID(), '[email protected]', 'Rajesh');
const users = db.prepare('SELECT * FROM users').all();PostgreSQL with Drizzle ORM (works everywhere):
bun add drizzle-orm postgres
bun add -D drizzle-kitimport { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import { pgTable, text, timestamp } from 'drizzle-orm/pg-core';
const client = postgres(Bun.env.DATABASE_URL!);
export const db = drizzle(client);
export const users = pgTable('users', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
email: text('email').unique().notNull(),
name: text('name').notNull(),
createdAt: timestamp('created_at').defaultNow(),
});
// Usage:
await db.insert(users).values({ email: '[email protected]', name: 'Rajesh' });
const all = await db.select().from(users);Drizzle is type-safe, introspectable, and works on Node, Bun, Edge. Unlike Prisma, no code generation step or binary dependency.
Step 4 — Deploy to DomainIndia VPS
systemd service /etc/systemd/system/hono-api.service:
[Unit]
Description=Hono API
After=network.target postgresql.service
[Service]
Type=simple
User=hono
WorkingDirectory=/opt/hono-api
ExecStart=/home/hono/.bun/bin/bun run src/index.ts
Restart=on-failure
RestartSec=3
EnvironmentFile=/opt/hono-api/.env
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.targetFront with nginx + SSL (see Go deployment guide for same pattern).
Step 5 — Deploy to Cloudflare Workers (same code!)
The magic of Hono: deploy the same app to Cloudflare Workers:
npm install -g wrangler
wrangler init --yeswrangler.toml:
name = "hono-api"
main = "src/index.ts"
compatibility_date = "2026-04-01"src/index.ts (modified for Workers):
export default {
async fetch(request: Request, env: Env): Promise<Response> {
return app.fetch(request, env);
},
};Deploy:
wrangler deploySame business logic. Now runs at 330+ edge locations. Dev/prod parity with your VPS.
Pattern: Edge + origin
Route some requests to edge (fast, cheap), others to VPS (stateful):
Client ─► Cloudflare Worker
│
├── /api/search → Worker handles (cached, fast)
├── /api/health → Worker handles
└── /api/orders → Forwards to VPS (needs DB)app.get('/api/orders/*', async (c) => {
// Forward to VPS origin
return fetch(`https://origin.yourcompany.com${c.req.path}`, c.req.raw);
});Performance — real numbers
On a DomainIndia VPS Starter (2 GB):
| Stack | Req/sec (hello world) | Req/sec (DB query) |
|---|---|---|
| Express + Node.js 20 | 18,000 | 2,500 |
| Hono + Node.js 20 | 32,000 | 3,000 |
| Hono + Bun | 55,000 | 3,200 |
| Hono + Cloudflare Workers | (global edge) | (depends on origin) |
Hono + Bun gives you raw throughput. For DB-bound workloads, runtime choice matters less — Postgres is the bottleneck.
Testing
Hono's test utility:
import { describe, test, expect } from 'bun:test';
describe('Health', () => {
test('returns ok', async () => {
const res = await app.request('/health');
expect(res.status).toBe(200);
const body = await res.json();
expect(body.status).toBe('ok');
});
});Run: bun test.
Common pitfalls
FAQ
Hono — multi-runtime, future-proof, minimal. Express still fine for pure Node.js simplicity. Fastify if you need schema-first JSON validation and don't care about edge deployment.
Yes, for 2026. Major apps (Midjourney, Vercel, Supabase) use it. Node.js is still more battle-tested; go Bun for greenfield, Node for legacy.
Drizzle: TypeScript-first, SQL-like API, no codegen, runs on edge. Prisma: More polished CLI, bigger ecosystem, slower on edge. Both are solid; Drizzle is the modern trend.
Shared cPanel "Setup Node.js App" doesn't support Bun runtime. Use VPS, or use Node.js compatibility mode and deploy normally.
Workers Free: 100K req/day, 10ms CPU, 128 MB. Workers Paid: 10M/mo, 50ms CPU. Past that: VPS with Hono + Node/Bun is unlimited (pay per VPS, not per request).
Run modern Node/Bun APIs on a DomainIndia VPS — zero platform tax. Get a VPS