Skip to main content

Best Hono Backend Boilerplates 2026

·StarterPick Team
honobackendapicloudflare-workersbuntypescript2026

TL;DR

Hono's official starter templates are the best starting point. create-hono generates a project configured for your target runtime (Node.js, Cloudflare Workers, Bun, AWS Lambda, Deno) in under a minute. Community starters add Drizzle/Prisma, JWT auth, and OpenAPI specs. The Hono ecosystem has matured quickly — you get a full API stack (routing, validation, middleware, RPC types) without the weight of NestJS or the limitations of tRPC.

Key Takeaways

  • create-hono: official scaffold, supports 8 runtimes, TypeScript first
  • hono-zod-openapi: community starter with Zod validation + auto-generated OpenAPI docs
  • Hono + Drizzle: common combo for serverless APIs (both edge-compatible)
  • Hono RPC: type-safe client without a separate schema layer (like tRPC but built-in)
  • Edge deployment: Cloudflare Workers starter is the most popular — zero cold starts
  • For SaaS backends: Node.js adapter + Drizzle + Hono RPC = full type-safe API

Official: create-hono

npm create hono@latest my-api
# Prompts:
#   ✔ Which template do you want to use?
#     › cloudflare-workers
#       nodejs
#       bun
#       aws-lambda
#       deno
#       vercel
#       lambda-edge
#       fastly

Cloudflare Workers Template

my-api/
  ├── src/
  │   └── index.ts       ← Hono app
  ├── wrangler.toml       ← Cloudflare Workers config
  ├── package.json
  └── tsconfig.json
// src/index.ts — Cloudflare Workers starter:
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';

type Bindings = {
  DATABASE_URL: string;
  JWT_SECRET: string;
  // Cloudflare bindings: KV, D1, R2, etc.
  CACHE: KVNamespace;
};

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

app.use('*', cors());
app.use('*', logger());

app.get('/', (c) => c.json({ message: 'Hello from Hono!' }));

app.get('/health', (c) => {
  return c.json({
    status: 'ok',
    runtime: 'cloudflare-workers',
    timestamp: new Date().toISOString(),
  });
});

export default app;
# wrangler.toml:
name = "my-api"
main = "src/index.ts"
compatibility_date = "2024-01-01"

[[kv_namespaces]]
binding = "CACHE"
id = "your-kv-id"

[vars]
DATABASE_URL = "your-db-url"

Node.js Template

// src/index.ts — Node.js server:
import { Hono } from 'hono';
import { serve } from '@hono/node-server';

const app = new Hono();

app.get('/', (c) => c.text('Hello Node.js!'));

const port = Number(process.env.PORT ?? 3000);
serve({ fetch: app.fetch, port }, (info) => {
  console.log(`Listening on http://localhost:${info.port}`);
});

Community Starter: Hono + Drizzle + Auth

A popular community pattern for full-featured Hono APIs:

# Manual setup (no dedicated repo — copy this pattern):
npm install hono @hono/node-server @hono/zod-validator
npm install drizzle-orm @auth/core hono-auth-helpers
npm install --save-dev drizzle-kit tsx
// src/db/schema.ts:
import { pgTable, text, timestamp } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
  email: text('email').notNull().unique(),
  name: text('name'),
  createdAt: timestamp('created_at').defaultNow().notNull(),
});

export const sessions = pgTable('sessions', {
  id: text('id').primaryKey(),
  userId: text('user_id').notNull().references(() => users.id),
  expiresAt: timestamp('expires_at').notNull(),
});
// src/routes/posts.ts — typed route handlers:
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { jwt } from 'hono/jwt';
import { z } from 'zod';
import { db } from '../db';
import { posts } from '../db/schema';
import { eq, desc } from 'drizzle-orm';

const postsRouter = new Hono()
  .use('*', jwt({ secret: process.env.JWT_SECRET! }))
  
  .get('/', async (c) => {
    const allPosts = await db.select().from(posts).orderBy(desc(posts.createdAt)).limit(20);
    return c.json(allPosts);
  })
  
  .post('/', zValidator('json', z.object({
    title: z.string().min(1).max(200),
    content: z.string().min(1),
  })), async (c) => {
    const { title, content } = c.req.valid('json');
    const payload = c.get('jwtPayload');
    
    const [post] = await db.insert(posts).values({
      title,
      content,
      authorId: payload.sub,
    }).returning();
    
    return c.json(post, 201);
  });

export { postsRouter };
// src/index.ts — assemble routes:
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import { cors } from 'hono/cors';
import { postsRouter } from './routes/posts';
import { authRouter } from './routes/auth';

const app = new Hono()
  .use('*', cors({ origin: process.env.FRONTEND_URL }))
  .route('/auth', authRouter)
  .route('/api/posts', postsRouter);

// Export type for RPC client:
export type AppType = typeof app;

serve({ fetch: app.fetch, port: 3000 });

Hono OpenAPI Starter

For public APIs needing documentation:

npm install @hono/zod-openapi @hono/swagger-ui
// OpenAPI route with auto-generated docs:
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';

const app = new OpenAPIHono();

const PostSchema = z.object({
  id: z.string().openapi({ example: 'post-123' }),
  title: z.string().openapi({ example: 'Hello World' }),
  content: z.string(),
});

const createPostRoute = createRoute({
  method: 'post',
  path: '/posts',
  request: {
    body: { content: { 'application/json': { schema: PostSchema.omit({ id: true }) } } },
  },
  responses: {
    201: { description: 'Post created', content: { 'application/json': { schema: PostSchema } } },
    400: { description: 'Validation error' },
  },
  tags: ['posts'],
  summary: 'Create a new post',
});

app.openapi(createPostRoute, async (c) => {
  const body = c.req.valid('json');
  const post = await db.insert(posts).values(body).returning();
  return c.json(post[0], 201);
});

// Auto-generate OpenAPI spec at /doc:
app.doc('/doc', { openapi: '3.0.0', info: { title: 'My API', version: '1.0.0' } });

// Swagger UI at /ui:
app.get('/ui', swaggerUI({ url: '/doc' }));

Hono RPC Client (Type-Safe, No Schema)

// Frontend/client — type-safe without a separate schema layer:
import { hc } from 'hono/client';
import type { AppType } from '../server/src/index';  // Import server types

const client = hc<AppType>(process.env.NEXT_PUBLIC_API_URL!);

// Fully typed — no codegen needed:
const postsResponse = await client.api.posts.$get();
const posts = await postsResponse.json();  // typed: Post[]

const newPost = await client.api.posts.$post({
  json: { title: 'Hello', content: 'World' },
});

Starter Templates by Use Case

Use CaseTemplateKey Packages
Cloudflare Workers APIcreate-hono (cf)Hono + Cloudflare D1 + Drizzle
Node.js REST APIcreate-hono (nodejs)Hono + Drizzle + JWT
Bun APIcreate-hono (bun)Hono + Bun + Drizzle
OpenAPI serviceManualHono + zod-openapi
Full-stack Next.js backendManualHono + Next.js + Hono RPC
Choose Hono boilerplate if:
  → Building a standalone API (not tRPC)
  → Deploying to Cloudflare Workers or edge
  → Need OpenAPI spec generation
  → Want type-safe client without full tRPC setup
  → Migrating from Express

Prefer tRPC (T3 Stack) if:
  → Next.js full-stack app
  → Server and client in same monorepo
  → Team knows T3 patterns

Find Hono boilerplates and compare backend starters at StarterPick.

Comments