storieasy-logo

Next.js Server-side API: A Comprehensive Guide

milan

Milan Patel

29 Mar 2025

|

30 min to read

NextJS

NextJS

APIRouting

ServerSideRendering

WebDevelopment

ServerSideAPI

Next.js is a powerful React framework that enables server-side rendering (SSR), static site generation (SSG), and API routes. One of its standout features is the ability to create server-side APIs within the project itself.

Mastering Next.js Server-side API: Beginner to Advanced Guide


Next.js is a powerful React framework that simplifies building full-stack applications. One of its key features is the ability to create server-side APIs using the built-in API routes. In this guide, we'll explore Next.js API routes in-depth

Folder Structure

NextJS folder structure

NextJS folder structure

Explanation of Each Folder/File

  • /app/api/getUsers/route.tsHandles GET requests (fetch users)
  • /app/api/createUser/route.tsHandles POST requests (add a new user)
  • /app/api/updateUser/route.tsHandles PUT requests (update user details)
  • /app/api/deleteUser/route.tsHandles DELETE requests (remove user)
  • /app/users/page.tsxClient-side page to display, create, update, and delete users
  • /utils/apiHandler.tsClient-side API call functions (fetch, create, update, delete users)

1. Create Separate API Routes in Next.js (App Router)

GET API (Fetch Users)

  • app/api/getUsers/route.ts
1import { NextResponse } from "next/server";
2
3let users = [
4{ id: 1, name: "John Doe", email: "john@example.com" },
5{ id: 2, name: "Jane Doe", email: "jane@example.com" },
6];
7
8export async function handler() {
9return NextResponse.json({ success: true, users }, { status: 200 });
10}

POST API (Create a New User)

  • app/api/createUser/route.ts
1import { NextRequest, NextResponse } from "next/server";
2
3export async function handler(req: NextRequest) {
4try {
5const { name, email } = await req.json();
6if (!name || !email) {
7return NextResponse.json({ success: false, message: "Name and email are required" }, { status: 400 });
8}
9
10const newUser = { id: Date.now(), name, email };
11return NextResponse.json({ success: true, user: newUser }, { status: 201 });
12} catch (error) {
13return NextResponse.json({ success: false, message: "Server error" }, { status: 500 });
14}
15}

PUT API (Update a User)

  • app/api/updateUser/route.ts
1import { NextRequest, NextResponse } from "next/server";
2
3export async function handler(req: NextRequest) {
4try {
5const { id, name, email } = await req.json();
6if (!id || !name || !email) {
7return NextResponse.json({ success: false, message: "ID, Name, and Email are required" }, { status: 400 });
8}
9
10return NextResponse.json({ success: true, message: "User updated successfully" }, { status: 200 });
11} catch (error) {
12return NextResponse.json({ success: false, message: "Server error" }, { status: 500 });
13}
14}

DELETE API (Delete a User)

  • app/api/deleteUser/route.ts
1import { NextRequest, NextResponse } from "next/server";
2
3export async function handler(req: NextRequest) {
4try {
5const { searchParams } = new URL(req.url);
6const id = searchParams.get("id");
7
8if (!id) return NextResponse.json({ success: false, message: "User ID is required" }, { status: 400 });
9
10return NextResponse.json({ success: true, message: "User deleted successfully" }, { status: 200 });
11} catch (error) {
12return NextResponse.json({ success: false, message: "Server error" }, { status: 500 });
13}
14}

2. Create API Call Functions (Client-Side)

1export const API_BASE_URL = "/api";
2
3// GET: Fetch all users
4export const fetchUsers = async () => {
5const res = await fetch(`${API_BASE_URL}/getUsers`);
6return res.json();
7};
8
9//  POST: Add a new user
10export const createUser = async (name: string, email: string) => {
11const res = await fetch(`${API_BASE_URL}/createUser`, {
12method: "POST",
13headers: { "Content-Type": "application/json" },
14body: JSON.stringify({ name, email }),
15});
16return res.json();
17};
18
19//  PUT: Update a user
20export const updateUser = async (id: number, name: string, email: string) => {
21const res = await fetch(`${API_BASE_URL}/updateUser`, {
22method: "PUT",
23headers: { "Content-Type": "application/json" },
24body: JSON.stringify({ id, name, email }),
25});
26return res.json();
27};
28
29//  DELETE: Remove a user
30export const deleteUser = async (id: number) => {
31const res = await fetch(`${API_BASE_URL}/deleteUser?id=${id}`, { method: "DELETE" });
32return res.json();
33};

3. Use APIs in the Next.js Page (Client-Side)

