You build a Node.js API. Then marketing wants it on Cloudflare Workers. Then the enterprise client needs it on AWS Lambda. You rewrite the deployment layer three times.
What if you wrote your server once and deployed it to ANY provider — Node.js, Deno, Bun, Cloudflare Workers, AWS Lambda, Vercel, Netlify — with zero code changes?
That's Nitro. The server engine behind Nuxt, now available as a standalone framework.
Quick Start
npx giget@latest nitro my-server
cd my-server && npm install && npm run dev
File-Based API Routes
server/
routes/
index.ts ← GET /
users/
index.ts ← GET /users
[id].ts ← GET /users/:id
index.post.ts ← POST /users
middleware/
auth.ts ← runs before every request
plugins/
database.ts ← server lifecycle hooks
// server/routes/users/[id].ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, "id");
const user = await db.users.findUnique({ where: { id } });
if (!user) {
throw createError({ statusCode: 404, message: "User not found" });
}
return user;
});
// server/routes/users/index.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event);
const user = await db.users.create({ data: body });
return user;
});
Deploy Anywhere — Zero Code Changes
# Node.js server
NITRO_PRESET=node npx nitropack build
# Cloudflare Workers
NITRO_PRESET=cloudflare-module npx nitropack build
# AWS Lambda
NITRO_PRESET=aws-lambda npx nitropack build
# Vercel Edge
NITRO_PRESET=vercel-edge npx nitropack build
# Deno Deploy
NITRO_PRESET=deno-deploy npx nitropack build
# Bun
NITRO_PRESET=bun npx nitropack build
# Static (pre-rendered)
NITRO_PRESET=static npx nitropack build
Same source code. 15+ deployment targets. Nitro adapts your server to each platform's runtime automatically.
Built-in Features
Caching
export default defineCachedEventHandler(async (event) => {
// This response is cached for 60 seconds
const data = await expensiveQuery();
return data;
}, {
maxAge: 60,
staleMaxAge: 600, // Serve stale for 10min while revalidating
});
Scheduled Tasks (Cron)
// server/tasks/cleanup.ts
export default defineTask({
meta: { description: "Clean up expired sessions" },
run: async () => {
await db.sessions.deleteMany({ where: { expiresAt: { lt: new Date() } } });
return { result: "Cleaned up expired sessions" };
},
});
// nitro.config.ts
export default defineNitroConfig({
scheduledTasks: {
"*/30 * * * *": ["cleanup"], // Every 30 minutes
},
});
KV Storage (Universal)
export default defineEventHandler(async () => {
const storage = useStorage("cache");
// Works with memory, Redis, Cloudflare KV, S3 — same API
await storage.setItem("key", { data: "value" });
const value = await storage.getItem("key");
return value;
});
WebSockets
// server/routes/_ws.ts
export default defineWebSocketHandler({
open(peer) {
peer.send("Welcome!");
peer.subscribe("chat");
},
message(peer, message) {
peer.publish("chat", message.text());
},
close(peer) {
peer.unsubscribe("chat");
},
});
When to Choose Nitro
Choose Nitro when:
- You need to deploy the same API to multiple providers
- You want file-based routing without a full-stack framework
- Built-in caching, storage, and WebSocket support matters
- You're building microservices or API-only backends
Skip Nitro when:
- You need a full-stack framework with SSR (use Nuxt, which uses Nitro internally)
- You only target one platform (Express or Hono might be simpler)
- You need a large ecosystem of middleware (Express has more)
The Bottom Line
Nitro is infrastructure-agnostic by design. Write your server logic once, deploy it everywhere — from a $5 VPS to Cloudflare's edge network. The platform is a configuration detail, not a rewrite.
Start here: nitro.build
Need custom data extraction, scraping, or automation? I build tools that collect and process data at scale — 78 actors on Apify Store and 265+ open-source repos. Email me: Spinov001@gmail.com | My Apify Actors
Top comments (0)