Урок 07

API Routes

API-роуты в App Router создаются файлами route.ts в папках app/api/.

Базовый роут

app/api/hello/route.ts:

import { NextResponse } from 'next/server';

export async function GET() {
    return NextResponse.json({ message: 'Hello' });
}

URL: /api/hello.

Методы

export async function GET(request: Request) {}
export async function POST(request: Request) {}
export async function PUT(request: Request) {}
export async function PATCH(request: Request) {}
export async function DELETE(request: Request) {}

Каждая функция — свой HTTP-метод.

Чтение запроса

export async function POST(request: Request) {
    const body = await request.json();
    const header = request.headers.get('authorization');
    const { searchParams } = new URL(request.url);
    const page = searchParams.get('page');

    return NextResponse.json({ body, page });
}

Динамические роуты

app/api/posts/[id]/route.ts:

export async function GET(
    request: Request,
    { params }: { params: Promise<{ id: string }> }
) {
    const { id } = await params;
    const post = await getPost(id);

    return NextResponse.json(post);
}

Ответы

// JSON
return NextResponse.json({ data });
return NextResponse.json({ error: 'Not found' }, { status: 404 });

// Пустой
return new Response(null, { status: 204 });

// Текст
return new Response('Hello', { headers: { 'Content-Type': 'text/plain' } });

// Редирект
return NextResponse.redirect(new URL('/login', request.url));

Заголовки

return NextResponse.json(data, {
    headers: {
        'Cache-Control': 'public, max-age=3600',
        'X-Custom': 'value',
    },
});

CORS

return NextResponse.json(data, {
    headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, POST',
        'Access-Control-Allow-Headers': 'Content-Type',
    },
});

// OPTIONS
return new Response(null, { status: 204, headers });

Валидация

import { z } from 'zod';

const schema = z.object({
    title: z.string().min(3),
    body: z.string().min(10),
});

export async function POST(request: Request) {
    const json = await request.json();
    const result = schema.safeParse(json);

    if (!result.success) {
        return NextResponse.json(
            { errors: result.error.flatten() },
            { status: 422 }
        );
    }

    const post = await db.post.create({ data: result.data });
    return NextResponse.json(post, { status: 201 });
}

Авторизация

import { cookies } from 'next/headers';

export async function GET() {
    const cookieStore = await cookies();
    const token = cookieStore.get('token')?.value;

    if (!token) {
        return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
    }

    const user = await verifyToken(token);
    return NextResponse.json({ user });
}

Работа с БД

import { db } from '@/lib/db';

export async function GET() {
    const posts = await db.post.findMany();
    return NextResponse.json(posts);
}

Streaming

export async function GET() {
    const stream = new ReadableStream({
        async start(controller) {
            for (let i = 0; i < 10; i++) {
                controller.enqueue(`chunk ${i}\n`);
                await new Promise((r) => setTimeout(r, 100));
            }
            controller.close();
        },
    });

    return new Response(stream, {
        headers: { 'Content-Type': 'text/plain' },
    });
}

Когда использовать

  • Webhooks — от внешних сервисов
  • Публичные API — для мобильных приложений
  • Интеграции — с другими сервисами
  • SSE и стримы — real-time

Для внутренних мутаций — Server Actions, они проще.

Итоги

  • route.ts в app/api/
  • Экспорт GET, POST, ...
  • NextResponse.json для ответа
  • Параметры через params
  • Zod для валидации
  • Server Actions для внутренних мутаций