Crash Courses · Basic
API Routes & Server Actions
Build backend endpoints and mutate data with Route Handlers and Server Actions.
11 min read+150 XP on completionCert: Next.js Crash Course
Tap any word in the text below to start reading from there.
API Routes & Server Actions
Next.js gives you two ways to run server-side logic:
- Route Handlers — traditional REST API endpoints consumed by external clients, mobile apps, or third-party services
- Server Actions — server functions called directly from React components, no HTTP overhead, fully type-safe
Route Handlers
// app/api/courses/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { COURSES } from '@/lib/courses'
export async function GET(req: NextRequest) {
const track = req.nextUrl.searchParams.get('track')
const courses = track
? COURSES.filter(c => c.track === track)
: COURSES
return NextResponse.json(courses)
}
export async function POST(req: NextRequest) {
const body = await req.json()
// Validate, save to DB...
const newCourse = await saveCourse(body)
return NextResponse.json(newCourse, { status: 201 })
}The file exports named functions matching HTTP methods: GET, POST, PATCH, DELETE. No framework config — the export name is the method.
Dynamic Route Handler
// app/api/courses/[id]/route.ts
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const course = COURSES.find(c => c.id === id)
if (!course) {
return NextResponse.json({ error: 'Course not found' }, { status: 404 })
}
return NextResponse.json(course)
}
export async function PATCH(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const updates = await req.json()
const updated = await updateCourse(id, updates)
return NextResponse.json(updated)
}Server Actions — Mutations Without API Routes
Server Actions are async functions marked with 'use server'. They run on the server, but client components can call them like regular JavaScript functions — no fetch, no endpoint, no CORS.
// app/actions/progress.ts
'use server'
import { revalidatePath } from 'next/cache'
import { saveCourseProgress } from '@/lib/db'
export async function markCourseComplete(courseId: string, userId: string) {
// Runs on the server — can access DB, env vars, secrets directly
await saveCourseProgress({ courseId, userId, completedAt: new Date() })
// Clear the cache so the next page render shows updated progress
revalidatePath('/courses')
revalidatePath(`/courses/${courseId}`)
return { success: true }
}// app/components/CompleteButton.tsx
'use client'
import { useState } from 'react'
import { markCourseComplete } from '@/app/actions/progress'
export function CompleteButton({ courseId, userId }: { courseId: string; userId: string }) {
const [loading, setLoading] = useState(false)
const [done, setDone] = useState(false)
async function handleClick() {
setLoading(true)
await markCourseComplete(courseId, userId) // calls server directly
setDone(true)
setLoading(false)
}
return (
<button onClick={handleClick} disabled={loading || done}>
{done ? 'Completed!' : loading ? 'Saving...' : 'Mark Complete'}
</button>
)
}No API route. No endpoint URL. No fetch. The client button calls a server function by reference.
When to Use What
| Use Case | Tool |
|---|---|
| Form submit, toggle, mutation from a React component | Server Action |
| REST API for a mobile app or external service | Route Handler |
| Webhook endpoint | Route Handler |
| Database mutation from a button click | Server Action |
| GraphQL endpoint | Route Handler |
Error Handling in Route Handlers
export async function POST(req: NextRequest) {
try {
const body = await req.json()
if (!body.title) {
return NextResponse.json({ error: 'title is required' }, { status: 400 })
}
const result = await saveItem(body)
return NextResponse.json(result, { status: 201 })
} catch (error) {
console.error('POST /api/items error:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}Reading progress
0% read
In this module
Route Handler
Server Action
revalidatePath
NextRequest
0%