Building REST APIs

Build production-grade REST APIs in Next.js — versioning, error handling, authentication, rate limiting, and the patterns used in enterprise applications.

Last reviewed: January 2025

·Next.js 13Next.js 14Next.js 15
✅
Quick Understanding: Next.js Route Handlers can build full REST APIs. The key patterns: consistent error responses, input validation with Zod, JWT or session authentication, rate limiting, and API versioning. For external consumers, treat your Route Handlers as a proper API with versioning and documentation.

🔍 What Is It?

Building REST APIs in Next.js means creating Route Handlers that follow REST conventions:

  • →Resource-based URLs: /api/v1/products, /api/v1/products/[id]
  • →HTTP method semantics: GET (read), POST (create), PUT/PATCH (update), DELETE (delete)
  • →Consistent response format: { data, error, meta } structure
  • →Proper status codes: 200, 201, 400, 401, 403, 404, 422, 500
  • →Input validation: Zod schemas for request bodies and query params
  • →Authentication: JWT bearer tokens or session cookies

🎯 Why Do We Need It?

When your Next.js app serves mobile apps, third-party integrations, or public API consumers, you need a proper REST API — not just internal Server Component data fetching.

Next.js Route Handlers provide a clean way to build this API in the same project as your frontend, sharing types, validation schemas, and business logic.

🏗️ What Happens in Real Projects?

REST API structure in a SaaS product:

app/api/v1/
├── auth/
│   ├── login/route.ts
│   └── refresh/route.ts
├── users/
│   ├── route.ts          # GET /api/v1/users, POST /api/v1/users
│   └── [id]/route.ts     # GET/PUT/DELETE /api/v1/users/:id
└── products/
    ├── route.ts
    └── [id]/
        ├── route.ts
        └── reviews/route.ts  # /api/v1/products/:id/reviews

⚙️ How It Works

REST API request flow:

  1. Client sends GET /api/v1/products?category=tech
  2. Middleware checks rate limit and authentication
  3. Route Handler validates query params
  4. Data access layer fetches from DB
  5. Response formatted with consistent structure
  6. Returned with appropriate cache headers

💻 Example

Production REST API Patterns — Consistent error handling, validation, and auth patterns for a production API.

ts
// lib/api-response.ts — Shared response utilities
import { NextResponse } from 'next/server';

type ApiResponse<T> = {
  data?: T;
  error?: string;
  message?: string;
};

export function ok<T>(data: T, status = 200) {
  return NextResponse.json<ApiResponse<T>>({ data }, { status });
}

export function created<T>(data: T) {
  return NextResponse.json<ApiResponse<T>>({ data }, { status: 201 });
}

export function badRequest(error: string) {
  return NextResponse.json<ApiResponse<never>>({ error }, { status: 400 });
}

export function unauthorised() {
  return NextResponse.json<ApiResponse<never>>(
    { error: 'Unauthorised' }, { status: 401 }
  );
}

export function notFound(resource = 'Resource') {
  return NextResponse.json<ApiResponse<never>>(
    { error: `${resource} not found` }, { status: 404 }
  );
}

export function serverError(error: unknown) {
  console.error('[API Error]', error);
  return NextResponse.json<ApiResponse<never>>(
    { error: 'Internal server error' }, { status: 500 }
  );
}

🤔 Common Confusions

❓ Should I build a REST API in Next.js or use a separate backend?

Next.js for APIs that are tightly coupled to your frontend (same types, same auth, same deployment). Separate backend (NestJS, Express) when the API is consumed by many different clients, needs to scale independently, or has complex business logic that benefits from a dedicated service.

❓ How do I version my API?

Use URL versioning: /api/v1/products, /api/v2/products. In Next.js, create route group folders: app/api/(v1)/products/route.ts and app/api/(v2)/products/route.ts. Alternatively, use a header-based versioning approach with middleware.

❓ Can I generate OpenAPI documentation from Next.js Route Handlers?

Not automatically — Next.js does not generate OpenAPI specs. Use libraries like next-swagger-doc or manually maintain an openapi.yaml. For type-safe APIs, consider tRPC instead of REST — it generates types automatically.

🚨 Common Production Issues

Missing error handling — an unhandled exception in a Route Handler returns a 500 with a stack trace in development, or a generic error in production. Always wrap handlers in try/catch and return structured error responses.

No rate limiting — public API endpoints without rate limiting are vulnerable to abuse. Add rate limiting at the middleware level using Upstash Redis or similar.

Returning sensitive data — accidentally including password hashes, API keys, or internal IDs in API responses. Always use explicit select statements and DTO transformation.

✅ Best Practices

Use a consistent response format across all endpoints — { data, error, meta }

Validate all inputs with Zod — both body and query params

Use URL versioning from day one — /api/v1/ is much easier than retrofitting

Never return raw DB objects — always transform to DTOs to control what is exposed

Add rate limiting to all public endpoints

🏛️ Architect's Note

Senior Architect's Perspective

For enterprise APIs, consider whether Next.js Route Handlers are the right tool. They are excellent for BFF (Backend for Frontend) patterns — APIs designed specifically for your frontend. For general-purpose APIs consumed by multiple clients, a dedicated NestJS or Express service with OpenAPI documentation is more appropriate.

💬 Frequently Asked Questions

How do I implement pagination in a REST API?

Use cursor-based pagination for large datasets (more efficient) or offset-based for simpler cases. Return: { data: [...], pagination: { page, limit, total, nextCursor } }. Cursor-based: WHERE id > lastId ORDER BY id LIMIT n, which avoids the performance issues of OFFSET on large tables.

Should I use tRPC instead of REST for my Next.js API?

tRPC is excellent for full-stack TypeScript apps where the API is only consumed by your own frontend. It provides end-to-end type safety without code generation. Use REST when you need to support non-TypeScript clients, need OpenAPI documentation, or need a public API.

How do I handle CORS for my Next.js API?

Add CORS headers in the Route Handler response, or use middleware.ts to add headers to all /api/* routes. For development, configure the allowed origins list. Never use * in production for authenticated endpoints.

🎯 Key Takeaways

1Use consistent response format: { data, error } across all endpoints
2Validate all inputs with Zod — both request body and query params
3Version your API from day one: /api/v1/
4Never return raw DB objects — use DTOs to control exposed data
5Add rate limiting to all public endpoints
6Consider tRPC for type-safe internal APIs, REST for public/external APIs

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Next.js references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Next.js learning path

Browse the full Next.js topic collection and learning order on SplashRide.

Next.js Interview Prep

Practice Next.js interview questions and scenario-based answers.

Frontend Engineer Career Path

A beginner-friendly journey through web foundations, React, production frontend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

Route Handlers

Master Route Handlers in Next.js — the App Router replacement for API routes, supporting all HTTP methods, streaming responses, and edge runtime.

Middleware

Master Next.js Middleware — the edge function that runs before every request, enabling auth redirects, A/B testing, geolocation, and request modification at the CDN edge.