Skip to main content

Command Palette

Search for a command to run...

Next.js API Routes Building Server-Side Functionality

Updated
5 min read
Next.js API Routes Building Server-Side Functionality

Next.js API Routes enable the creation of standalone server-side functionality within a Next.js application, capable of handling HTTP requests and returning JSON data or other responses. API routes reside in the pages/api directory, with each file mapping to a specific API endpoint.

Basic Example

pages/api/users.js

import type { NextApiRequest, NextApiResponse } from 'next';

// Get user list
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'GET') {
    const users = [
      { id: 1, name: 'User 1' },
      { id: 2, name: 'User 2' },
      { id: 3, name: 'User 3' },
    ];

    res.status(200).json(users);
  } else if (req.method === 'POST') {
    const user = req.body;

    // Simulate database connection
    // await addUserToDatabase(user);

    res.status(201).json({ message: 'User added successfully.' });
  } else {
    res.setHeader('Allow', ['GET', 'POST']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}
  1. The pages/api/users.js file defines an API route accessible at /api/users.

  2. The handler function accepts two parameters: req (a NextApiRequest object representing the HTTP request) and res (a NextApiResponse object for the HTTP response).

  3. For GET requests, it returns a user list (hardcoded here, but typically queried from a database in practice).

  4. For POST requests, it accepts user data from the request body, simulates adding it to a database, and returns a success message.

  5. For unsupported methods, it returns a 405 Method Not Allowed error with allowed methods specified.

Next.js API Routes handle JSON responses by default, but you can return other content types as needed. For example, use res.send to return HTML.

Middleware and Request Handling Chain

Next.js API Routes support a middleware pattern, allowing preprocessing of requests or post-processing of responses before reaching the final handler. This is useful for validating headers, authentication, logging, etc.

Middleware Example

To validate an API key for all API requests:

// pages/api/middleware/authenticate.ts
export function authenticate(req: NextApiRequest, res: NextApiResponse, next: () => void) {
  const apiKey = req.headers['x-api-key'];

  if (!apiKey || apiKey !== process.env.API_KEY) {
    return res.status(401).json({ message: 'Unauthorized' });
  }

  next();
}

Apply the middleware in an API route:

// pages/api/users.js
import { authenticate } from './middleware/authenticate';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  authenticate(req, res, () => {
    // Original logic
  });
}

Error Handling

Robust error handling is critical for production-grade applications. Next.js API Routes allow custom error handling logic.

Error Handling Example

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    // Code that might throw an error
    const result = await fetchDataFromDatabase();

    res.status(200).json(result);
  } catch (error) {
    console.error('Error occurred:', error);
    res.status(500).json({ error: 'An error occurred while processing your request.' });
  }
}

Type Safety

Using TypeScript for type annotations enhances code robustness and maintainability.

Type Safety Example

// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next';

type User = {
  id: number;
  name: string;
};

export default async function handler(req: NextApiRequest, res: NextApiResponse<User[] | { message: string }>) {
  // ...
}

Interacting with External Services

Most API routes interact with external services like databases or third-party APIs. Here’s how to use axios for HTTP requests.

Install axios:

npm install axios

Use in an API route:

import axios from 'axios';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    const response = await axios.get('https://api.example.com/data');
    res.status(200).json(response.data);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch data from external service.' });
  }
}

Custom and Dynamic Routes

Next.js API Routes support more than single paths, allowing complex routing structures, including dynamic routes.

Custom Routes

To organize related API endpoints, use subdirectories. For a blog API, you might structure it as:

pages/
  api/
    blog/
      posts.ts          # Handles /api/blog/posts requests
      post/[id].ts      # Dynamic route, handles /api/blog/post/:id requests

Dynamic Routes

Dynamic routes capture URL segments as parameters. In the above example, [id] is a dynamic segment replaced by an actual ID. Access these parameters via req.query.

Dynamic Route Example (pages/api/blog/post/[id].ts)

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { id } = req.query; // Get dynamic ID

  if (!id) {
    return res.status(400).json({ message: 'Missing post ID' });
  }

  try {
    const post = await getPostById(id as string); // Assume this fetches a post from a database
    if (!post) {
      return res.status(404).json({ message: 'Post not found' });
    }
    return res.status(200).json(post);
  } catch (error) {
    console.error('Error fetching post:', error);
    return res.status(500).json({ message: 'Internal server error' });
  }
}

API Route Caching

To improve performance, you may want to cache API responses. Next.js doesn’t provide built-in API caching, but you can use client-side libraries like swr or server-side caching with services like Redis.

Server-Side Caching Example (Using Redis)

Install redis and ioredis:

npm install redis ioredis

Use Redis to cache data in an API route:

import redis from 'ioredis';

const redisClient = new redis(process.env.REDIS_URL);

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { id } = req.query;

  let post;
  try {
    // Try fetching from Redis cache
    post = await redisClient.get(`post:${id}`);
    if (post) {
      post = JSON.parse(post);
      return res.status(200).json(post);
    }
  } catch (err) {
    console.error('Redis error:', err);
  }

  // Fetch from database if not cached
  post = await getPostById(id as string);

  if (post) {
    // Store in Redis for future requests
    redisClient.set(`post:${id}`, JSON.stringify(post));
    res.status(200).json(post);
  } else {
    res.status(404).json({ message: 'Post not found' });
  }
}

CORS Support

Cross-Origin Resource Sharing (CORS) is a key aspect of web security. Next.js API Routes support CORS by default, but you can further control CORS policies.

CORS Example

import Cors from 'cors'; // Install cors library

// Initialize CORS middleware
const cors = Cors({
  methods: ['GET', 'HEAD'],
});

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  // Apply CORS middleware
  await new Promise((resolve, reject) => {
    cors(req, res, (result) => {
      if (result instanceof Error) {
        reject(result);
      } else {
        resolve(result);
      }
    });
  });

  // Subsequent handling logic
}

Web Development

Part 6 of 46

The content covers the three basics of HTML/CSS/JS/TS, modular development, mainstream frameworks (Vue, React, Angular, Svelte), build tools, browser plug-in development, Node.js backend practice, Next.js/Nest.js and other popular technologies.

Up next

Web Components and Framework Integration Vue & React Case Study

For a long time, I dreamed of building a fully customizable, reusable, and cross-framework UI component library to streamline my project development. By chance, I discovered Web Components, a native Web API that allows the creation of custom HTML tag...

More from this blog

T

Tianya School Technical Articles

48 posts

Welcome to our tech publication, where we share high-quality content on full-stack development, frontend/backend/web3/AI, and engineering best practices.