
Build 10x products in minutes by chatting with AI - beyond just a prototype.
Topics
Can I use TypeScript with Route Handlers?
How do I secure API routes?
When should I proxy an API?
Should I always create a public API?
What are Server Actions?
Next.js isn't just for building powerful frontend apps—it’s also a full-stack framework. With its latest advancements like the App Router and Route Handlers, you can create backend APIs right within your Next.js app using web-standard Request and Response objects.
In this article, we explore how to build scalable, production-ready APIs using the modern Next.js architecture. Whether you're serving web clients, mobile apps, or third-party services, you'll learn everything from dynamic routing to secure middleware patterns.
To create a new Next.js app with API examples included:
npx create-next-app@latest --api
This scaffolds a project using the App Router with example route.ts files under the app/api/ directory.
Use Next.js for APIs when:
If you only need server-side data fetching for your frontend (without exposing endpoints), consider using Server Components.
In the App Router, route handlers live inside the app/api/ directory.
Example folder structure:
app/ └── api/ └── users/ └── route.ts
Basic API handler:
export async function GET(request: Request) { const users = [{ id: 1, name: 'Alice' }]; return new Response(JSON.stringify(users), { status: 200, headers: { 'Content-Type': 'application/json' }, }); }
You can export multiple methods (GET, POST, PUT, etc.) in a single file.
export async function POST(request: Request) { const body = await request.json(); const { name } = body; const newUser = { id: Date.now(), name }; return new Response(JSON.stringify(newUser), { status: 201, headers: { 'Content-Type': 'application/json' }, }); }
This allows /api/users to handle both read and write operations from the same file.
The App Router uses Web Platform APIs. You receive Request objects and return Response objects.
import { NextRequest } from 'next/server'; export function GET(request: NextRequest) { const query = request.nextUrl.searchParams.get('q'); return new Response(JSON.stringify({ query })); }
import { cookies, headers } from 'next/headers'; export function GET() { const token = cookies().get('token')?.value; const referer = headers().get('referer'); return new Response(JSON.stringify({ token, referer })); }
For routes like /api/users/123:
app/ └── api/ └── users/ └── [id]/ └── route.ts
export async function GET( request: Request, { params }: { params: { id: string } } ) { return new Response(JSON.stringify({ id: params.id })); }
app/api/docs/[...slug]/route.ts
export async function GET( request: Request, { params }: { params: { slug: string[] } } ) { return new Response(JSON.stringify({ slug: params.slug })); }
Reusable auth wrapper:
Usage:
| Aspect | Recommendation |
|---|---|
| Platform | Use Vercel for full compatibility |
| Secrets | Use or the Vercel dashboard |
| Static Export | Avoid if using API routes |
| CLI Deploy | for production deployments |
| Rate Limiting | Use Vercel Firewall or edge middleware |
Use for internal-only data fetching:
Building APIs with using the and provides a modern, scalable, and developer-friendly full-stack experience.
✅ Use web-standard and objects
✅ Handle multiple HTTP methods in a single file
✅ Create dynamic, nested, and catch-all routes
✅ Share middleware logic across endpoints
✅ Proxy other APIs or handle webhooks
✅ Test and deploy APIs with confidence
Embrace not just as a frontend tool—but as your complete backend layer too.
.envnext exportvercel --prodServer ComponentsNext.jsApp RouterRoute HandlersRequestResponseNext.jsexport async function GET() {
const res = await fetch('https://api.example.com/data', {
headers: { Authorization: `Bearer ${process.env.API_KEY}` },
});
const data = await res.json();
return new Response(JSON.stringify({ ...data, proxy: true }));
}type Handler = (req: Request) => Promise<Response>;
export function withAuth(handler: Handler): Handler {
return async (req) => {
const token = req.headers.get('authorization');
if (!token || token !== 'Bearer valid-token') {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
});
}
return handler(req);
};
}import { withAuth } from '@/lib/with-auth';
async function secretHandler(request: Request) {
return new Response(JSON.stringify({ secret: '🍕' }));
}
export const GET = withAuth(secretHandler);import handler from '@/app/api/users/[id]/route';
test('GET user by ID', async () => {
const request = new Request('http://localhost/api/users/123', {
method: 'GET',
});
const response = await handler(request, { params: { id: '123' } });
const body = await response.json();
expect(response.status).toBe(200);
expect(body).toEqual({ id: '123' });
});// app/users/page.tsx
export default async function UsersPage() {
const res = await fetch('https://api.example.com/users');
const users = await res.json();
return (
<ul>
{users.map((u: any) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}