API Routes
Next.js API Routes — кухня ресторану з вікном видачі де кухар передає страву офіціанту
У попередніх уроках ми навчилися створювати сторінки та отримувати дані. Але що, якщо нам потрібен свій сервер для обробки форм, збереження даних або інтеграції з іншими сервісами? Next.js дозволяє створювати API endpoints прямо в проекті -- без окремого бекенду!
Навіщо API Routes?
У звичайному React-проекті для серверної логіки потрібен окремий сервер (Express, Fastify тощо). Next.js вирішує це вбудованими API routes:
- Обробка форм (контактна форма, реєстрація)
- CRUD операції з базою даних
- Інтеграція з зовнішніми API (приховування ключів)
- Webhook-обробники
- Авторизація
API routes виконуються тільки на сервері. Код у них ніколи не потрапляє в бандл браузера. Тут безпечно використовувати секретні ключі, підключення до БД тощо.
Route Handlers (App Router)
В App Router API-ендпоінти створюються через файл route.ts (не page.tsx!):
src/app/
api/
hello/
route.ts → GET /api/hello
users/
route.ts → GET/POST /api/users
[id]/
route.ts → GET/PUT/DELETE /api/users/:id
Перший API route
// src/app/api/hello/route.ts
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({
message: "Привіт з API!",
timestamp: new Date().toISOString(),
});
}
Тепер відкрий http://localhost:3000/api/hello -- побачиш JSON-відповідь!
HTTP-методи
Кожен метод -- це окрема експортована функція:
// src/app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
interface User {
id: number;
name: string;
email: string;
}
// Імітація бази даних (в реальному проекті -- БД)
let users: User[] = [
{ id: 1, name: "Олексій", email: "alex@example.com" },
{ id: 2, name: "Марія", email: "maria@example.com" },
];
// GET /api/users -- отримати всіх
export async function GET() {
return NextResponse.json(users);
}
// POST /api/users -- створити нового
export async function POST(request: NextRequest) {
const body = await request.json();
const newUser: User = {
id: users.length + 1,
name: body.name,
email: body.email,
};
users.push(newUser);
return NextResponse.json(newUser, { status: 201 });
}
Масив users тут зберігається в пам'яті -- при перезапуску сервера дані зникнуть. У реальному проекті використовуй базу даних (Firebase, PostgreSQL, MongoDB тощо).
Динамічні API routes
Для операцій з конкретним ресурсом (GET одного, PUT, DELETE):
// src/app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from "next/server";
interface User {
id: number;
name: string;
email: string;
}
// Імітація БД (у реальному проекті -- спільна БД)
let users: User[] = [
{ id: 1, name: "Олексій", email: "alex@example.com" },
{ id: 2, name: "Марія", email: "maria@example.com" },
];
// GET /api/users/1 -- отримати одного
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const user = users.find((u) => u.id === Number(id));
if (!user) {
return NextResponse.json(
{ error: "Користувача не знайдено" },
{ status: 404 }
);
}
return NextResponse.json(user);
}
// PUT /api/users/1 -- оновити
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json();
const index = users.findIndex((u) => u.id === Number(id));
if (index === -1) {
return NextResponse.json(
{ error: "Користувача не знайдено" },
{ status: 404 }
);
}
users[index] = { ...users[index], ...body };
return NextResponse.json(users[index]);
}
// DELETE /api/users/1 -- видалити
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const index = users.findIndex((u) => u.id === Number(id));
if (index === -1) {
return NextResponse.json(
{ error: "Користувача не знайдено" },
{ status: 404 }
);
}
const deleted = users.splice(index, 1)[0];
return NextResponse.json({ message: "Видалено", user: deleted });
}
Робота з Request
NextRequest надає зручні методи для роботи із запитом:
Query параметри
// GET /api/users?search=олексій&page=2
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const search = searchParams.get("search"); // "олексій"
const page = Number(searchParams.get("page")) || 1; // 2
// Фільтрація та пагінація
let result = users;
if (search) {
result = result.filter((u) =>
u.name.toLowerCase().includes(search.toLowerCase())
);
}
const perPage = 10;
const start = (page - 1) * perPage;
result = result.slice(start, start + perPage);
return NextResponse.json({
data: result,
page,
total: users.length,
});
}
Headers та Cookies
export async function GET(request: NextRequest) {
// Читання headers
const authHeader = request.headers.get("authorization");
const contentType = request.headers.get("content-type");
// Читання cookies
const token = request.cookies.get("session")?.value;
// Встановлення headers у відповіді
const response = NextResponse.json({ data: "test" });
response.headers.set("X-Custom-Header", "hello");
// Встановлення cookies
response.cookies.set("visited", "true", {
httpOnly: true,
maxAge: 60 * 60 * 24, // 1 день
});
return response;
}
Валідація запитів
Завжди перевіряй вхідні дані:
// src/app/api/users/route.ts
interface CreateUserBody {
name: string;
email: string;
}
function validateUser(body: unknown): body is CreateUserBody {
if (!body || typeof body !== "object") return false;
const obj = body as Record<string, unknown>;
if (typeof obj.name !== "string" || obj.name.length < 2) return false;
if (typeof obj.email !== "string" || !obj.email.includes("@")) return false;
return true;
}
export async function POST(request: NextRequest) {
let body: unknown;
try {
body = await request.json();
} catch {
return NextResponse.json(
{ error: "Невалідний JSON" },
{ status: 400 }
);
}
if (!validateUser(body)) {
return NextResponse.json(
{ error: "Невалідні дані. Потрібно: name (мін. 2), email (з @)" },
{ status: 400 }
);
}
// Тепер body типізований як CreateUserBody
const newUser = {
id: Date.now(),
name: body.name,
email: body.email,
};
return NextResponse.json(newUser, { status: 201 });
}
Для серйозних проектів використовуй бібліотеки валідації: Zod, Yup, або Valibot. Вони дають і TypeScript типи, і runtime-валідацію в одному місці.
Виклик API з клієнта
Як використовувати створені API routes з React-компонентів:
// src/components/UserList.tsx
"use client";
import { useState, useEffect } from "react";
interface User {
id: number;
name: string;
email: string;
}
export default function UserList() {
const [users, setUsers] = useState<User[]>([]);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
// Завантаження списку
useEffect(() => {
fetch("/api/users")
.then((res) => res.json())
.then(setUsers);
}, []);
// Створення нового
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const res = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, email }),
});
if (res.ok) {
const newUser = await res.json();
setUsers((prev) => [...prev, newUser]);
setName("");
setEmail("");
}
};
// Видалення
const handleDelete = async (id: number) => {
const res = await fetch(`/api/users/${id}`, { method: "DELETE" });
if (res.ok) {
setUsers((prev) => prev.filter((u) => u.id !== id));
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="Ім'я" />
<input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
<button type="submit">Додати</button>
</form>
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} ({user.email})
<button onClick={() => handleDelete(user.id)}>Видалити</button>
</li>
))}
</ul>
</div>
);
}
Підсумок
- Route Handlers (
route.ts) створюють API endpoints в Next.js - Експортуй функції
GET,POST,PUT,DELETEдля відповідних HTTP-методів - NextRequest -- для читання body, query params, headers, cookies
- NextResponse.json() -- для створення JSON-відповідей зі статус-кодами
- Завжди валідуй вхідні дані на сервері
- API routes виконуються тільки на сервері -- безпечно для секретів
Що далі?
У наступному уроці зануримося в App Router -- розберемо layouts, Server Components vs Client Components, та сучасну архітектуру Next.js додатків.
Корисні посилання:
- Next.js: Route Handlers -- офіційна документація
- Zod -- бібліотека валідації з TypeScript типами
- REST API Best Practices -- принципи проєктування REST API