Why Build an API in Next.js?

Next.js allows you to create server-side code that runs in a serverless environment. This means you can build a complete backend for your application—for handling form submissions, interacting with a database, or integrating with third-party services—without ever leaving your Next.js project.

1. The Pages Router: API Routes

In the Pages Router, any file inside the pages/api directory is treated as an API endpoint instead of a React page.

  • File-based Routing: Just like pages, the file structure maps to the URL.
  • pages/api/hello.js → /api/hello
  • pages/api/users/[id].js → /api/users/123
  • The Handler Function: Each file exports a default function that receives req (request) and res (response) objects, similar to Node.js/Express.

Code Example (pages/api/users.js):

JavaScript


// This function handles all requests to /api/users
export default function handler(req, res) {
  if (req.method === 'GET') {
    // Handle GET request
    const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
    res.status(200).json(users);
  } else if (req.method === 'POST') {
    // Handle POST request
    const newUser = req.body.name;
    // ... logic to save the new user to a database ...
    res.status(201).json({ message: 'User created!', name: newUser });
  } else {
    // Handle other methods
    res.setHeader('Allow', ['GET', 'POST']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

2. The App Router: Route Handlers

The App Router introduces a more modern approach with Route Handlers. Instead of a single handler function, you create a route.js (or .ts) file and export named functions corresponding to the HTTP methods you want to handle.

  • Folder-based Routing: Like pages, folders define the route. The endpoint itself is a route.js file.
  • app/api/hello/route.js → /api/hello
  • Named Export Functions: You export async functions named GET, POST, PUT, DELETE, etc.
  • Modern Request/Response: These functions use the standard Web Request and Response APIs.

Code Example (app/api/users/route.js):

JavaScript


import { NextResponse } from 'next/server';

// In-memory data for demonstration
let users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];

// Handles GET requests to /api/users
export async function GET(request) {
  return NextResponse.json(users);
}

// Handles POST requests to /api/users
export async function POST(request) {
  const { name } = await request.json(); // Parse the request body
  const newUser = { id: Date.now(), name };

  // ... logic to save the new user to a database ...
  users.push(newUser);

  return NextResponse.json({ message: 'User created', user: newUser }, { status: 201 });
}

Dynamic API Routes

Both routers support dynamic segments for creating endpoints that accept parameters, like an ID.

App Router Example (app/api/users/[id]/route.js): The params object is passed as the second argument to your handler.

JavaScript


import { NextResponse } from 'next/server';

// Handles GET requests to /api/users/1, /api/users/2, etc.
export async function GET(request, { params }) {
  const userId = params.id; // Access the dynamic 'id' parameter
  // ... logic to find the user by id from a database ...
  const user = { id: userId, name: 'Example User' };

  if (!user) {
    return NextResponse.json({ error: 'User not found' }, { status: 404 });
  }

  return NextResponse.json(user);
}