Dowiedz się, jak tworzyć potężne punkty końcowe API za pomocą Next.js Route Handlers. Ten przewodnik omawia wszystko, od podstawowej konfiguracji po zaawansowane techniki, z praktycznymi przykładami i najlepszymi praktykami.
Next.js Route Handlers: Kompleksowy przewodnik po tworzeniu punktów końcowych API
Next.js zrewolucjonizował sposób, w jaki tworzymy aplikacje internetowe, dzięki swoim potężnym funkcjom, takim jak renderowanie po stronie serwera, generowanie statycznych stron, a teraz także Route Handlers. Route Handlers zapewniają elastyczny i wydajny sposób tworzenia punktów końcowych API bezpośrednio w aplikacji Next.js. Ten przewodnik omawia koncepcję Route Handlers, ich zalety oraz sposoby ich efektywnego wykorzystania do budowy solidnych interfejsów API.
Czym są Next.js Route Handlers?
Route Handlers to funkcje zdefiniowane w katalogu app
projektu Next.js, które obsługują przychodzące żądania HTTP. W przeciwieństwie do starszego podejścia pages/api
(które używa API Routes), Route Handlers oferują bardziej usprawniony i elastyczny sposób definiowania punktów końcowych API obok komponentów React. Są to w zasadzie funkcje bezserwerowe wykonywane na brzegu sieci (edge) lub w wybranym środowisku serwerowym.
Można myśleć o Route Handlers jako o logice backendowej aplikacji Next.js, odpowiedzialnej za przetwarzanie żądań, interakcję z bazami danych i zwracanie odpowiedzi.
Korzyści z używania Route Handlers
- Kolokacja: Route Handlers znajdują się bezpośrednio obok komponentów React w katalogu
app
, co sprzyja lepszej organizacji i utrzymaniu kodu. - Wsparcie dla TypeScript: Wbudowane wsparcie dla TypeScript zapewnia bezpieczeństwo typów i poprawia doświadczenie deweloperskie.
- Integracja z Middleware: Łatwa integracja z middleware do zadań takich jak uwierzytelnianie, autoryzacja i walidacja żądań.
- Wsparcie dla streamingu: Route Handlers mogą przesyłać dane strumieniowo, umożliwiając stopniowe wysyłanie odpowiedzi, co jest korzystne w przypadku dużych zbiorów danych lub długotrwałych procesów.
- Funkcje brzegowe (Edge Functions): Wdrażaj Route Handlers jako funkcje brzegowe (Edge Functions) dla odpowiedzi o niskim opóźnieniu, bliżej użytkowników, wykorzystując globalne sieci CDN.
- Uproszczony projekt API: Route Handlers zapewniają czyste i intuicyjne API do obsługi żądań i odpowiedzi.
- Integracja z Server Actions: Ścisła integracja z Server Actions pozwala na płynną komunikację między komponentami po stronie klienta a logiką po stronie serwera.
Konfiguracja projektu Next.js
Zanim zagłębisz się w Route Handlers, upewnij się, że masz skonfigurowany projekt Next.js z katalogiem app
. Jeśli rozpoczynasz nowy projekt, użyj następującego polecenia:
npx create-next-app@latest my-nextjs-app
Wybierz katalog app
podczas procesu konfiguracji, aby włączyć nowy system routingu.
Tworzenie pierwszego Route Handlera
Stwórzmy prosty punkt końcowy API, który zwraca odpowiedź w formacie JSON. Utwórz nowy katalog wewnątrz katalogu app
, na przykład /app/api/hello
. W tym katalogu utwórz plik o nazwie route.ts
(lub route.js
, jeśli nie używasz TypeScript).
Oto kod Twojego pierwszego Route Handlera:
// app/api/hello/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
return NextResponse.json({ message: 'Hello from Next.js Route Handlers!' });
}
Wyjaśnienie:
import { NextResponse } from 'next/server';
: Importuje obiektNextResponse
, który jest używany do konstruowania odpowiedzi API.export async function GET(request: Request) { ... }
: Definiuje asynchroniczną funkcję, która obsługuje żądania GET do punktu końcowego/api/hello
. Parametrrequest
zapewnia dostęp do obiektu przychodzącego żądania.return NextResponse.json({ message: 'Hello from Next.js Route Handlers!' });
: Tworzy odpowiedź JSON z wiadomością i zwraca ją za pomocąNextResponse.json()
.
Teraz możesz uzyskać dostęp do tego punktu końcowego, przechodząc do /api/hello
w przeglądarce lub używając narzędzia takiego jak curl
lub Postman
.
Obsługa różnych metod HTTP
Route Handlers obsługują różne metody HTTP, takie jak GET, POST, PUT, DELETE, PATCH i OPTIONS. Możesz zdefiniować osobne funkcje dla każdej metody w tym samym pliku route.ts
.
// app/api/users/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
// Logic to retrieve all users from the database
const users = [{ id: 1, name: 'John Doe' }, { id: 2, name: 'Jane Smith' }]; // Example data
return NextResponse.json(users);
}
export async function POST(request: Request) {
const data = await request.json(); // Parse the request body as JSON
// Logic to create a new user in the database using 'data'
const newUser = { id: 3, name: data.name, email: data.email }; // Example
return NextResponse.json(newUser, { status: 201 }); // Return the new user with a 201 Created status code
}
Wyjaśnienie:
- Funkcja
GET
pobiera listę użytkowników (tutaj symulowaną) i zwraca ją jako odpowiedź JSON. - Funkcja
POST
przetwarza ciało żądania jako JSON, tworzy nowego użytkownika (symulowanego) i zwraca nowego użytkownika z kodem statusu 201 Created.
Dostęp do danych żądania
Obiekt request
zapewnia dostęp do różnych informacji o przychodzącym żądaniu, w tym nagłówków, parametrów zapytania i ciała żądania.
Nagłówki
Możesz uzyskać dostęp do nagłówków żądania za pomocą właściwości request.headers
:
export async function GET(request: Request) {
const userAgent = request.headers.get('user-agent');
console.log('User Agent:', userAgent);
return NextResponse.json({ userAgent });
}
Parametry zapytania
Aby uzyskać dostęp do parametrów zapytania, możesz użyć konstruktora URL
:
export async function GET(request: Request) {
const url = new URL(request.url);
const searchParams = new URLSearchParams(url.search);
const id = searchParams.get('id');
console.log('ID:', id);
return NextResponse.json({ id });
}
Ciało żądania
Dla żądań POST, PUT i PATCH możesz uzyskać dostęp do ciała żądania za pomocą metod request.json()
lub request.text()
, w zależności od typu zawartości.
export async function POST(request: Request) {
const data = await request.json();
console.log('Data:', data);
return NextResponse.json({ receivedData: data });
}
Zwracanie odpowiedzi
Obiekt NextResponse
służy do konstruowania odpowiedzi API. Zapewnia kilka metod ustawiania nagłówków, kodów statusu i ciał odpowiedzi.
Odpowiedzi JSON
Użyj metody NextResponse.json()
, aby zwrócić odpowiedzi JSON:
return NextResponse.json({ message: 'Success!', data: { name: 'John Doe' } }, { status: 200 });
Odpowiedzi tekstowe
Użyj konstruktora new Response()
, aby zwrócić odpowiedzi w postaci zwykłego tekstu:
return new Response('Hello, world!', { status: 200, headers: { 'Content-Type': 'text/plain' } });
Przekierowania
Użyj NextResponse.redirect()
, aby przekierować użytkowników na inny adres URL:
import { redirect } from 'next/navigation';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
return NextResponse.redirect(new URL('/new-location', request.url));
}
Ustawianie nagłówków
Możesz ustawić niestandardowe nagłówki za pomocą opcji headers
w NextResponse.json()
lub new Response()
:
return NextResponse.json({ message: 'Success!' }, { status: 200, headers: { 'Cache-Control': 'no-cache' } });
Integracja z Middleware
Middleware pozwala na uruchomienie kodu przed obsłużeniem żądania przez Route Handler. Jest to przydatne do uwierzytelniania, autoryzacji, logowania i innych zagadnień przekrojowych.
Aby utworzyć middleware, stwórz plik o nazwie middleware.ts
(lub middleware.js
) w katalogu app
lub dowolnym podkatalogu. Middleware będzie miało zastosowanie do wszystkich tras w tym katalogu i jego podkatalogach.
// app/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/protected/:path*'], // Apply this middleware to paths starting with /protected/
};
Wyjaśnienie:
- Funkcja
middleware
sprawdza obecność tokena uwierzytelniającego w ciasteczkach żądania. - Jeśli tokena brakuje, przekierowuje użytkownika na stronę logowania.
- W przeciwnym razie pozwala na kontynuowanie żądania do Route Handlera.
- Obiekt
config
określa, że to middleware powinno być stosowane tylko do tras zaczynających się od/protected/
.
Obsługa błędów
Prawidłowa obsługa błędów jest kluczowa dla budowy solidnych interfejsów API. Możesz użyć bloków try...catch
do obsługi wyjątków i zwracania odpowiednich odpowiedzi o błędach.
export async function GET(request: Request) {
try {
// Simulate an error
throw new Error('Something went wrong!');
} catch (error: any) {
console.error('Error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
Wyjaśnienie:
- Blok
try...catch
przechwytuje wszelkie wyjątki, które wystąpią wewnątrz Route Handlera. - W bloku
catch
błąd jest logowany, a odpowiedź o błędzie jest zwracana z kodem statusu 500 Internal Server Error.
Odpowiedzi strumieniowe
Route Handlers obsługują odpowiedzi strumieniowe, co pozwala na stopniowe wysyłanie danych do klienta. Jest to szczególnie przydatne w przypadku dużych zbiorów danych lub długotrwałych procesów.
import { Readable } from 'stream';
import { NextResponse } from 'next/server';
async function* generateData() {
for (let i = 0; i < 10; i++) {
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate delay
yield `Data chunk ${i}\n`;
}
}
export async function GET(request: Request) {
const readableStream = Readable.from(generateData());
return new Response(readableStream, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
}
Wyjaśnienie:
- Funkcja
generateData
to asynchroniczny generator, który generuje fragmenty danych z opóźnieniem. - Metoda
Readable.from()
tworzy czytelny strumień z generatora. - Obiekt
Response
jest tworzony z czytelnym strumieniem jako ciałem, a nagłówekContent-Type
jest ustawiony natext/plain
.
Uwierzytelnianie i autoryzacja
Zabezpieczanie punktów końcowych API jest kluczowe. Możesz zaimplementować uwierzytelnianie i autoryzację za pomocą middleware lub bezpośrednio w swoich Route Handlers.
Uwierzytelnianie
Uwierzytelnianie weryfikuje tożsamość użytkownika wysyłającego żądanie. Typowe metody uwierzytelniania to:
- JWT (JSON Web Tokens): Generowanie tokena po pomyślnym zalogowaniu i weryfikowanie go przy kolejnych żądaniach.
- Uwierzytelnianie oparte na sesji: Używanie ciasteczek do przechowywania identyfikatorów sesji i weryfikowanie ich przy każdym żądaniu.
- OAuth: Delegowanie uwierzytelniania do dostawcy zewnętrznego, takiego jak Google czy Facebook.
Oto przykład uwierzytelniania JWT za pomocą middleware:
// app/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import jwt from 'jsonwebtoken';
const secret = process.env.JWT_SECRET || 'your-secret-key'; // Replace with a strong, randomly generated secret
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value;
if (!token) {
return NextResponse.json({ message: 'Authentication required' }, { status: 401 });
}
try {
jwt.verify(token, secret);
return NextResponse.next();
} catch (error) {
return NextResponse.json({ message: 'Invalid token' }, { status: 401 });
}
}
export const config = {
matcher: ['/api/protected/:path*'],
};
Autoryzacja
Autoryzacja określa, do jakich zasobów użytkownik ma dostęp. Zazwyczaj opiera się to na rolach lub uprawnieniach.
Możesz zaimplementować autoryzację w swoich Route Handlers, sprawdzając role lub uprawnienia użytkownika i zwracając błąd, jeśli nie ma on dostępu.
// app/api/admin/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
// Assume you have a function to get the user's role from the token or session
const userRole = await getUserRole(request);
if (userRole !== 'admin') {
return NextResponse.json({ message: 'Unauthorized' }, { status: 403 });
}
// Logic to retrieve admin data
const adminData = { message: 'Admin data' };
return NextResponse.json(adminData);
}
async function getUserRole(request: Request): Promise {
// Replace with your actual logic to extract the user's role from the request
// This could involve verifying a JWT token or checking a session
return 'admin'; // Example: hardcoded role for demonstration
}
Wdrażanie Route Handlers
Route Handlers są wdrażane jako funkcje bezserwerowe u wybranego dostawcy hostingu. Next.js obsługuje różne platformy wdrożeniowe, w tym Vercel, Netlify, AWS i inne.
W przypadku Vercel wdrożenie jest tak proste, jak podłączenie repozytorium Git do Vercel i wypchnięcie kodu. Vercel automatycznie wykrywa projekt Next.js i wdraża Route Handlers jako funkcje bezserwerowe.
Zaawansowane techniki
Funkcje brzegowe (Edge Functions)
Route Handlers mogą być wdrażane jako funkcje brzegowe (Edge Functions), które są wykonywane na brzegu sieci CDN, bliżej użytkowników. Może to znacznie zmniejszyć opóźnienia i poprawić wydajność.
Aby wdrożyć Route Handler jako funkcję brzegową, dodaj środowisko uruchomieniowe edge
do pliku route.ts
:
export const runtime = 'edge';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
return NextResponse.json({ message: 'Hello from the Edge!' });
}
Server Actions
Server Actions pozwalają na wykonywanie kodu po stronie serwera bezpośrednio z komponentów React. Route Handlers i Server Actions doskonale ze sobą współpracują, umożliwiając łatwe tworzenie złożonych aplikacji.
Oto przykład użycia Server Action do wywołania Route Handlera:
// app/components/MyComponent.tsx
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
async function handleSubmit(data: FormData) {
'use server';
const name = data.get('name');
const email = data.get('email');
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify({ name, email }),
});
if (response.ok) {
router.refresh(); // Refresh the page to reflect the changes
}
}
export default function MyComponent() {
const router = useRouter();
return (
);
}
Buforowanie (Caching)
Buforowanie może znacznie poprawić wydajność punktów końcowych API. Możesz użyć nagłówka Cache-Control
do kontrolowania, jak odpowiedzi są buforowane przez przeglądarki i sieci CDN.
return NextResponse.json({ message: 'Success!' }, { status: 200, headers: { 'Cache-Control': 'public, max-age=3600' } });
Ten przykład ustawia nagłówek Cache-Control
na public, max-age=3600
, co informuje przeglądarki i sieci CDN, aby buforowały odpowiedź przez jedną godzinę.
Najlepsze praktyki
- Używaj TypeScript: Wykorzystaj bezpieczeństwo typów TypeScript do poprawy jakości kodu i zapobiegania błędom.
- Waliduj żądania: Waliduj przychodzące żądania, aby zapewnić integralność danych i zapobiec złośliwym danym wejściowym.
- Obsługuj błędy z gracją: Zaimplementuj prawidłową obsługę błędów, aby dostarczać klientom informacyjne komunikaty o błędach.
- Zabezpiecz swoje punkty końcowe: Zaimplementuj uwierzytelnianie i autoryzację, aby chronić swoje punkty końcowe API.
- Używaj middleware: Używaj middleware do zagadnień przekrojowych, takich jak uwierzytelnianie, logowanie i walidacja żądań.
- Buforuj odpowiedzi: Używaj buforowania, aby poprawić wydajność swoich punktów końcowych API.
- Monitoruj swoje API: Monitoruj swoje API, aby szybko identyfikować i rozwiązywać problemy.
- Dokumentuj swoje API: Dokumentuj swoje API, aby ułatwić ich używanie innym deweloperom. Rozważ użycie narzędzi takich jak Swagger/OpenAPI do dokumentacji API.
Przykłady z życia wzięte
Oto kilka przykładów z życia wziętych, jak można wykorzystać Route Handlers:
- API e-commerce: Tworzenie punktów końcowych API do zarządzania produktami, zamówieniami i użytkownikami.
- API mediów społecznościowych: Tworzenie punktów końcowych API do publikowania postów, obserwowania użytkowników i pobierania osi czasu.
- API systemu zarządzania treścią (CMS): Tworzenie punktów końcowych API do zarządzania treścią, użytkownikami i ustawieniami.
- API analityki danych: Tworzenie punktów końcowych API do zbierania i analizowania danych. Na przykład, Route Handler mógłby odbierać dane z pikseli śledzących na różnych stronach internetowych i agregować te informacje do celów raportowania.
Przykład międzynarodowego e-commerce: Route Handler używany do pobierania cen produktów na podstawie kraju użytkownika. Punkt końcowy mógłby wykorzystać geolokalizację żądania (pochodzącą z adresu IP) do określenia lokalizacji użytkownika i zwrócenia cen w odpowiedniej walucie. Przyczynia się to do zlokalizowanego doświadczenia zakupowego.
Przykład globalnego uwierzytelniania: Route Handler implementujący uwierzytelnianie wieloskładnikowe (MFA) dla użytkowników na całym świecie. Mogłoby to obejmować wysyłanie kodów SMS lub korzystanie z aplikacji uwierzytelniających, z poszanowaniem przepisów dotyczących prywatności i infrastruktury telekomunikacyjnej różnych regionów.
Dostarczanie treści wielojęzycznych: Route Handler dostarczający treść w preferowanym języku użytkownika. Można to określić na podstawie nagłówka `Accept-Language` w żądaniu. Ten przykład podkreśla potrzebę prawidłowego kodowania UTF-8 i wsparcia dla języków pisanych od prawej do lewej, w stosownych przypadkach.
Podsumowanie
Next.js Route Handlers zapewniają potężny i elastyczny sposób tworzenia punktów końcowych API bezpośrednio w aplikacji Next.js. Wykorzystując Route Handlers, można z łatwością budować solidne interfejsy API, umieszczać logikę backendową obok komponentów React i korzystać z funkcji takich jak middleware, streaming i Edge Functions.
Ten kompleksowy przewodnik omówił wszystko, od podstawowej konfiguracji po zaawansowane techniki. Postępując zgodnie z najlepszymi praktykami opisanymi w tym przewodniku, możesz tworzyć wysokiej jakości interfejsy API, które są bezpieczne, wydajne i łatwe w utrzymaniu.