1"use client";
2import { useState, useEffect } from "react";
3import { fetchUsers, createUser, updateUser, deleteUser } from "@/utils/apiHandler";
4
5export default function UsersPage() {
6const [users, setUsers] = useState([]);
7const [name, setName] = useState("");
8const [email, setEmail] = useState("");
9
10//  Load users when the page loads
11useEffect(() => {
12fetchUsers().then((data) => setUsers(data.users));
13}, []);
14
15//  Add a new user
16const handleAddUser = async () => {
17const data = await createUser(name, email);
18if (data.success) setUsers([...users, data.user]);
19setName("");
20setEmail("");
21};
22
23//  Update user
24const handleUpdateUser = async (id: number) => {
25const newName = prompt("New Name:", "");
26const newEmail = prompt("New Email:", "");
27if (!newName || !newEmail) return;
28
29const data = await updateUser(id, newName, newEmail);
30if (data.success) {
31setUsers(users.map((user) => (user.id === id ? { ...user, name: newName, email: newEmail } : user)));
32}
33};
34
35// Delete user
36const handleDeleteUser = async (id: number) => {
37if (confirm("Delete this user?")) {
38const data = await deleteUser(id);
39if (data.success) setUsers(users.filter((user) => user.id !== id));
40}
41};
42
43return (
44<div className="p-6">
45<h1 className="text-2xl font-bold mb-4">Users</h1>
46
47{/* Form to Add User */}
48<div className="mb-4">
49<input type="text" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} className="border p-2 mr-2" />
50<input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} className="border p-2 mr-2" />
51<button onClick={handleAddUser} className="bg-blue-500 text-white px-4 py-2">Add</button>
52</div>
53
54{/* User List */}
55<ul>
56{users.map((user) => (
57<li key={user.id} className="flex justify-between items-center border-b p-2">
58{user.name} - {user.email}
59<div>
60<button onClick={() => handleUpdateUser(user.id)} className="bg-yellow-500 text-white px-2 py-1 mr-2">Edit</button>
61<button onClick={() => handleDeleteUser(user.id)} className="bg-red-500 text-white px-2 py-1">Delete</button>
62</div>
63</li>
64))}
65</ul>
66</div>
67);
68}

4. Advanced Next.js API Techniques

Once you have basic CRUD APIs working, it’s time to level up with advanced techniques that make your API robust, maintainable, and production-ready.

4.1 Middleware for Authentication & Authorization

Protecting your APIs is crucial for any full-stack application. Next.js 14+ supports middleware in the App Router:

1// app/middleware.ts
2import { NextResponse } from "next/server";
3import type { NextRequest } from "next/server";
4
5export function middleware(req: NextRequest) {
6  const token = req.headers.get("authorization");
7
8  if (!token || token !== "your-secret-token") {
9    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
10  }
11
12  return NextResponse.next();
13}
14
15export const config = {
16  matcher: ["/api/createUser", "/api/updateUser", "/api/deleteUser"],
17};

Benefits:
✔ Secure sensitive endpoints
✔ Easy to manage across multiple API routes
✔ Centralized authentication logic

4.2 Handling Query Parameters in API Routes

Next.js allows dynamic API routes to handle specific data efficiently:

1// app/api/getUser/[id]/route.ts
2import { NextResponse } from "next/server";
3
4export async function GET(req: Request, { params }: { params: { id: string } }) {
5  const { id } = params;
6  const user = await getUserById(id); // Fetch user from database
7  if (!user) return NextResponse.json({ error: "User not found" }, { status: 404 });
8  return NextResponse.json(user);
9}

Use Cases:
✔ Fetch a single resource by ID
✔ Filter data dynamically
✔ Build scalable RESTful APIs

4.3 Error Handling & Status Codes

Proper error handling improves the reliability of your API:

1export async function POST(req: Request) {
2  try {
3    const body = await req.json();
4    if (!body.name || !body.email) {
5      return NextResponse.json({ error: "Name and Email required" }, { status: 400 });
6    }
7    const newUser = await createUser(body);
8    return NextResponse.json(newUser, { status: 201 });
9  } catch (error) {
10    return NextResponse.json({ error: "Server Error" }, { status: 500 });
11  }
12}

Benefits:
✔ Consistent API responses
✔ Helps front-end handle errors gracefully
✔ Enhances developer experience

4.4 Integrating with Databases

You can connect Next.js API routes to any database:

  • MongoDB: Using Mongoose or native MongoDB driver
  • PostgreSQL/MySQL: Using Prisma or Sequelize
  • Firebase: Serverless real-time database integration

Example: Connecting MongoDB with Next.js API

1import clientPromise from "@/lib/mongodb";
2import { NextResponse } from "next/server";
3
4export async function GET() {
5  try {
6    const client = await clientPromise;
7    const db = client.db("myDB");
8    const users = await db.collection("users").find({}).toArray();
9    return NextResponse.json(users);
10  } catch (err) {
11    return NextResponse.json({ error: "Database connection failed" }, { status: 500 });
12  }
13}

4.5 API Response Caching

For high-traffic applications, caching API responses reduces server load:

  • Use Cache-Control headers in API routes
  • Integrate Redis or in-memory caches for faster responses
1export async function GET(req: Request) {
2  const users = await fetchUsersFromDB();
3  return NextResponse.json(users, {
4    status: 200,
5    headers: {
6      "Cache-Control": "s-maxage=60, stale-while-revalidate=30",
7    },
8  });
9}

Benefits:
✔ Improves performance
✔ Reduces database queries
✔ Better user experience

4.6 SEO & API Performance Tips

To make your Next.js app SEO-friendly and fast:

  1. Use server-side rendering (SSR) for pages fetching API data.
  2. Enable ISR (Incremental Static Regeneration) where possible.
  3. Optimize database queries to reduce response time.
  4. Add Open Graph metadata dynamically using API data for better social sharing.
  5. Use lazy loading for images and heavy components.

5. Best Practices for Next.js API Development

  • Keep API routes modular and organized
  • Validate request payloads to avoid bad data
  • Use TypeScript for type safety
  • Handle errors and send meaningful status codes
  • Protect routes using authentication and middleware
  • Optimize performance using caching and database indexing
  • Write reusable API handler functions to simplify client-side calls

🔥 Done!

✅ API routes: getUsers, createUser, updateUser, deleteUser
✅ Uses proper status codes and error handling
✅ Client-side API calls are easy to manage
User-friendly UI with Tailwind

Newsletter

Subscribe to our newsletter and get our latest updates.

Share with your friends: