OpenAPI REST API
External REST API with Hono and automatic OpenAPI documentation
OpenAPI REST API
Maildrone includes a REST API built with Hono and automatic OpenAPI documentation. Perfect for:
- External integrations (mobile apps, third-party services)
- Public API for your email marketing platform
- Webhooks and callbacks
- Non-TypeScript clients
tRPC vs REST: Use tRPC for your Next.js frontend (type-safe, faster), use REST API for external clients.
API Documentation Options
There are three ways to access API documentation:
Swagger UI
Interactive API explorer with try-it-out feature
OpenAPI JSON
Raw OpenAPI 3.0 specification for tools & SDKs
API Reference
Integrated docs with Fumadocs styling
1. Swagger UI (/api/v1/doc)
The classic interactive API documentation. Test API calls directly from the browser.
http://localhost:3000/api/v1/doc📖 Open Swagger UI →
2. OpenAPI JSON (/api/v1/openapi.json)
Raw OpenAPI 3.0 specification. Use this for:
- Generating SDKs with tools like
openapi-generator - Importing into Postman, Insomnia, or other API clients
- CI/CD validation and contract testing
http://localhost:3000/api/v1/openapi.json📄 View OpenAPI JSON →
3. API Reference (Fumadocs Integration)
Beautiful, searchable API documentation integrated into the docs site using Fumadocs OpenAPI.
Note: The API Reference pages are auto-generated from your OpenAPI schema. Run pnpm openapi:generate to generate them before accessing the links below.
📚 Browse API Reference →
Quick Start
Make Your First Request
# Health check
curl http://localhost:3000/api/v1/health
# Response
{
"status": "ok",
"timestamp": "2025-11-17T00:00:00.000Z",
"version": "1.0.0"
}Project Structure
src/
└── app/
└── api/
└── v1/
├── [[...server]]/
│ ├── route.ts # Next.js handler (delegates to Hono)
│ └── app.ts # Hono app definition
└── routes/
├── index.ts # Route registration
├── health.ts # Health check endpoint
└── ... # Other API endpointsCreating Endpoints
Basic Endpoint
Create a new route file in src/app/api/v1/routes/:
import { createRoute, z } from '@hono/zod-openapi';
import { app } from '../[[...server]]/app';
// Define OpenAPI schema
const route = createRoute({
method: 'get',
path: '/hello',
summary: 'Say hello',
tags: ['General'],
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: z.object({
message: z.string(),
timestamp: z.string(),
}),
},
},
},
},
});
// Implement handler
app.openapi(route, (c) => {
return c.json({
message: 'Hello from Maildrone!',
timestamp: new Date().toISOString(),
});
});Register in src/app/api/v1/routes/index.ts:
import './hello'; // ← Add thisNow visit: http://localhost:3000/api/v1/hello 🎉
Authentication
API Key Auth
Add authentication middleware:
import { createMiddleware } from 'hono/factory';
const apiKeyAuth = createMiddleware(async (c, next) => {
const apiKey = c.req.header('X-API-Key');
if (!apiKey) {
return c.json({ error: 'Missing API key' }, 401);
}
// Verify API key
const valid = await verifyApiKey(apiKey);
if (!valid) {
return c.json({ error: 'Invalid API key' }, 401);
}
await next();
});
// Use in routes
app.use('/api/v1/protected/*', apiKeyAuth);Bearer Token Auth
const bearerAuth = createMiddleware(async (c, next) => {
const authorization = c.req.header('Authorization');
if (!authorization?.startsWith('Bearer ')) {
return c.json({ error: 'Missing bearer token' }, 401);
}
const token = authorization.replace('Bearer ', '');
try {
const session = await verifyToken(token);
c.set('session', session);
await next();
} catch (err) {
return c.json({ error: 'Invalid token' }, 401);
}
});Validation
Hono uses Zod for validation. All schemas are automatically documented in OpenAPI!
const schema = z.object({
email: z.string().email('Invalid email format'),
subject: z.string().min(1, 'Subject is required'),
content: z.string().min(1, 'Content is required'),
recipients: z.array(z.string().email()).min(1),
});
app.openapi(route, async (c) => {
const data = c.req.valid('json'); // ← Already validated!
// data is typed and validated
console.log(data.email); // ✅ string
console.log(data.recipients); // ✅ string[]
});Error Handling
Standard Error Responses
// 400 Bad Request
return c.json({ error: 'Invalid input', details: validationErrors }, 400);
// 401 Unauthorized
return c.json({ error: 'Authentication required' }, 401);
// 403 Forbidden
return c.json({ error: 'Insufficient permissions' }, 403);
// 404 Not Found
return c.json({ error: 'Resource not found' }, 404);
// 500 Internal Server Error
return c.json({ error: 'Internal server error' }, 500);Global Error Handler
// src/app/api/v1/[[...server]]/app.ts
app.onError((err, c) => {
console.error('API Error:', err);
if (err instanceof ZodError) {
return c.json({
error: 'Validation failed',
details: err.errors,
}, 400);
}
return c.json({
error: 'Internal server error',
message: process.env.NODE_ENV === 'development' ? err.message : undefined,
}, 500);
});CORS
Enable CORS for external clients:
import { cors } from 'hono/cors';
app.use('/api/v1/*', cors({
origin: ['https://yourdomain.com', 'https://app.yourdomain.com'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
credentials: true,
}));Development (allow all):
app.use('/api/v1/*', cors({
origin: '*', // ⚠️ Only for development!
}));Best Practices
✅ Do
- Version your API - Use
/api/v1/prefix - Validate all inputs - Use Zod schemas
- Return consistent errors - Standard error format
- Document everything - OpenAPI makes it easy
- Use proper HTTP methods - GET, POST, PUT, DELETE
- Return proper status codes - 200, 201, 400, 401, 404, 500
❌ Don't
- Don't skip validation - Always validate inputs
- Don't expose internal errors - Generic error messages in production
- Don't return sensitive data - Strip passwords, tokens, etc.
- Don't ignore security - Use auth, rate limiting, CORS
- Don't make breaking changes - Version your API instead
Next Steps
- Authentication - Secure your endpoints
- Settings - Configure API keys and webhooks
- Troubleshooting - Common API issues
Resources
- Hono Docs: hono.dev
- Zod OpenAPI: github.com/honojs/middleware
- OpenAPI Spec: swagger.io/specification