Chapter 6 of 11
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.
// 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)
);
}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,
});
}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" });
}"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>
);
}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*"],
};