A Next.js app with a working contact form that uses a Server Action to validate and save data — no API route needed, no useState, just a form that works.
A Next.js app with a working contact form that uses a Server Action to validate and save data — no API route needed, no useState, just a form that works.
API routes in the App Router are called Route Handlers. Create a route.ts file in any directory under app/ to define HTTP methods as exported functions.
import { NextResponse } from 'next/server' const posts = [ { id: 1, title: 'Hello World' }, { id: 2, title: 'Next.js is Great' }, ] // GET /api/posts export async function GET() { return NextResponse.json(posts) } // POST /api/posts export async function POST(request: Request) { const body = await request.json() if (!body.title) { return NextResponse.json( { error: 'Title required' }, { status: 400 } ) } const newPost = { id: posts.length + 1, ...body } posts.push(newPost) return NextResponse.json(newPost, { status: 201 }) }
Server Actions let you define server-side functions that forms call directly — no API route, no fetch() call, no useState. The form submits and the action runs on the server. Works even with JavaScript disabled.
'use server' // at the top of an actions file import { revalidatePath } from 'next/cache' async function submitContact(formData: FormData) { 'use server' // or inline like this const name = formData.get('name') as string const email = formData.get('email') as string const message = formData.get('message') as string // Validate if (!name || !email || !message) { throw new Error('All fields required') } // Save to DB, send email, etc. console.log({ name, email, message }) // Revalidate the page cache revalidatePath('/contact') } export default function ContactPage() { return ( <form action={submitContact}> <input name="name" required /> <input name="email" type="email" required /> <textarea name="message" required /> <button type="submit">Send</button> </form> ) }
Next.js has three data fetching patterns. Understanding when to use each one is the key to building fast apps.
// ISR: revalidate every 60 seconds export const revalidate = 60 // Or disable caching entirely (SSR) export const dynamic = 'force-dynamic' // Or make fully static (SSG) export const dynamic = 'force-static' async function getBlogPosts() { const res = await fetch('/api/posts', { next: { revalidate: 60 } // ISR on per-fetch level }) return res.json() }
Middleware runs before every request and can redirect, rewrite, or add headers. Use it to protect routes without loading the full page first.
import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { const token = request.cookies.get('auth-token') // Protect /dashboard routes if (request.nextUrl.pathname.startsWith('/dashboard') && !token) { return NextResponse.redirect(new URL('/login', request.url)) } return NextResponse.next() } export const config = { matcher: ['/dashboard/:path*', '/api/protected/:path*'] }
Day 3 is ready when you are.
Want live instruction and hands-on projects? Join the AI bootcamp — 3 days, 5 cities.
The foundations from today carry directly into Day 3. In the next session the focus shifts to Day 3 — building directly on everything covered here.
Before moving on, verify you can answer these without looking:
Live Bootcamp
Learn this in person — 2 days, 5 cities
Thu–Fri sessions in Denver, Los Angeles, New York, Chicago, and Dallas. $1,490 per seat. June–October 2026.
Reserve Your Seat →