Skip to main content
This guide covers deploying your Hono OpenAPI application to production environments.

Build Process

Before deploying, build your application:
pnpm build
This runs TypeScript compilation and path alias resolution:
package.json
{
  "scripts": {
    "build": "tsc && tsc-alias",
    "start": "node ./dist/src/index.js"
  }
}
The build output is written to the dist/ directory.
The tsc-alias step resolves TypeScript path aliases (like @/db) to relative paths for runtime.

Environment Configuration

Ensure all required environment variables are set:
.env
NODE_ENV=production
PORT=9999
LOG_LEVEL=info
DATABASE_URL=your-database-url
DATABASE_AUTH_TOKEN=your-auth-token  # Required for Turso
Never commit .env files to version control. Use your platform’s environment variable management.

Database Setup

Turso is a distributed SQLite database perfect for edge deployments.
1
Install Turso CLI
2
curl -sSfL https://get.tur.so/install.sh | bash
3
Create Database
4
turso db create my-api-db
5
Get Connection Details
6
# Database URL
turso db show my-api-db --url

# Auth token
turso db tokens create my-api-db
7
Update Configuration
8
Update your drizzle.config.ts and .env:
9
export default defineConfig({
  schema: "./src/db/schema.ts",
  out: "./src/db/migrations",
  dialect: "turso",
  casing: "snake_case",
  dbCredentials: {
    url: env.DATABASE_URL,
    authToken: env.DATABASE_AUTH_TOKEN,
  },
});
10
DATABASE_URL=libsql://your-db.turso.io
DATABASE_AUTH_TOKEN=your-token-here
11
Push Schema
12
pnpm drizzle-kit push

Using Local SQLite

For simpler deployments without Turso:
.env
DATABASE_URL=file:./data/prod.db
Ensure the data directory persists between deployments.

Deployment Platforms

Node.js Platforms

The starter uses @hono/node-server which works on any Node.js hosting platform.
Railway provides simple Node.js deployments.
  1. Create a new project on Railway
  2. Connect your GitHub repository
  3. Add environment variables in the Railway dashboard
  4. Railway auto-detects the build command from package.json
# Build command (auto-detected)
pnpm build

# Start command (auto-detected)
pnpm start

Cloudflare Workers

For Cloudflare Workers deployment, use the cloudflare branch of the starter template. Key differences:
  • Uses Cloudflare Workers runtime
  • Integrates with D1 database
  • Requires wrangler.toml configuration

Docker

Create a Dockerfile for containerized deployments:
Dockerfile
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm install -g pnpm && pnpm install

COPY . .
RUN pnpm build

FROM node:20-alpine

WORKDIR /app

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

EXPOSE 9999

CMD ["node", "./dist/src/index.js"]
Build and run:
# Build image
docker build -t hono-api .

# Run container
docker run -p 9999:9999 \
  -e DATABASE_URL=your-url \
  -e DATABASE_AUTH_TOKEN=your-token \
  hono-api

Database Migrations

For production deployments, use migrations instead of drizzle-kit push.
1
Generate Migration
2
pnpm drizzle-kit generate
3
This creates SQL migration files in src/db/migrations/.
4
Review Migration
5
Always review generated migrations before applying:
6
cat src/db/migrations/0001_migration_name.sql
7
Apply in Production
8
Add migration command to your deployment script:
9
import { migrate } from "drizzle-orm/libsql/migrator";
import db from "@/db";

async function runMigrations() {
  console.log("Running migrations...");
  await migrate(db, { migrationsFolder: "./src/db/migrations" });
  console.log("Migrations complete");
  process.exit(0);
}

runMigrations().catch((err) => {
  console.error("Migration failed", err);
  process.exit(1);
});
10
Run before starting the app:
11
# Build
pnpm build

# Run migrations
node ./dist/src/scripts/migrate.js

# Start app
pnpm start

Health Checks

Add a health check endpoint for monitoring:
src/routes/index.route.ts
import { createRouter } from "@/lib/create-app";
import { createRoute } from "@hono/zod-openapi";
import * as HttpStatusCodes from "stoker/http-status-codes";

const router = createRouter();

const health = createRoute({
  path: "/health",
  method: "get",
  responses: {
    [HttpStatusCodes.OK]: {
      description: "API is healthy",
    },
  },
});

router.openapi(health, (c) => {
  return c.json({ status: "ok" }, HttpStatusCodes.OK);
});

export default router;
Configure your platform to check /health.

Logging

Structured logging is configured with pino.

Production Log Level

.env
LOG_LEVEL=info  # Options: trace, debug, info, warn, error

Log Output

Logs are written to stdout in JSON format:
{
  "level": 30,
  "time": 1640000000000,
  "msg": "Server is running on port 9999",
  "hostname": "api-server"
}
Configure your platform to capture and store logs.

Performance

Production Optimizations

Add compression middleware for smaller responses:
import { compress } from "hono/compress";

app.use(compress());
Use caching for OpenAPI documentation:
app.get("/doc", cache({ cacheName: "openapi-spec", wait: true }), (c) => {
  return c.json(app.getOpenAPIDocument());
});
Turso handles connection pooling automatically. For other databases, configure pool size:
const db = drizzle({
  connection: {
    url: env.DATABASE_URL,
    authToken: env.DATABASE_AUTH_TOKEN,
  },
  casing: "snake_case",
  schema,
});
Add rate limiting to protect your API:
import { rateLimiter } from "hono-rate-limiter";

app.use(rateLimiter({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
}));

Monitoring

Error Tracking

Integrate error tracking services:
src/lib/create-app.ts
import * as Sentry from "@sentry/node";

if (env.NODE_ENV === "production") {
  Sentry.init({
    dsn: env.SENTRY_DSN,
    environment: env.NODE_ENV,
  });
}

app.onError((err, c) => {
  if (env.NODE_ENV === "production") {
    Sentry.captureException(err);
  }
  
  // ... error handling
});

Uptime Monitoring

Use services like: Configure them to ping your /health endpoint.

Security Checklist

1
Environment Variables
2
  • Never commit .env files
  • Use platform environment variable management
  • Rotate secrets regularly
  • 3
    CORS Configuration
    4
    import { cors } from "hono/cors";
    
    app.use(cors({
      origin: ["https://yourdomain.com"],
      allowMethods: ["GET", "POST", "PATCH", "DELETE"],
    }));
    
    5
    Rate Limiting
    6
    Add rate limiting to prevent abuse (see Performance section).
    7
    HTTPS Only
    8
    Ensure your platform serves traffic over HTTPS only.
    9
    Database Security
    10
  • Use authentication tokens for Turso
  • Restrict database access to application servers only
  • Never expose database credentials in logs
  • Deployment Examples

    For complete deployment examples, see the hono-node-deployment-examples repository.

    Next Steps

    Routes

    Learn how to create API routes

    Database

    Database configuration and migrations

    Testing

    Test before deploying

    Turso Docs

    Turso database documentation

    Build docs developers (and LLMs) love