@zimyo/api
v0.0.1
Published
Fastify-based framework with file-based routing, auth middleware, database adapters, and production-ready plugins
Readme
@zimyo-api
Production-ready Fastify framework with file-based routing, Express migration support, database adapters, and built-in security/performance plugins.
Quick Start
pnpm add @zimyo-apiimport { createApp } from "@zimyo-api";
import path from "path";
const app = await createApp({
port: 3000,
routing: "controller",
routesDir: path.join(__dirname, "routes"),
cors: true,
helmet: true,
compress: true,
swagger: true,
});
await app.start();Table of Contents
- Routing Modes
- Plugins
- Logging
- HTTP Client
- Database Adapters
- Authentication
- Monitoring Dashboard
- Health Check
- Express Migration
- Full Config Reference
Routing Modes
File-Based Routing
Auto-discovers controllers from a directory. File paths map to URL paths.
createApp({
routing: "file",
controllersDir: path.join(__dirname, "controllers"),
});controllers/
users/
index.ts → GET/POST /users
[id].ts → GET/PUT/DELETE /users/:id
health.ts → GET /healthEach file exports HTTP method handlers:
// controllers/users/index.ts
import type { RouteModule } from "@zimyo-api";
export const GET: RouteModule["GET"] = async (request, reply) => {
return { users: [] };
};
export const POST: RouteModule["POST"] = async (request, reply) => {
return { created: true };
};Controller-Based Routing
Scans *.routes.ts files using defineRoutes():
createApp({
routing: "controller",
routesDir: path.join(__dirname, "routes"),
});// routes/user.routes.ts
import { defineRoutes } from "@zimyo-api";
export default defineRoutes("/users", [
{ method: "GET", path: "/", handler: getAll },
{ method: "GET", path: "/:id", handler: getById },
{ method: "POST", path: "/", handler: create, config: { isPublic: true } },
]);Express Compatibility
Loads existing Express route files without rewriting:
import { createApp, expressAdapter } from "@zimyo-api";
const app = await createApp(expressAdapter({
port: 8082,
routeEntry: path.join(__dirname, "src/routes/index.js"),
appConfig: require("./src/config/server.config"),
}));Plugins
All plugins are opt-in via config flags. Pass true for defaults or an object for custom config. Each requires installing its Fastify plugin package.
CORS
pnpm add @fastify/corscreateApp({
// ... routing config
cors: true, // defaults: all origins, all methods
// or
cors: {
origin: ["https://app.zimyo.com"],
credentials: true,
methods: ["GET", "POST", "PUT", "DELETE"],
maxAge: 86400,
},
});Rate Limiting
pnpm add @fastify/rate-limitcreateApp({
rateLimit: true, // 100 req/min per IP
// or
rateLimit: {
max: 200,
timeWindow: 60_000, // 1 minute
allowList: ["/health"],
keyGenerator: (req) => req.headers["x-tenant-id"] || req.ip,
errorMessage: "Too many requests, please try again later.",
},
});Helmet (Security Headers)
pnpm add @fastify/helmetcreateApp({
helmet: true, // sensible defaults
// or
helmet: {
contentSecurityPolicy: false, // disable CSP (common for APIs)
hsts: { maxAge: 31536000, includeSubDomains: true },
frameguard: { action: "deny" },
},
});Response Compression
pnpm add @fastify/compresscreateApp({
compress: true, // brotli > gzip > deflate
// or
compress: {
encodings: ["gzip", "deflate"], // disable brotli
threshold: 2048, // min 2KB to compress
},
});File Uploads (Multipart)
pnpm add @fastify/multipartcreateApp({
multipart: true, // 10MB max, 10 files max
// or
multipart: {
limits: {
fileSize: 50 * 1024 * 1024, // 50MB
files: 5,
},
attachFieldsToBody: true,
},
});In route handlers:
export const POST = async (request, reply) => {
const file = await request.file();
await file.toBuffer(); // or pipe to disk
};Swagger / OpenAPI Docs
pnpm add @fastify/swagger @fastify/swagger-uicreateApp({
swagger: true, // serves at /docs
// or
swagger: {
title: "My API",
description: "API documentation",
version: "2.0.0",
routePrefix: "/api-docs",
contact: { name: "Team", email: "[email protected]" },
servers: [
{ url: "https://api.zimyo.com", description: "Production" },
{ url: "http://localhost:3000", description: "Local" },
],
securityDefinitions: {
token: { type: "apiKey", name: "token", in: "header" },
},
},
});Add schemas to routes for auto-documentation:
export default defineRoutes("/users", [
{
method: "GET",
path: "/",
handler: getAll,
config: {
schema: {
querystring: {
type: "object",
properties: {
page: { type: "integer", default: 1 },
limit: { type: "integer", default: 20 },
},
},
response: {
200: {
type: "object",
properties: {
data: { type: "array", items: { type: "object" } },
},
},
},
},
},
},
]);Logging
Three modes: "pretty" (colored console, default in dev), "json" (structured, default in production), "silent".
createApp({
logMode: "json", // structured JSON logs for ELK/CloudWatch/Datadog
});Pretty mode (development):
14:32:01.234 POST 200 /api/users 12.3ms 127.0.0.1JSON mode (production):
{"level":"info","time":"2026-06-04T14:32:01.234Z","msg":"POST /api/users","type":"request","method":"POST","url":"/api/users","statusCode":200,"durationMs":12.3,"ip":"127.0.0.1","reqId":"a1b2c3d4-..."}Use the logger directly:
import { log } from "@zimyo-api";
log.info("Server starting...");
log.success("Connected to database");
log.warn("Deprecated API called", { endpoint: "/v1/old" });
log.error("Payment failed", { orderId: 123 });
log.debug("Cache miss for key: user:42");HTTP Client
Built-in fetch-based HTTP client with retry logic and circuit breaker.
Basic Usage
import { http } from "@zimyo-api";
const { data } = await http.get("https://api.example.com/users");
const { data } = await http.post("https://api.example.com/users", { name: "John" });
const { data } = await http.put("https://api.example.com/users/1", { name: "Jane" });
const { data } = await http.patch("https://api.example.com/users/1", { name: "Jane" });
const { data } = await http.delete("https://api.example.com/users/1");
// With headers and timeout
const { data } = await http.get(url, {
headers: { Authorization: "Bearer token123" },
timeout: 5000,
});Retry Logic
Automatic retry with exponential backoff for transient failures:
// Simple: retry 3 times
const { data } = await http.get(url, { retry: 3 });
// Custom retry config
const { data } = await http.get(url, {
retry: {
attempts: 5, // max retries
delay: 1000, // base delay (ms)
backoff: 2, // exponential multiplier
retryOn: [408, 429, 500, 502, 503, 504], // status codes to retry
retryOnNetworkError: true, // retry on ECONNREFUSED etc.
},
});Retry timeline with defaults (delay: 1000, backoff: 2):
Attempt 1 → fails → wait 1s
Attempt 2 → fails → wait 2s
Attempt 3 → fails → wait 4s
Attempt 4 → success!Circuit Breaker
Prevent cascading failures when a downstream service is down:
const loginApi = http.create({
baseURL: "https://login-api.zimyo.com",
timeout: 5000,
retry: 2,
circuitBreaker: {
threshold: 5, // open circuit after 5 consecutive failures
resetTimeout: 30000, // try again after 30 seconds
},
});
// All requests through this client share the circuit breaker
const { data } = await loginApi.get("/validate-token");
const { data } = await loginApi.post("/authenticate", { token });
// Check circuit state
const state = loginApi.getCircuitState?.();
// { state: "CLOSED", failures: 0 }
// { state: "OPEN", failures: 5 }
// { state: "HALF_OPEN", failures: 5 }Circuit breaker states:
- CLOSED — normal operation, requests pass through
- OPEN — too many failures, requests rejected instantly with 503
- HALF_OPEN — after
resetTimeout, one request allowed through to test recovery
Custom Client Instances
const paymentApi = http.create({
baseURL: "https://payments.zimyo.com/api/v1",
headers: { "X-API-Key": process.env.PAYMENT_API_KEY },
timeout: 10_000,
retry: { attempts: 3, retryOn: [502, 503, 504] },
circuitBreaker: { threshold: 3, resetTimeout: 60_000 },
});
const { data } = await paymentApi.post("/charge", { amount: 100 });Database Adapters
Single Database
import { createApp } from "@zimyo-api";
import { sequelizeAdapter } from "@zimyo-api/adapters/sequelize";
const app = await createApp({
routing: "controller",
routesDir: "./routes",
database: sequelizeAdapter({
dialect: "mysql",
host: "localhost",
database: "mydb",
username: "root",
password: "secret",
}),
});
// In route handlers: ctx.db is the Sequelize instanceAvailable adapters:
import { sequelizeAdapter } from "@zimyo-api/adapters/sequelize";
import { drizzleAdapter } from "@zimyo-api/adapters/drizzle";
import { redisAdapter } from "@zimyo-api/adapters/redis";
import { mongoAdapter } from "@zimyo-api/adapters/mongo";
import { legacySequelizeAdapter } from "@zimyo-api/adapters/legacy-sequelize";Multiple Databases
Pass an array of adapters. Each must have a unique name. Route handlers receive a map:
import { sequelizeAdapter } from "@zimyo-api/adapters/sequelize";
import { redisAdapter } from "@zimyo-api/adapters/redis";
const app = await createApp({
routing: "controller",
routesDir: "./routes",
database: [
sequelizeAdapter({ name: "mysql", dialect: "mysql", ... }),
redisAdapter({ host: "localhost", port: 6379 }),
],
});
// In route handlers:
// ctx.db = { mysql: SequelizeInstance, redis: RedisClient }Authentication
API Key Auth
createApp({
auth: {
headerName: "x-api-key",
validateKey: async (key) => key === process.env.API_KEY,
publicUrls: ["/health", "/docs"],
},
});JWT / Token Auth
import { createApp, authenticateUser } from "@zimyo-api";
const app = await createApp({
routing: "controller",
routesDir: "./routes",
setup: async (fastify) => {
authenticateUser(fastify, {
whitelistUrls: ["guest/login", "user/forgot-password"],
errorCode: 401,
// Custom auth function:
authenticate: async (request, token) => {
const user = await verifyToken(token);
return user; // attached to request.auth
},
// Or use built-in Login API (reads LOGIN_API_BASE_URL env):
// loginApiBaseUrl: "https://login.zimyo.com",
// appName: "performance",
});
},
});Role-based access on routes:
defineRoutes("/admin", [
{
method: "GET",
path: "/dashboard",
handler: adminDashboard,
config: { role_id: [1, 2] }, // only roles 1 and 2 can access
},
]);Monitoring Dashboard
Built-in request metrics and Sentry-style error tracking with a live dashboard UI. Persistent storage via MongoDB.
createApp({
monitoring: true, // dashboard at /monitoring
// or with MongoDB persistence:
monitoring: {
routePrefix: "/monitoring", // dashboard URL
maxDataPoints: 120, // time-series points (2 min @ 1s)
maxErrors: 500, // recent errors buffer size
mongo: { // MongoDB persistence
uri: "mongodb://localhost:27017",
dbName: "monitoring",
},
},
});Dashboard Features
- Request Metrics — Total requests, req/s, status code breakdown, per-route stats
- Response Times — Percentiles (p50/p95/p99) with live chart
- Error Tracking — Auto-captures errors with stack traces, request context, fingerprint grouping
- System Health — Memory (RSS, heap), active connections, uptime
API Endpoints
| Endpoint | Description |
|----------|-------------|
| GET /monitoring | Live dashboard UI |
| GET /monitoring/api/metrics | JSON metrics snapshot |
| GET /monitoring/api/errors | Error groups and recent errors |
| POST /monitoring/api/reset | Clear all metrics and errors |
Programmatic Access
import { metrics, errorTracker } from "@zimyo-api";
// Get metrics snapshot
const snapshot = metrics.getSnapshot();
console.log(snapshot.totalRequests, snapshot.p95Ms);
// Manually capture an error
errorTracker.capture(new Error("Payment failed"), {
method: "POST",
url: "/api/payments",
statusCode: 500,
metadata: { orderId: 12345 },
});Health Check
Built-in GET /health endpoint with database status and memory info:
{
"status": "ok",
"uptime": 12345.67,
"timestamp": 1717500000000,
"memory": {
"rss": 52428800,
"heapUsed": 20971520,
"heapTotal": 35651584
},
"databases": {
"sequelize": "connected",
"redis": "connected"
}
}Returns "status": "degraded" if any database is disconnected.
Express Migration
Migrate existing Express apps to Fastify without rewriting routes:
import { createApp, expressAdapter } from "@zimyo-api";
import { legacySequelizeAdapter } from "@zimyo-api/adapters/legacy-sequelize";
const app = await createApp(expressAdapter({
port: 8082,
routeEntry: path.join(__dirname, "src/routes/index.js"),
appConfig: require("./src/config/server.config"),
utilModule: require("./system/util"),
srcDir: path.join(__dirname, "src"),
rootDir: __dirname,
database: legacySequelizeAdapter({ ... }),
auth: {
whitelistUrls: ["guest/login", "user/getToken"],
// authenticate: customAuthFunction, // or uses Login API by default
},
mocks: {
"../middlewares": (options) => mockMiddleware,
},
}));
await app.start();Your existing Express route files work unchanged — require('express') is intercepted and routed through Fastify.
Full Config Reference
createApp({
// ── Server ──
port: 3000, // default: 3000
host: "0.0.0.0", // default: "0.0.0.0"
basePath: "/api/v1", // prefix for all routes
bodyLimit: 5 * 1024 * 1024, // max request body (5MB)
logMode: "json", // "pretty" | "json" | "silent"
fastifyOptions: {}, // raw Fastify constructor options
// ── Routing (pick one) ──
routing: "file",
controllersDir: "./controllers",
// routing: "controller",
// routesDir: "./routes",
// routing: "express",
// routeEntry: "./src/routes/index.js",
// ── Database ──
database: adapter, // single adapter or array
// ── Auth ──
auth: { validateKey, publicUrls },
// ── Plugins ──
cors: true | { origin, credentials, ... },
helmet: true | { hsts, frameguard, ... },
rateLimit: true | { max, timeWindow, ... },
compress: true | { threshold, encodings, ... },
multipart: true | { limits, ... },
swagger: true | { title, routePrefix, ... },
monitoring: true | { routePrefix, maxDataPoints, maxErrors },
// ── Custom Setup ──
setup: async (fastify, ctx) => {
// Register custom plugins, hooks, decorators
},
});Graceful Shutdown
The framework automatically handles SIGTERM and SIGINT signals, draining active connections and closing database pools before exit.
Request ID
Every request gets a UUID (request.id) for log correlation. Override with fastifyOptions.genReqId.
Install Plugins
Only install the plugins you need:
# Security
pnpm add @fastify/cors @fastify/helmet @fastify/rate-limit
# Performance
pnpm add @fastify/compress
# File uploads
pnpm add @fastify/multipart
# API docs
pnpm add @fastify/swagger @fastify/swagger-ui
# Database (pick what you use)
pnpm add sequelize mysql2 # MySQL/Postgres via Sequelize
pnpm add drizzle-orm pg # Postgres via Drizzle
pnpm add ioredis # Redis
pnpm add mongodb # MongoDBLicense
MIT
