How to Build Backend APIs with NitroJs?

Technical guide to NitroJs for backend APIs. Covers quick start, routing, caching, deployment, and real-world development scenarios for modern web backends.

Ashley Goolam

Ashley Goolam

19 January 2026

How to Build Backend APIs with NitroJs?

When I built my first production API, I used Express.js and the setup took about two hours: boilerplate, middleware, route handlers, TypeScript config, and deployment scripts. Recently, I shipped a complete REST API in only 20 minutes using NitroJs. No app.use() chains, no manual routing, no deployment headaches.

This guide shows you how NitroJs eliminates boilerplate while giving you a production-ready backend that runs anywhere, Node, Bun, Deno, serverless, or edge.

What Is NitroJs and Why Use It for Backend APIs?

NitroJs is a server toolkit from the UnJS ecosystem that treats your backend as a build artifact. Instead of writing a server that couples to a runtime, you write route handlers and Nitro compiles them into a portable bundle. Deploy the same code to Node.js, Cloudflare Workers, Vercel, or a Docker container without changing a line.

NitroJs for Backend APIs

Core mental model: Write routes, not servers. You focus on API logic; Nitro handles HTTP, middleware, and platform glue.

Key advantages for backend APIs:

For API developers, this means you ship faster and refactor less when changing infrastructure.

button

Getting Started: Install NitroJs in 5 Minutes

Step 1: Scaffold a Project

# Using pnpm (recommended)
pnpm dlx giget@latest nitro nitro-api-backend

# Or npm
npm create nitro@latest nitro-api-backend

# Or bun
bunx giget@latest nitro nitro-api-backend

This clones the official starter template into nitro-api-backend.

Step 2: Explore the Structure

cd nitro-api-backend
tree -L 2

Output:

├── server
│   ├── api
│   ├── routes
│   ├── tasks
│   └── middleware
├── nitro.config.ts
├── package.json
└── tsconfig.json

Critical directories:

For a pure backend API, ignore routes and put everything in api.

Step 3: Run Development Server

pnpm install
pnpm dev

Nitro starts on http://localhost:3000 with hot reload. Edit a file, and the server restarts in under 200ms.

getting started with NitroJs

Building Your First API Endpoint

Simple GET Handler

Create server/api/users.get.ts:

// server/api/users.get.ts
export default defineEventHandler(() => {
  return [
    { id: 1, name: "Alice", email: "alice@example.com" },
    { id: 2, name: "Bob", email: "bob@example.com" }
  ];
});

Nitro automatically serves this at GET /api/users. The defineEventHandler wrapper provides type safety and access to the request context.

Dynamic Route with Parameters

Create server/api/users/[id].get.ts:

// server/api/users/[id].get.ts
export default defineEventHandler((event) => {
  const id = getRouterParam(event, 'id');
  
  if (!id) {
    throw createError({
      statusCode: 400,
      statusMessage: 'User ID is required'
    });
  }
  
  return {
    id: Number(id),
    name: `User ${id}`,
    email: `user${id}@example.com`
  };
});

Access GET /api/users/42 to see { id: 42, name: "User 42", ... }.

POST with JSON Body

Create server/api/users.post.ts:

// server/api/users.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event);
  
  // Nitro parses JSON automatically
  const { name, email } = body;
  
  if (!name || !email) {
    throw createError({
      statusCode: 422,
      statusMessage: 'Name and email are required'
    });
  }
  
  // Simulate database insertion
  const newUser = {
    id: Math.floor(Math.random() * 1000),
    name,
    email,
    createdAt: new Date().toISOString()
  };
  
  return newUser;
});

Test it:

curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Charlie","email":"charlie@example.com"}'

Type-Safe Responses

Nitro infers response types from your return values. For strict contracts, use explicit types:

// server/api/products.get.ts
interface Product {
  id: number;
  name: string;
  price: number;
  inStock: boolean;
}

export default defineEventHandler<Product[]>(() => {
  return [
    { id: 1, name: "Laptop", price: 1299.99, inStock: true },
    { id: 2, name: "Mouse", price: 29.99, inStock: false }
  ];
});

