Module 15 — API Routes and Route Handlers
API Routes and Route Handlers are one of the most important parts of full-stack Next.js development.
They allow you to build backend HTTP endpoints directly inside your Next.js application.
You can use them for:
- Creating APIs
- Reading database records
- Creating users
- Updating resources
- Deleting records
- Authentication
- Form submissions
- File uploads
- Connecting external services
- Validating requests
- Returning structured errors
The basic architecture is:
1Client 2 ↓ 3HTTP Request 4 ↓ 5Route Handler 6 ↓ 7Authentication 8 ↓ 9Validation 10 ↓ 11Business Logic 12 ↓ 13Database / External API 14 ↓ 15HTTP Response 16 ↓ 17Client
1. What Is a Route Handler?
A Route Handler is a special route.ts or route.js file inside the Next.js app directory.
For example:
1app/ 2└── api/ 3 └── courses/ 4 └── route.ts
This creates an API endpoint:
1/api/courses
The file can define different HTTP methods:
1GET 2POST 3PUT 4PATCH 5DELETE
For example:
1import { NextResponse } from "next/server"; 2 3export async function GET() { 4 const courses = [ 5 { 6 id: 1, 7 title: "Next.js Fundamentals", 8 }, 9 { 10 id: 2, 11 title: "Advanced Next.js", 12 }, 13 ]; 14 15 return NextResponse.json(courses); 16}
Now requesting:
1GET /api/courses
returns:
1[ 2 { 3 "id": 1, 4 "title": "Next.js Fundamentals" 5 }, 6 { 7 "id": 2, 8 "title": "Advanced Next.js" 9 } 10]
2. Route Handler Architecture
Think of a Route Handler as a small backend controller.
1app/api/courses/route.ts 2 │ 3 ▼ 4 HTTP Request 5 │ 6 ▼ 7 Route Handler 8 │ 9 ┌────┴────┐ 10 │ │ 11 GET POST 12 │ │ 13 ▼ ▼ 14 Read Create 15 │ │ 16 └────┬────┘ 17 ▼ 18 Response
This is particularly useful when your Next.js application needs both frontend and backend functionality.
3. GET — Reading Data
GET is normally used to retrieve data.
Example:
1import { NextResponse } from "next/server"; 2 3export async function GET() { 4 const courses = await getCourses(); 5 6 return NextResponse.json(courses); 7}
The request:
1GET /api/courses
might return:
1[ 2 { 3 "id": "1", 4 "title": "Next.js" 5 }, 6 { 7 "id": "2", 8 "title": "React" 9 } 10]
The architecture is:
1GET /api/courses 2 ↓ 3Route Handler 4 ↓ 5getCourses() 6 ↓ 7Database 8 ↓ 9Courses 10 ↓ 11JSON Response
4. POST — Creating Data
POST is commonly used to create a new resource.
For example:
1import { NextResponse } from "next/server"; 2 3export async function POST(request: Request) { 4 const body = await request.json(); 5 6 const course = await createCourse(body); 7 8 return NextResponse.json(course, { 9 status: 201, 10 }); 11}
The client might send:
1{ 2 "title": "Advanced Next.js", 3 "description": "Learn advanced Next.js" 4}
The request flow is:
1Client 2 ↓ 3POST /api/courses 4 ↓ 5Request Body 6 ↓ 7Route Handler 8 ↓ 9Create Course 10 ↓ 11Database 12 ↓ 13201 Created
5. PUT — Replacing a Resource
PUT is generally used when replacing an existing resource.
For example:
1PUT /api/courses/123
Route:
1app/ 2└── api/ 3 └── courses/ 4 └── [id]/ 5 └── route.ts
Example:
1export async function PUT( 2 request: Request, 3 context: { 4 params: Promise<{ id: string }>; 5 } 6) { 7 const { id } = await context.params; 8 9 const body = await request.json(); 10 11 const course = await replaceCourse(id, body); 12 13 return Response.json(course); 14}
Conceptually:
1PUT 2 ↓ 3Replace existing resource
6. PATCH — Partially Updating Data
PATCH is commonly used when only part of a resource needs to change.
Suppose we have:
1{ 2 "id": "123", 3 "title": "Next.js", 4 "published": false 5}
We only want to change:
1{ 2 "published": true 3}
We can send:
1PATCH /api/courses/123
Example:
1export async function PATCH( 2 request: Request, 3 context: { 4 params: Promise<{ id: string }>; 5 } 6) { 7 const { id } = await context.params; 8 9 const body = await request.json(); 10 11 const course = await updateCourse(id, body); 12 13 return Response.json(course); 14}
Remember:
1PUT 2 ↓ 3Replace resource 4 5PATCH 6 ↓ 7Partially update resource
7. DELETE — Removing Data
DELETE is used to remove a resource.
Example:
1export async function DELETE( 2 request: Request, 3 context: { 4 params: Promise<{ id: string }>; 5 } 6) { 7 const { id } = await context.params; 8 9 await deleteCourse(id); 10 11 return new Response(null, { 12 status: 204, 13 }); 14}
The request:
1DELETE /api/courses/123
can remove course 123.
The flow is:
1DELETE Request 2 ↓ 3Authentication 4 ↓ 5Authorization 6 ↓ 7Find Course 8 ↓ 9Delete Course 10 ↓ 11204 No Content
8. Request Body
When sending JSON to a Route Handler, you can read the request body using:
1const body = await request.json();
Example:
1export async function POST(request: Request) { 2 const body = await request.json(); 3 4 console.log(body); 5 6 return Response.json({ 7 success: true, 8 }); 9}
If the client sends:
1{ 2 "title": "Next.js", 3 "price": 499 4}
you can access:
1const title = body.title; 2const price = body.price;
A complete example:
1export async function POST(request: Request) { 2 const body = await request.json(); 3 4 const course = await createCourse({ 5 title: body.title, 6 price: body.price, 7 }); 8 9 return Response.json(course, { 10 status: 201, 11 }); 12}
9. Never Trust Request Data
Everything coming from the browser should be considered untrusted input.
A malicious client could send:
1{ 2 "title": 123, 3 "price": "free", 4 "admin": true 5}
Therefore, validate incoming data before using it.
Bad:
1export async function POST(request: Request) { 2 const body = await request.json(); 3 4 await createCourse(body); 5 6 return Response.json({ 7 success: true, 8 }); 9}
Better:
1export async function POST(request: Request) { 2 const body = await request.json(); 3 4 if ( 5 typeof body.title !== "string" || 6 body.title.length < 3 7 ) { 8 return Response.json( 9 { 10 error: "Invalid title", 11 }, 12 { 13 status: 400, 14 } 15 ); 16 } 17 18 const course = await createCourse(body); 19 20 return Response.json(course, { 21 status: 201, 22 }); 23}
10. Query Parameters
Query parameters are values placed after ? in a URL.
Example:
1/api/courses?category=nextjs&limit=10
You can read them using URL:
1export async function GET(request: Request) { 2 const { searchParams } = new URL(request.url); 3 4 const category = searchParams.get("category"); 5 const limit = searchParams.get("limit"); 6 7 return Response.json({ 8 category, 9 limit, 10 }); 11}
For:
1/api/courses?category=nextjs&limit=10
the result is:
1{ 2 "category": "nextjs", 3 "limit": "10" 4}
Notice that query parameters are strings.
If you need a number:
1const limit = Number( 2 searchParams.get("limit") ?? "10" 3);
You should still validate that the resulting value is actually usable.
11. Query Parameters for Filtering
Query parameters are especially useful for filtering and pagination.
Example:
1/api/courses?category=javascript
or:
1/api/courses?category=javascript&page=2&limit=20
Route Handler:
1export async function GET(request: Request) { 2 const { searchParams } = new URL(request.url); 3 4 const category = 5 searchParams.get("category"); 6 7 const page = 8 Number(searchParams.get("page") ?? "1"); 9 10 const limit = 11 Number(searchParams.get("limit") ?? "20"); 12 13 const courses = await getCourses({ 14 category, 15 page, 16 limit, 17 }); 18 19 return Response.json(courses); 20}
The architecture becomes:
1/api/courses 2 │ 3 ├── category 4 ├── page 5 └── limit 6 ↓ 7 Route Handler 8 ↓ 9 Database Query 10 ↓ 11 Results
12. Route Parameters
Route parameters are dynamic values inside the URL path.
Create:
1app/ 2└── api/ 3 └── courses/ 4 └── [id]/ 5 └── route.ts
This represents:
1/api/courses/[id]
Examples:
1/api/courses/1 2/api/courses/2 3/api/courses/100
The [id] segment is dynamic.
Read it using:
1export async function GET( 2 request: Request, 3 context: { 4 params: Promise<{ id: string }>; 5 } 6) { 7 const { id } = await context.params; 8 9 const course = await getCourse(id); 10 11 return Response.json(course); 12}
13. Route Parameters vs Query Parameters
This distinction is important.
Route parameter
1/api/courses/123
The 123 identifies a specific course.
1Route parameter 2 ↓ 3Which resource?
Query parameter
1/api/courses?category=nextjs
The query parameter controls filtering or other request options.
1Query parameter 2 ↓ 3How should I query the resources?
Think:
1/api/courses/123 2 ↑ 3 Resource 4 5/api/courses?category=nextjs 6 ↑ 7 Filter
14. Headers
HTTP headers provide additional request information.
Common headers include:
1Authorization 2Content-Type 3Accept 4User-Agent
You can read headers with:
1export async function GET(request: Request) { 2 const authorization = 3 request.headers.get("authorization"); 4 5 const contentType = 6 request.headers.get("content-type"); 7 8 return Response.json({ 9 authorization, 10 contentType, 11 }); 12}
Headers are commonly used for:
1Authentication 2Content types 3Request metadata 4API versioning 5Caching information
15. Authorization Header
A common API authentication format is:
1Authorization: Bearer <token>
You can read it:
1export async function GET(request: Request) { 2 const authorization = 3 request.headers.get("authorization"); 4 5 if (!authorization) { 6 return Response.json( 7 { 8 error: "Authorization required", 9 }, 10 { 11 status: 401, 12 } 13 ); 14 } 15 16 return Response.json({ 17 authenticated: true, 18 }); 19}
However, checking whether the header exists is not authentication.
The token must actually be validated.
16. Cookies
Cookies are another common way of maintaining authentication sessions.
Next.js provides the cookies() API.
1import { cookies } from "next/headers"; 2 3export async function GET() { 4 const cookieStore = await cookies(); 5 6 const session = cookieStore.get("session"); 7 8 return Response.json({ 9 authenticated: Boolean(session), 10 }); 11}
Cookies can be used for:
1Session IDs 2Authentication 3Preferences 4Security tokens 5User settings
17. Setting Cookies
A Route Handler can also create or update cookies.
1import { cookies } from "next/headers"; 2 3export async function POST() { 4 const cookieStore = await cookies(); 5 6 cookieStore.set( 7 "session", 8 "example-session-id", 9 { 10 httpOnly: true, 11 secure: true, 12 sameSite: "lax", 13 path: "/", 14 } 15 ); 16 17 return Response.json({ 18 success: true, 19 }); 20}
Important cookie options include:
1httpOnly 2secure 3sameSite 4path 5maxAge 6expires
For authentication cookies, secure configuration is extremely important.
18. Authentication
Authentication answers:
Who is this user?
For example:
1Login 2 ↓ 3Credentials verified 4 ↓ 5Session created 6 ↓ 7User authenticated
A protected Route Handler might look like:
1export async function GET() { 2 const user = await getCurrentUser(); 3 4 if (!user) { 5 return Response.json( 6 { 7 error: "Unauthorized", 8 }, 9 { 10 status: 401, 11 } 12 ); 13 } 14 15 const courses = 16 await getCoursesForUser(user.id); 17 18 return Response.json(courses); 19}
The important point is that the server determines the authenticated user.
19. Authentication vs Authorization
These are different concepts.
Authentication
1Who are you?
Authorization
1What are you allowed to do?
Example:
1User logs in 2 ↓ 3Authentication 4 ↓ 5User is identified 6 ↓ 7Authorization 8 ↓ 9Check permissions 10 ↓ 11Allow / Deny
A user may be authenticated but still not have permission to delete a course.
20. Authorization Example
Suppose only administrators can delete courses.
1export async function DELETE( 2 request: Request, 3 context: { 4 params: Promise<{ id: string }>; 5 } 6) { 7 const user = await getCurrentUser(); 8 9 if (!user) { 10 return Response.json( 11 { 12 error: "Unauthorized", 13 }, 14 { 15 status: 401, 16 } 17 ); 18 } 19 20 if (user.role !== "admin") { 21 return Response.json( 22 { 23 error: "Forbidden", 24 }, 25 { 26 status: 403, 27 } 28 ); 29 } 30 31 const { id } = await context.params; 32 33 await deleteCourse(id); 34 35 return new Response(null, { 36 status: 204, 37 }); 38}
Notice:
1401 2 ↓ 3Not authenticated 4 5403 6 ↓ 7Authenticated but not authorized
21. Validation
Validation ensures that incoming data has the correct structure and values.
For larger applications, schema validation is recommended.
For example, with Zod:
1import { z } from "zod"; 2 3const courseSchema = z.object({ 4 title: z.string().min(3), 5 description: z.string().min(10), 6});
Then:
1export async function POST(request: Request) { 2 const body = await request.json(); 3 4 const result = 5 courseSchema.safeParse(body); 6 7 if (!result.success) { 8 return Response.json( 9 { 10 error: "Invalid course data", 11 }, 12 { 13 status: 400, 14 } 15 ); 16 } 17 18 const course = 19 await createCourse(result.data); 20 21 return Response.json(course, { 22 status: 201, 23 }); 24}
The architecture becomes:
1Request 2 ↓ 3Parse JSON 4 ↓ 5Validate 6 │ 7 ├── Invalid → 400 8 │ 9 └── Valid 10 ↓ 11 Business Logic 12 ↓ 13 Database
22. Error Responses
A good API should return useful error responses.
For example:
1return Response.json( 2 { 3 error: "Course not found", 4 }, 5 { 6 status: 404, 7 } 8);
Validation error:
1return Response.json( 2 { 3 error: "Invalid request", 4 fields: { 5 title: "Title is required", 6 }, 7 }, 8 { 9 status: 400, 10 } 11);
Authentication error:
1return Response.json( 2 { 3 error: "Authentication required", 4 }, 5 { 6 status: 401, 7 } 8);
Permission error:
1return Response.json( 2 { 3 error: "You do not have permission", 4 }, 5 { 6 status: 403, 7 } 8);
23. HTTP Status Codes
HTTP status codes communicate what happened.
2xx — Success
1200 OK 2201 Created 3202 Accepted 4204 No Content
Typical usage:
1GET → 200 2POST → 201 3DELETE → 204
4xx — Client Errors
1400 Bad Request 2401 Unauthorized 3403 Forbidden 4404 Not Found 5409 Conflict 6422 Unprocessable Content 7429 Too Many Requests
Examples:
1400 2↓ 3Invalid request 4 5401 6↓ 7Not authenticated 8 9403 10↓ 11Not authorized 12 13404 14↓ 15Resource doesn't exist 16 17409 18↓ 19Resource conflict 20 21429 22↓ 23Too many requests
5xx — Server Errors
1500 Internal Server Error 2502 Bad Gateway 3503 Service Unavailable 4504 Gateway Timeout
These generally represent server-side or upstream problems.
24. Complete CRUD API Structure
A course API could be organized like this:
1app/ 2└── api/ 3 └── courses/ 4 ├── route.ts 5 │ 6 └── [id]/ 7 └── route.ts
The collection endpoint:
1GET /api/courses 2POST /api/courses
The individual resource:
1GET /api/courses/123 2PUT /api/courses/123 3PATCH /api/courses/123 4DELETE /api/courses/123
This gives you a clean REST-style API.
25. Complete route.ts Example
File:
1app/api/courses/route.ts
Code:
1import { NextResponse } from "next/server"; 2 3export async function GET() { 4 const courses = await getCourses(); 5 6 return NextResponse.json(courses); 7} 8 9export async function POST(request: Request) { 10 const body = await request.json(); 11 12 if (!body.title) { 13 return NextResponse.json( 14 { 15 error: "Title is required", 16 }, 17 { 18 status: 400, 19 } 20 ); 21 } 22 23 const course = await createCourse({ 24 title: body.title, 25 description: body.description, 26 }); 27 28 return NextResponse.json(course, { 29 status: 201, 30 }); 31}
This file supports:
1GET 2 ↓ 3Retrieve courses 4 5POST 6 ↓ 7Create course
26. Complete Dynamic Route
File:
1app/api/courses/[id]/route.ts
Code:
1import { NextResponse } from "next/server"; 2 3type Context = { 4 params: Promise<{ 5 id: string; 6 }>; 7}; 8 9export async function GET( 10 request: Request, 11 context: Context 12) { 13 const { id } = await context.params; 14 15 const course = await getCourse(id); 16 17 if (!course) { 18 return NextResponse.json( 19 { 20 error: "Course not found", 21 }, 22 { 23 status: 404, 24 } 25 ); 26 } 27 28 return NextResponse.json(course); 29} 30 31export async function PATCH( 32 request: Request, 33 context: Context 34) { 35 const { id } = await context.params; 36 37 const body = await request.json(); 38 39 const course = await updateCourse( 40 id, 41 body 42 ); 43 44 return NextResponse.json(course); 45} 46 47export async function DELETE( 48 request: Request, 49 context: Context 50) { 51 const { id } = await context.params; 52 53 await deleteCourse(id); 54 55 return new Response(null, { 56 status: 204, 57 }); 58}
27. Calling the API from a Client Component
A Client Component can communicate with the Route Handler.
1"use client"; 2 3export default function CreateCourse() { 4 async function createCourse() { 5 const response = await fetch( 6 "/api/courses", 7 { 8 method: "POST", 9 headers: { 10 "Content-Type": "application/json", 11 }, 12 body: JSON.stringify({ 13 title: "Advanced Next.js", 14 description: 15 "Learn advanced Next.js development", 16 }), 17 } 18 ); 19 20 if (!response.ok) { 21 throw new Error( 22 "Failed to create course" 23 ); 24 } 25 26 const course = 27 await response.json(); 28 29 console.log(course); 30 } 31 32 return ( 33 <button onClick={createCourse}> 34 Create Course 35 </button> 36 ); 37}
The complete flow is:
1Button Click 2 ↓ 3fetch() 4 ↓ 5POST /api/courses 6 ↓ 7Route Handler 8 ↓ 9Validation 10 ↓ 11Database 12 ↓ 13201 Created 14 ↓ 15JSON 16 ↓ 17Client
28. Request Processing Pipeline
A production Route Handler should generally follow this sequence:
1HTTP Request 2 ↓ 3Parse request 4 ↓ 5Authentication 6 ↓ 7Authorization 8 ↓ 9Validation 10 ↓ 11Business Logic 12 ↓ 13Database / External API 14 ↓ 15Response
For example:
1export async function POST( 2 request: Request 3) { 4 // Authentication 5 const user = 6 await getCurrentUser(); 7 8 if (!user) { 9 return Response.json( 10 { 11 error: "Unauthorized", 12 }, 13 { 14 status: 401, 15 } 16 ); 17 } 18 19 // Parse request 20 const body = 21 await request.json(); 22 23 // Validation 24 if (!body.title) { 25 return Response.json( 26 { 27 error: "Title is required", 28 }, 29 { 30 status: 400, 31 } 32 ); 33 } 34 35 // Business logic 36 const course = 37 await createCourse({ 38 title: body.title, 39 authorId: user.id, 40 }); 41 42 // Response 43 return Response.json( 44 course, 45 { 46 status: 201, 47 } 48 ); 49}
This pattern is useful across almost every backend API.
29. Don't Expose Sensitive Information
Never return private information simply because it exists in the database.
Avoid:
1return Response.json({ 2 user, 3 password: user.password, 4 apiKey: user.apiKey, 5 internalToken: user.internalToken, 6});
Instead:
1return Response.json({ 2 id: user.id, 3 name: user.name, 4 email: user.email, 5});
The principle is:
Return only the data the client actually needs.
30. Don't Trust Client-Supplied Identity
A client might send:
1{ 2 "userId": "admin" 3}
You should not automatically trust that value.
Instead:
1Request 2 ↓ 3Session / Authentication 4 ↓ 5Server identifies user 6 ↓ 7Check permissions 8 ↓ 9Perform operation
The server should be responsible for determining:
1Identity 2Permissions 3Ownership 4Roles 5Access
31. API Security Architecture
A secure Route Handler often looks like:
1 Request 2 │ 3 ▼ 4 Route Handler 5 │ 6 ▼ 7 Authentication 8 │ 9 ┌─────┴─────┐ 10 │ │ 11 NO YES 12 │ │ 13 401 ▼ 14 Authorization 15 │ 16 ┌─────┴─────┐ 17 │ │ 18 NO YES 19 │ │ 20 403 ▼ 21 Validate Input 22 │ 23 ▼ 24 Business Logic 25 │ 26 ▼ 27 Database/API 28 │ 29 ▼ 30 Response
This architecture should become second nature when building production APIs.
32. Common Mistakes
Mistake 1 — Not validating input
Never assume browser data is valid.
1Client input 2 ↓ 3Validation 4 ↓ 5Business logic
Mistake 2 — Using 200 for every response
Use meaningful status codes:
1201 → Created 2400 → Bad request 3401 → Not authenticated 4403 → Not authorized 5404 → Not found 6409 → Conflict 7500 → Server error
Mistake 3 — Trusting client user IDs
Do not use:
1const userId = body.userId;
as proof of identity.
Use the authenticated session instead.
Mistake 4 — Exposing secrets
Never send:
1API keys 2Database passwords 3Private tokens 4Password hashes 5Internal credentials
to the browser.
Mistake 5 — Confusing authentication and authorization
1Authentication 2↓ 3Who are you? 4 5Authorization 6↓ 7What can you do?
Mistake 6 — Ignoring failed requests
Always handle failed HTTP responses:
1if (!response.ok) { 2 throw new Error( 3 "Request failed" 4 ); 5}
Mistake 7 — Returning excessive data
Only return fields that the client needs.
33. Real-World Course API
A complete course platform could use:
1/api/courses 2│ 3├── GET 4│ └── List courses 5│ 6├── POST 7│ └── Create course 8│ 9└── [id] 10 │ 11 ├── GET 12 │ └── Get course 13 │ 14 ├── PUT 15 │ └── Replace course 16 │ 17 ├── PATCH 18 │ └── Update course 19 │ 20 └── DELETE 21 └── Delete course
The architecture becomes:
1 Course API 2 │ 3 ┌────────────┴────────────┐ 4 │ │ 5 Collection Resource 6 │ │ 7 /api/courses /api/courses/123 8 │ │ 9 ┌────┴────┐ ┌─────┴─────┐ 10 │ │ │ │ │ 11 GET POST GET PATCH DELETE 12 │ │ │ │ │ 13 List Create Read Update Remove
34. Route Handlers and Databases
A Route Handler can connect to a database through server-side code.
For example:
1import { db } from "@/lib/db"; 2 3export async function GET() { 4 const courses = 5 await db.course.findMany(); 6 7 return Response.json(courses); 8}
The architecture is:
1Browser 2 ↓ 3/api/courses 4 ↓ 5Route Handler 6 ↓ 7Database Client 8 ↓ 9PostgreSQL / MySQL 10 ↓ 11Courses 12 ↓ 13JSON Response
The database credentials remain on the server.
35. Route Handlers and External APIs
Route Handlers can also act as a secure server-side layer in front of an external API.
1Browser 2 ↓ 3Next.js Route Handler 4 ↓ 5External API 6 ↓ 7Response 8 ↓ 9Browser
For example:
1export async function GET() { 2 const response = await fetch( 3 "https://api.example.com/courses", 4 { 5 headers: { 6 Authorization: 7 `Bearer ${process.env.API_KEY}`, 8 }, 9 } 10 ); 11 12 if (!response.ok) { 13 return Response.json( 14 { 15 error: "External API failed", 16 }, 17 { 18 status: 502, 19 } 20 ); 21 } 22 23 const data = 24 await response.json(); 25 26 return Response.json(data); 27}
This allows the private API key to remain on the server.
36. The Complete Mental Model
Think of Route Handlers as the backend boundary of your Next.js application:
1 Next.js 2 │ 3 ┌──────────┴──────────┐ 4 │ │ 5 Frontend Backend 6 │ │ 7 React Components Route Handlers 8 │ │ 9 │ ┌──────┼──────┐ 10 │ │ │ │ 11 │ Auth Validate Database 12 │ │ 13 └──────────┬──────────┘ 14 │ 15 ▼ 16 User
The frontend handles the user interface.
The Route Handler handles the HTTP API boundary.
The database and private services remain behind the server.
37. Final Architecture
A production Next.js API can follow this structure:
1 Client 2 │ 3 ▼ 4 HTTP Request 5 │ 6 ▼ 7 Route Handler 8 │ 9 ▼ 10 Authentication 11 │ 12 ▼ 13 Authorization 14 │ 15 ▼ 16 Validation 17 │ 18 ▼ 19 Business Logic 20 │ 21 ┌────────┴────────┐ 22 │ │ 23 ▼ ▼ 24 Database External API 25 │ │ 26 └────────┬────────┘ 27 ▼ 28 HTTP Response 29 │ 30 ▼ 31 Client
The most important rule is:
Never trust the client. Treat every request as untrusted input and enforce authentication, authorization, validation, and security on the server.
38. Module Summary
In this module, you learned how Next.js Route Handlers support full-stack API development.
You learned:
1Route Handlers 2 ↓ 3GET 4 ↓ 5POST 6 ↓ 7PUT 8 ↓ 9PATCH 10 ↓ 11DELETE 12 ↓ 13Request Body 14 ↓ 15Query Parameters 16 ↓ 17Route Parameters 18 ↓ 19Headers 20 ↓ 21Cookies 22 ↓ 23Authentication 24 ↓ 25Authorization 26 ↓ 27Validation 28 ↓ 29Error Responses 30 ↓ 31HTTP Status Codes
The key mapping to remember is:
1GET 2 ↓ 3Read 4 5POST 6 ↓ 7Create 8 9PUT 10 ↓ 11Replace 12 13PATCH 14 ↓ 15Partially update 16 17DELETE 18 ↓ 19Delete
And the production request pipeline is:
1Request 2 ↓ 3Parse 4 ↓ 5Authenticate 6 ↓ 7Authorize 8 ↓ 9Validate 10 ↓ 11Business Logic 12 ↓ 13Database / API 14 ↓ 15Response
Once you understand this model, you have the foundation needed to build REST APIs, authentication systems, database-backed applications, dashboards, SaaS platforms, and full-stack Next.js applications.