Next.js Server-side API: A Comprehensive Guide
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
Explanation of Each Folder/File
- /app/api/getUsers/route.ts → Handles GET requests (fetch users)
- /app/api/createUser/route.ts → Handles POST requests (add a new user)
- /app/api/updateUser/route.ts → Handles PUT requests (update user details)
- /app/api/deleteUser/route.ts → Handles DELETE requests (remove user)
- /app/users/page.tsx → Client-side page to display, create, update, and delete users
- /utils/apiHandler.ts → Client-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:
- Use server-side rendering (SSR) for pages fetching API data.
- Enable ISR (Incremental Static Regeneration) where possible.
- Optimize database queries to reduce response time.
- Add Open Graph metadata dynamically using API data for better social sharing.
- 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