Now clients importing this endpoint get full IntelliSense.

Advanced API Patterns

Middleware for Auth

Create server/middleware/auth.ts:

// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
  const protectedRoutes = ['/api/admin', '/api/users/me'];
  
  if (protectedRoutes.some(route => event.path.startsWith(route))) {
    const token = getHeader(event, 'authorization')?.replace('Bearer ', '');
    
    if (!token || token !== process.env.API_SECRET) {
      throw createError({
        statusCode: 401,
        statusMessage: 'Unauthorized'
      });
    }
  }
});

Middleware runs before every route. Add it to nitro.config.ts:

// nitro.config.ts
export default defineNitroConfig({
  srcDir: 'server',
  runtimeConfig: {
    apiSecret: process.env.API_SECRET
  }
});

Caching API Responses

Cache expensive computations for 60 seconds:

// server/api/stats.get.ts
export default defineCachedEventHandler(async () => {
  const db = useStorage('data');
  const users = await db.getItem('users') || [];
  
  return {
    totalUsers: users.length,
    activeUsers: users.filter(u => u.lastSeen > Date.now() - 86400000).length
  };
}, {
  maxAge: 60, // seconds
  name: 'stats',
  getKey: () => 'global-stats'
});

Nitro handles cache invalidation and storage (memory, Redis, or Cloudflare KV based on deployment target).

File Upload Handling

// server/api/upload.post.ts
export default defineEventHandler(async (event) => {
  const files = await readMultipartFormData(event);
  
  if (!files?.length) {
    throw createError({ statusCode: 400, statusMessage: 'No files uploaded' });
  }
  
  const storage = useStorage('uploads');
  const file = files[0];
  
  await storage.setItem(file.filename, file.data);
  
  return { success: true, filename: file.filename };
});

Test with:

curl -X POST http://localhost:3000/api/upload \
  -F "file=@/path/to/image.jpg"

Deployment: From Local to Production

Build for Production

pnpm build

Nitro generates a .output directory with platform-specific entry points.

Run on Node.js

node .output/server/index.mjs

No node_modules required—the build bundles everything.

Deploy to Cloudflare Workers

# Install preset
pnpm add -D nitro-preset-cloudflare-workers

# Build with preset
NITRO_PRESET=cloudflare-workers pnpm build

# Deploy
wrangler deploy .output

Deploy to Vercel (Serverless)

# Vercel detects Nitro automatically
vercel

Or configure nitro.config.ts:

export default defineNitroConfig({
  preset: 'vercel-edge'
});

Environment Variables

Create .env locally, then use platform dashboards in production:

# .env
API_SECRET=dev-secret-key
DATABASE_URL=file:./dev.db

Access in handlers:

const config = useRuntimeConfig();
const secret = config.apiSecret;

Advanced Use Cases

WebSockets & Server-Sent Events

Nitro supports WebSockets via defineWebSocketHandler:

// server/api/ws.ts
export default defineWebSocketHandler({
  open(peer) {
    console.log('WebSocket connected:', peer);
  },
  message(peer, message) {
    peer.send(`Echo: ${message.text()}`);
  },
  close(peer, details) {
    console.log('WebSocket closed:', details);
  }
});

Access at ws://localhost:3000/api/ws.

Background Tasks

Create server/tasks/cleanup.ts:

// server/tasks/cleanup.ts
export default defineCronHandler('0 2 * * *', async () => {
  const db = useStorage('temp');
  const keys = await db.getKeys();
  
  for (const key of keys) {
    const metadata = await db.getMeta(key);
    if (metadata.expiresAt < Date.now()) {
      await db.removeItem(key);
    }
  }
  
  console.log('Cleanup completed');
});

This runs daily at 2 AM. Deploy to a platform with cron support (e.g., Vercel, Netlify).

Workflow Engines

Integrate with useWorkflow.dev for complex orchestration:

// server/api/workflows/process.ts
import { createWorkflow } from '@useworkflow/sdk';

export default defineEventHandler(async (event) => {
  const workflow = createWorkflow('data-pipeline', {
    steps: [
      { id: 'fetch', run: 'https://api.example.com/data' },
      { id: 'transform', run: 'transform.js' },
      { id: 'store', run: async (data) => {
        const storage = useStorage('results');
        await storage.setItem('latest', data);
      }}
    ]
  });
  
  await workflow.start();
  return { status: 'started' };
});

Real Developer Scenarios

Scenario 1: Static Site Backend

Deploy a Nitro API to Cloudflare Workers that serves JSON data to a static JAMstack site. The free tier handles 100K requests/day.

Scenario 2: Mobile App API

Build REST endpoints for a React Native app. Use Nitro’s built-in CORS handling:

// nitro.config.ts
export default defineNitroConfig({
  routeRules: {
    '/api/**': { cors: true }
  }
});

Scenario 3: Microservices Gateway

Run Nitro as an API gateway that aggregates microservices. Use $fetch to proxy requests:

// server/api/aggregate.get.ts
export default defineEventHandler(async (event) => {
  const [users, posts] = await Promise.all([
    $fetch('https://users-service.internal/users'),
    $fetch('https://posts-service.internal/posts')
  ]);
  
  return { users, posts };
});

Test Your NitroJs APIs with Apidog

Nitro generates backends fast, but speed means nothing if your endpoints break. When you ship a new POST /api/users, import the route definition into Apidog. It reverse-engineers your handler’s return type and generates contract tests automatically.

API Contract Testing with Apidog
button

Run them in CI to catch breaking changes before your frontend team pulls their hair out. It’s free to start, and it’s the guardrail your rapid Nitro development needs.

What is API Contract Testing and How to Use Apidog for API Contract Testing
API contract testing verifies that your API implementation matches its documented contract. Learn how to use Apidog to validate response schemas, run contract tests, and generate mock servers for reliable API integration.

Frequently Asked Questions

1. Can Nitro replace Express.js completely?
Yes. Nitro handles routing, middleware, and deployment better. Migrate by moving Express middleware to Nitro plugins and routes to server/api/.

2. How does Nitro handle database connections?
Use Nitro plugins to initialize pools on startup. The storage layer abstracts caching, but for SQL, bring your own ORM (Prisma, Drizzle).

3. Is Nitro production-ready?
Absolutely. Nuxt uses Nitro for millions of production sites. It’s battle-tested for scale.

4. Can I use Nitro with GraphQL?
Yes. Add a GraphQL handler in server/api/graphql.post.ts. Use any Node GraphQL library—Nitro doesn’t restrict you.

5. What’s the difference between Nitro and Hono?
Hono is a lightweight router. Nitro is a full server toolkit with builds, presets, and storage. Use Hono for microservices, Nitro for full backends.

Conclusion

NitroJs rethinks backend development by compiling your API into a portable artifact. You get filesystem routing, TypeScript safety, and universal deployment without boilerplate. Build your next REST API with Nitro, and validate it with Apidog. Your future self will thank you when you migrate from Node to the edge in one config line.

button

Explore more

Eigent AI: The Open-Source Claude Cowork Alternative

Eigent AI: The Open-Source Claude Cowork Alternative

Eigent AI is an open-source, local-first AI cowork app built around multi-agent workflows. This guide explains how it works, how to use it, and when it outperforms Claude.

19 January 2026

Awesome Claude Code Skills for Coding & Development

Awesome Claude Code Skills for Coding & Development

This guide explores Claude Code Skills for coding and development, showing how to install, use, and integrate them into real workflows—from code reviews to API testing—while boosting productivity with tools like Apidog.

16 January 2026

Awesome Claude Code Skills for Document Processing

Awesome Claude Code Skills for Document Processing

A technical guide to Claude Code Skills for document processing, covering Word, PDF, PowerPoint, and Excel automation with practical integration examples across Claude.ai, CLI, and API.

16 January 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs