This guide covers deploying your Hono OpenAPI application to production environments.
Build Process
Before deploying, build your application:
This runs TypeScript compilation and path alias resolution:
{
"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:
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
Using Turso (Recommended)
Turso is a distributed SQLite database perfect for edge deployments.
curl -sSfL https://get.tur.so/install.sh | bash
turso db create my-api-db
# Database URL
turso db show my-api-db --url
# Auth token
turso db tokens create my-api-db
Update your drizzle.config.ts and .env:
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,
},
});
DATABASE_URL=libsql://your-db.turso.io
DATABASE_AUTH_TOKEN=your-token-here
Using Local SQLite
For simpler deployments without Turso:
DATABASE_URL=file:./data/prod.db
Ensure the data directory persists between deployments.
The starter uses @hono/node-server which works on any Node.js hosting platform.
Railway
Render
Fly.io
DigitalOcean
Railway provides simple Node.js deployments.
- Create a new project on Railway
- Connect your GitHub repository
- Add environment variables in the Railway dashboard
- Railway auto-detects the build command from
package.json
# Build command (auto-detected)
pnpm build
# Start command (auto-detected)
pnpm start
Deploy to Render with their free tier.
- Create a new Web Service
- Connect your repository
- Configure build settings:
# Build Command
pnpm install && pnpm build && pnpm drizzle-kit push
# Start Command
pnpm start
- Add environment variables in Render dashboard
Deploy to Fly.io with global distribution.
- Install Fly CLI:
curl -L https://fly.io/install.sh | sh
- Login and launch:
fly auth login
fly launch
- Set environment variables:
fly secrets set DATABASE_URL=your-url
fly secrets set DATABASE_AUTH_TOKEN=your-token
- Deploy:
Deploy to DigitalOcean App Platform.
- Create a new App
- Connect your GitHub repository
- Configure app:
name: hono-api
services:
- name: api
build_command: pnpm install && pnpm build
run_command: pnpm start
envs:
- key: NODE_ENV
value: production
- key: DATABASE_URL
value: ${DATABASE_URL}
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:
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.
pnpm drizzle-kit generate
This creates SQL migration files in src/db/migrations/.
Always review generated migrations before applying:
cat src/db/migrations/0001_migration_name.sql
Add migration command to your deployment script:
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);
});
Run before starting the app:
# 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
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.
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:
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
Never commit .env files
Use platform environment variable management
Rotate secrets regularly
import { cors } from "hono/cors";
app.use(cors({
origin: ["https://yourdomain.com"],
allowMethods: ["GET", "POST", "PATCH", "DELETE"],
}));
Add rate limiting to prevent abuse (see Performance section).
Ensure your platform serves traffic over HTTPS only.
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