NelsonLabs

API Routes

Next.js lets you build backend API endpoints right inside your project. These are server-side functions that receive HTTP requests and return data — without needing a separate backend server.

ANALOGY

Real-world analogy: A vending machine. An API route is like a vending machine. You press a button (make a request), the machine processes your selection internally (runs your code), and returns your item (sends back JSON data). You never see what happens inside — just send a request, get a response.

Creating your first API route

app/api/courses/route.js — accessible at /api/courses
js
// Named exports correspond to HTTP methods
// GET /api/courses → returns list of courses
export async function GET(request) {
  const courses = [
    { id: 1, title: "HTML Fundamentals", level: "Beginner" },
    { id: 2, title: "CSS Styling",       level: "Beginner" },
    { id: 3, title: "JavaScript Basics", level: "Beginner" },
  ];

  return Response.json(courses);
  // Automatically sets Content-Type: application/json
}

// POST /api/courses → creates a new course
export async function POST(request) {
  const body = await request.json();  // Parse the JSON request body

  // In a real app you would save to a database here
  console.log("Creating course:", body.title);

  return Response.json(
    { message: "Course created successfully", course: body },
    { status: 201 }  // 201 = Created (HTTP status code)
  );
}

Reading query parameters

app/api/search/route.js — GET /api/search?q=javascript
js
export async function GET(request) {
  // Extract query parameters from the URL
  const { searchParams } = new URL(request.url);
  const query = searchParams.get("q");      // "javascript"
  const page  = searchParams.get("page") || "1";

  // In a real app: query your database
  const results = await db.search(query, parseInt(page));

  return Response.json({
    query,
    page: parseInt(page),
    results,
  });
}

Dynamic API routes

app/api/courses/[id]/route.js — GET /api/courses/42
js
export async function GET(request, { params }) {
  const { id } = await params;

  // In a real app: look this up in a database
  const course = await db.findCourse(id);

  if (!course) {
    return Response.json(
      { error: "Course not found" },
      { status: 404 }  // 404 = Not Found
    );
  }

  return Response.json(course);
}

export async function DELETE(request, { params }) {
  const { id } = await params;
  await db.deleteCourse(id);
  return Response.json({ message: "Course deleted" });
}

Using your API from a Client Component

components/CreateCourseForm.jsx
jsx
"use client";
import { useState } from "react";

export default function CreateCourseForm() {
  const [title,   setTitle]   = useState("");
  const [message, setMessage] = useState("");

  async function handleSubmit() {
    const res = await fetch("/api/courses", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ title }),
    });

    const data = await res.json();
    setMessage(data.message);
  }

  return (
    <div>
      <input
        value={title}
        onChange={e => setTitle(e.target.value)}
        placeholder="Course title"
      />
      <button onClick={handleSubmit}>Create Course</button>
      {message && <p>{message}</p>}
    </div>
  );
}

Middleware — run code before every request

middleware.js — in the project root
js
import { NextResponse } from "next/server";

export function middleware(request) {
  const token = request.cookies.get("auth-token");

  // If someone tries to access /dashboard without a token, send them to /login
  if (request.nextUrl.pathname.startsWith("/dashboard")) {
    if (!token) {
      return NextResponse.redirect(new URL("/login", request.url));
    }
  }

  return NextResponse.next(); // Allow the request through
}

// Specify which routes this middleware applies to
export const config = {
  matcher: ["/dashboard/:path*", "/admin/:path*"],
};