Route Handlers (Build an API)
What is a Route Handler?
A Route Handler is a file named route.js that returns data (usually JSON) instead of a page. You export a function per HTTP method: GET, POST, PUT, PATCH, DELETE. It is how your Next.js site becomes an API for Android apps or other websites.
page.jsx
- Returns HTML
- For humans in a browser
- URL: /courses
route.js
- Returns JSON / text / file
- For apps and scripts
- URL: /api/courses
GET /api/courses
// app/api/courses/route.js (from the EduSite project)
import { searchCourses } from '@/lib/courses'
// GET /api/courses?q=python → JSON list of matching courses.
export async function GET(request) {
const q = request.nextUrl.searchParams.get('q') || ''
const data = searchCourses(q).map(({ slug, title, level, fee, duration }) =>
({ slug, title, level, fee, duration }))
return Response.json({ count: data.length, courses: data })
}Query strings: ?q=python
request.nextUrl.searchParams.get('q') reads the query string. Test it:
POST with a JSON body
// app/api/enquiries/route.js
import { NextResponse } from 'next/server'
const enquiries = [] // demo only – use a database in real apps
export async function GET() {
return NextResponse.json(enquiries)
}
export async function POST(request) {
let body
try {
body = await request.json()
} catch {
return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })
}
const name = String(body.name || '').trim()
const phone = String(body.phone || '').trim()
if (name.length < 2 || !/^[6-9]\d{9}$/.test(phone)) {
return NextResponse.json({ error: 'Name and a valid 10-digit mobile are required' }, { status: 422 })
}
const item = { id: enquiries.length + 1, name, phone, at: new Date().toISOString() }
enquiries.push(item)
return NextResponse.json(item, { status: 201 }) // 201 = Created
}Dynamic API: /api/courses/[slug]
// app/api/courses/[slug]/route.js
import { getCourse } from '@/lib/courses'
export async function GET(request, { params }) {
const { slug } = await params // params is a Promise here too
const course = getCourse(slug)
if (!course) {
return Response.json({ error: 'Course not found' }, { status: 404 })
}
return Response.json(course)
}Request → response flow
Status codes and errors
| Code | Meaning | When to use |
|---|---|---|
| 200 | OK | Successful GET |
| 201 | Created | POST created a record |
| 400 | Bad Request | Invalid JSON / missing fields |
| 401 | Unauthorized | Not logged in / bad token |
| 404 | Not Found | Record does not exist |
| 422 | Unprocessable | Validation failed |
| 500 | Server Error | Unexpected crash |
Headers and CORS for mobile apps
Native Android apps (Retrofit) do not need CORS, but JavaScript on other websites does.
// app/api/courses/route.js — allow any origin to read (public API)
const cors = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export async function OPTIONS() { // browser "preflight" request
return new Response(null, { status: 204, headers: cors })
}
export async function GET(request) {
const data = { ok: true }
return Response.json(data, { headers: { ...cors, 'Cache-Control': 'public, max-age=60' } })
}Testing your API
// From any JavaScript (browser console, another site, React Native):
const res = await fetch('https://your-site.com/api/courses?q=web')
const json = await res.json()
console.log(json.count, json.courses[0].title)You can also open http://localhost:3000/api/courses in the browser, or use Postman / Thunder Client (VS Code extension) for POST requests.
Common mistakes
export default functionin route.js → must be named exports (export async function GET).- Putting route.js next to page.jsx → conflict.
- Returning 200 with
{ error }→ apps cannot detect failure; set the right status. - Trusting input without validation → always validate on the server.
Summary
route.jsexportsGET,POST… and returnsResponse.json().- Read query via
request.nextUrl.searchParams, body viaawait request.json(), path viaawait params. - Use correct status codes; add CORS only for cross-site browser clients.
Create /api/courses/[slug] and /api/enquiries (GET + POST). Test POST with curl using a valid and an invalid phone number and check the status codes.
