1. Middleware Deep Dive
// middleware.ts — runs BEFORE every matched route
import { NextResponse, type NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Read & modify headers
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-request-id', crypto.randomUUID());
// Redirect
if (request.nextUrl.pathname === '/old-page') {
return NextResponse.redirect(new URL('/new-page', request.url));
}
// Rewrite (URL stays same, content from different path)
if (request.nextUrl.pathname.startsWith('/api/v1')) {
return NextResponse.rewrite(
new URL(request.nextUrl.pathname.replace('/v1', '/v2'), request.url)
);
}
// Continue with modified headers
return NextResponse.next({
request: { headers: requestHeaders },
});
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico).*)',
],
};
2. Route Handlers
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/db';
export async function GET(request: NextRequest) {
const { searchParams } = request.nextUrl;
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '10');
const posts = await db.post.findMany({
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' },
});
return NextResponse.json({ data: posts, page, limit });
}
export async function POST(request: NextRequest) {
const body = await request.json();
const post = await db.post.create({
data: {
title: body.title,
content: body.content,
},
});
return NextResponse.json(post, { status: 201 });
}
3. Dynamic Route Handlers
// app/api/posts/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const post = await db.post.findUnique({ where: { id } });
if (!post) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
return NextResponse.json(post);
}
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json();
const post = await db.post.update({
where: { id },
data: body,
});
return NextResponse.json(post);
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
await db.post.delete({ where: { id } });
return new NextResponse(null, { status: 204 });
}
4. CORS Configuration
// middleware.ts hoặc route handler
const corsHeaders = {
'Access-Control-Allow-Origin': 'https://example.com',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};
export async function OPTIONS() {
return new NextResponse(null, { status: 204, headers: corsHeaders });
}
export async function GET(request: NextRequest) {
const data = { message: 'Hello' };
return NextResponse.json(data, { headers: corsHeaders });
}
5. Streaming Response
// app/api/stream/route.ts
export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 10; i++) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ count: i })}\n\n`)
);
await new Promise(r => setTimeout(r, 1000));
}
controller.close();
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
});
}
6. Webhooks
// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(request: NextRequest) {
const body = await request.text();
const headersList = await headers();
const signature = headersList.get('stripe-signature')!;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch {
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
}
switch (event.type) {
case 'checkout.session.completed':
// Handle successful payment
break;
case 'invoice.payment_failed':
// Handle failed payment
break;
}
return NextResponse.json({ received: true });
}
Bài tiếp theo: Database Integration & Prisma — ORM, migrations, relations, queries.