Création et Utilisation des API Routes dans Next.js
Introduction aux API Routes dans Next.js
Bienvenue dans ce module de notre cours "Maîtriser Next.js : Construire des Applications Web Full-Stack Performantes et Scalables". Next.js est bien plus qu'un simple framework pour créer des interfaces utilisateur réactives ; c'est un framework full-stack qui permet de construire des applications complètes, du front-end au back-end.
Au cœur de cette capacité full-stack se trouvent les API Routes. Imaginez-vous devoir gérer des données, interagir avec une base de données, ou intégrer des services tiers dans votre application Next.js. Traditionnellement, cela nécessiterait un serveur back-end séparé (Node.js avec Express, Python avec Django/Flask, etc.). Next.js simplifie grandement ce processus en vous permettant de créer des endpoints API directement au sein de votre projet, et ce, de manière très naturelle.
Les API Routes sont des fonctions serverless (sans serveur) qui résident sur votre serveur Next.js. Elles vous permettent de construire des APIs RESTful ou GraphQL sans avoir à configurer un serveur back-end complexe séparément. Elles sont idéales pour :
- Récupérer et manipuler des données depuis une base de données.
- Gérer l'authentification et l'autorisation.
- Traiter des formulaires.
- Intégrer des services tiers (passerelles de paiement, APIs externes, etc.).
Comprendre et maîtriser les API Routes est essentiel pour transformer vos applications Next.js de simples "sites web" en de véritables "applications web" interactives et performantes, capables de gérer toute la logique métier nécessaire.
1. Comprendre les API Routes
1.1 Qu'est-ce qu'une API Route ?
Une API Route dans Next.js est essentiellement une fonction Node.js qui s'exécute côté serveur. Elle est définie dans le dossier pages/api de votre projet et est automatiquement traitée comme un point d'accès API. Chaque fichier dans ce dossier (.js, .ts, .jsx, ou .tsx) devient un endpoint API accessible via une URL correspondante.
Par exemple, si vous créez un fichier pages/api/hello.js, il sera accessible via l'URL /api/hello.
1.2 Structure et Fonctionnement
Les API Routes fonctionnent comme des fonctions serverless (ou "lambdas"). Lorsque Next.js déploie votre application, chaque API Route est transformée en une fonction serverless indépendante. Cela signifie qu'elles sont :
- Scalables : Elles s'adaptent automatiquement à la charge.
- Rentables : Vous ne payez que pour l'exécution réelle.
- Faciles à déployer : Le déploiement est intégré au processus de déploiement de votre application Next.js.
Chaque API Route doit exporter une fonction par défaut qui accepte deux arguments :
req(request) : Un objet représentant la requête HTTP entrante. Il contient des informations comme la méthode HTTP (GET,POST, etc.), les en-têtes, les paramètres de requête (query), et le corps de la requête (body).res(response) : Un objet permettant de construire et d'envoyer la réponse HTTP. Il permet de définir le code de statut, les en-têtes, et le corps de la réponse.
// pages/api/my-first-api.js
export default function handler(req, res) {
// Votre logique API ici
res.status(200).json({ name: 'John Doe' });
}
1.3 Avantages des API Routes
- Intégration transparente : Pas besoin de configurer un serveur back-end séparé.
- Développement Full-Stack unifié : Gérez votre front-end et votre back-end dans un seul et même projet.
- Fonctionnalités Node.js complètes : Accès à l'écosystème Node.js (modules, npm, etc.).
- Performance : Pas de chargement de bundles JavaScript côté client pour ces fonctions.
- Sécurité : Les opérations sensibles (accès à la base de données, secrets API) restent côté serveur et ne sont pas exposées au client.
2. Créer sa Première API Route
Créons une API Route simple qui renvoie un message de bienvenue.
2.1 Convention de Nommage et Localisation
Toutes les API Routes doivent résider dans le dossier pages/api. La structure des fichiers dans ce dossier détermine les chemins d'accès de vos API.
pages/api/hello.js->/api/hellopages/api/users/index.js->/api/userspages/api/users/[id].js->/api/users/123(API Route dynamique, nous y reviendrons)
2.2 Exemple : Une API Route "Hello World"
Créons le fichier pages/api/hello.js :
// pages/api/hello.js
/**
* Cette fonction est l'export par défaut et gérera toutes les requêtes
* envoyées à l'endpoint /api/hello.
* @param {import('next').NextApiRequest} req - L'objet requête.
* @param {import('next').NextApiResponse} res - L'objet réponse.
*/
export default function handler(req, res) {
// Nous vérifions la méthode HTTP de la requête.
// C'est une bonne pratique pour gérer différentes actions pour différentes méthodes.
if (req.method === 'GET') {
// Si c'est une requête GET, nous renvoyons un simple message JSON.
// res.status(200) définit le code de statut HTTP à 200 (OK).
// .json() envoie une réponse JSON.
res.status(200).json({ message: 'Hello from Next.js API Routes!', timestamp: new Date().toISOString() });
} else {
// Si la méthode n'est pas GET, nous renvoyons une erreur 405 (Method Not Allowed).
res.setHeader('Allow', ['GET']); // Indique les méthodes autorisées.
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
Explication du code :
export default function handler(req, res): C'est la signature standard d'une API Route.handlerest le nom de la fonction, mais vous pouvez utiliser n'importe quel nom.req.method === 'GET': L'objetreqcontient la propriétémethodqui indique la méthode HTTP utilisée par le client (GET,POST,PUT,DELETE, etc.). Il est crucial de vérifier cette propriété pour implémenter une logique différente selon le type de requête.res.status(200): Définit le code de statut HTTP de la réponse.200signifie "OK". D'autres codes courants incluent404(Not Found),401(Unauthorized),500(Internal Server Error), etc..json({ ... }): Envoie une réponse au format JSON. C'est la méthode la plus courante pour les APIs. Next.js gère automatiquement les en-têtesContent-Type: application/json..end(): Termine la réponse sans envoyer de corps de réponse. Utile pour les codes d'erreur ou les requêtes qui ne nécessitent pas de données en retour.res.setHeader('Allow', ['GET']): Définit un en-tête HTTP. Dans ce cas, il informe le client que seule la méthodeGETest autorisée pour cet endpoint.
Pour tester cette API Route, démarrez votre application Next.js en mode développement (npm run dev ou yarn dev) et accédez à http://localhost:3000/api/hello dans votre navigateur ou via un outil comme Postman/Insomnia.
3. Gérer Différentes Méthodes HTTP
Une API robuste doit être capable de gérer différentes méthodes HTTP (GET, POST, PUT, DELETE, etc.) pour les opérations CRUD (Create, Read, Update, Delete). Bien que chaque méthode puisse avoir son propre endpoint (par exemple, /api/users/add pour POST et /api/users/get pour GET), il est plus courant et plus RESTful de gérer différentes méthodes sur le même endpoint.
Exemple : Une API Route pour la gestion de tâches (Todos)
Créons le fichier pages/api/todos.js qui gérera la récupération (GET) et la création (POST) de tâches. Pour cet exemple, nous utiliserons un tableau en mémoire simple pour simuler une base de données.
// pages/api/todos.js
// Ceci simule une base de données. En production, vous vous connecteriez à une vraie DB.
let todos = [
{ id: 1, text: 'Apprendre Next.js API Routes', completed: false },
{ id: 2, text: 'Construire une application full-stack', completed: false },
];
let nextId = 3; // Pour attribuer des IDs uniques
export default function handler(req, res) {
switch (req.method) {
case 'GET':
// Récupérer toutes les tâches
res.status(200).json(todos);
break;
case 'POST':
// Ajouter une nouvelle tâche
// Le corps de la requête est accessible via req.body
// Il est recommandé de valider les données de req.body en production.
const { text } = req.body;
if (!text) {
return res.status(400).json({ message: 'Le texte de la tâche est requis.' });
}
const newTodo = {
id: nextId++,
text,
completed: false,
};
todos.push(newTodo);
res.status(201).json(newTodo); // 201 Created pour une nouvelle ressource
break;
// Vous pouvez ajouter d'autres méthodes ici
// case 'PUT':
// // Mettre à jour une tâche existante
// // Nécessiterait l'ID de la tâche (par exemple via req.query.id ou req.body.id)
// // ...
// break;
// case 'DELETE':
// // Supprimer une tâche
// // Nécessiterait l'ID de la tâche
// // ...
// break;
default:
// Si la méthode n'est pas gérée, renvoyer 405 Method Not Allowed
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
Explication du code :
switch (req.method): Une structureswitchest une manière propre et efficace de gérer différentes méthodes HTTP sur le même endpoint.- GET : Renvoie le tableau
todoscomplet. - POST :
req.body: Contient les données envoyées dans le corps de la requête. Pour les requêtes POST avecContent-Type: application/json, Next.js parse automatiquement le corps de la requête et le rend disponible sous forme d'objet JavaScript dansreq.body.- Validation : Une vérification simple
if (!text)est effectuée. En production, cette validation serait beaucoup plus robuste. res.status(201): Le code de statut201 Createdest utilisé lorsqu'une nouvelle ressource a été créée avec succès.
defaultcase : Capture toutes les méthodes non explicitement gérées et renvoie un405 Method Not Allowed, informant le client des méthodes supportées via l'en-têteAllow.
Pour tester :
- GET : Accédez à
http://localhost:3000/api/todosdans votre navigateur. - POST : Utilisez un outil comme
curlou Postman/Insomnia pour envoyer une requête POST àhttp://localhost:3000/api/todosavec un corps JSON comme{"text": "Ma nouvelle tâche"}.
4. Interaction Client-Serveur
Maintenant que vous savez créer des API Routes, voyons comment les appeler depuis votre front-end Next.js. La méthode la plus courante est d'utiliser l'API fetch native du navigateur ou une bibliothèque comme Axios.
Exemple : Afficher et ajouter des tâches depuis une page Next.js
Créons une page pages/index.js simple pour interagir avec notre API de tâches.
// pages/index.js
import { useState, useEffect } from 'react';
export default function HomePage() {
const [todos, setTodos] = useState([]);
const [newTodoText, setNewTodoText] = useState('');
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
// Fonction pour charger les tâches depuis l'API
const fetchTodos = async () => {
try {
setLoading(true);
const response = await fetch('/api/todos'); // Appel à notre API Route
if (!response.ok) {
throw new Error(`Erreur HTTP: ${response.status}`);
}
const data = await response.json();
setTodos(data);
} catch (err) {
setError(`Impossible de charger les tâches: ${err.message}`);
} finally {
setLoading(false);
}
};
// Charger les tâches au montage du composant
useEffect(() => {
fetchTodos();
}, []);
// Fonction pour ajouter une nouvelle tâche
const addTodo = async (e) => {
e.preventDefault(); // Empêche le rechargement de la page
setError(null); // Réinitialise l'erreur
if (!newTodoText.trim()) {
setError('Veuillez saisir un texte pour la tâche.');
return;
}
try {
const response = await fetch('/api/todos', {
method: 'POST', // Spécifie la méthode POST
headers: {
'Content-Type': 'application/json', // Indique que nous envoyons du JSON
},
body: JSON.stringify({ text: newTodoText }), // Convertit l'objet JS en chaîne JSON
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || `Erreur HTTP: ${response.status}`);
}
const addedTodo = await response.json();
setTodos((prevTodos) => [...prevTodos, addedTodo]); // Ajoute la nouvelle tâche à l'état local
setNewTodoText(''); // Réinitialise le champ de saisie
} catch (err) {
setError(`Impossible d'ajouter la tâche: ${err.message}`);
}
};
if (loading) return <p>Chargement des tâches...</p>;
if (error) return <p style={{ color: 'red' }}>Erreur: {error}</p>;
return (
<div>
<h1>Mes Tâches</h1>
<form onSubmit={addTodo}>
<input
type="text"
value={newTodoText}
onChange={(e) => setNewTodoText(e.target.value)}
placeholder="Nouvelle tâche..."
/>
<button type="submit">Ajouter Tâche</button>
</form>
<ul>
{todos.length === 0 ? (
<li>Aucune tâche pour le moment.</li>
) : (
todos.map((todo) => (
<li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</li>
))
)}
</ul>
</div>
);
}
Explication du code :
useStateetuseEffect: Hooks React standards pour gérer l'état local et les effets de bord.fetch('/api/todos'): L'appel API est fait à l'URL relative/api/todos, qui correspond à notre API Route. Next.js (et le navigateur) sait que cela doit être une requête HTTP vers le même serveur.- Pour
GET: L'appelfetchpar défaut est une requête GET. Nous vérifionsresponse.okpour les erreurs HTTP et utilisonsawait response.json()pour parser la réponse. - Pour
POST:method: 'POST': Spécifie explicitement la méthode HTTP.headers: { 'Content-Type': 'application/json' }: Indique au serveur que le corps de la requête est au format JSON. C'est crucial pour quereq.bodysoit correctement parsé dans votre API Route.body: JSON.stringify({ text: newTodoText }): Convertit l'objet JavaScript en une chaîne JSON avant de l'envoyer.
Ce simple exemple illustre la puissance de Next.js en tant que framework full-stack, vous permettant de développer des fonctionnalités côté client et côté serveur de manière cohérente au sein d'un seul projet.
5. Concepts Avancés et Bonnes Pratiques
5.1 API Routes Dynamiques
Tout comme les pages Next.js peuvent être dynamiques (ex: pages/posts/[slug].js), les API Routes le peuvent aussi. Cela vous permet de créer des endpoints qui acceptent des paramètres dans l'URL.
Exemple : pages/api/todos/[id].js
// pages/api/todos/[id].js
// Cette fois, la "base de données" sera locale à cette route
let todos = [
{ id: 1, text: 'Faire les courses', completed: false },
{ id: 2, text: 'Préparer la leçon Next.js', completed: false },
];
export default function handler(req, res) {
const { id } = req.query; // Accès aux paramètres dynamiques via req.query
// Convertir l'ID en nombre pour la comparaison
const todoId = parseInt(id, 10);
const todoIndex = todos.findIndex(t => t.id === todoId);
if (todoIndex === -1 && req.method !== 'POST') { // POST pourrait créer une nouvelle ressource avec un ID spécifique si désiré
return res.status(404).json({ message: 'Tâche non trouvée.' });
}
switch (req.method) {
case 'GET':
// Récupérer une tâche spécifique
res.status(200).json(todos[todoIndex]);
break;
case 'PUT':
// Mettre à jour une tâche spécifique
const { text, completed } = req.body;
if (text === undefined && completed === undefined) {
return res.status(400).json({ message: 'Le corps de la requête doit contenir "text" ou "completed".' });
}
todos[todoIndex] = {
...todos[todoIndex],
...(text !== undefined && { text }),
...(completed !== undefined && { completed }),
};
res.status(200).json(todos[todoIndex]);
break;
case 'DELETE':
// Supprimer une tâche spécifique
todos = todos.filter(t => t.id !== todoId);
res.status(204).end(); // 204 No Content pour une suppression réussie
break;
default:
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
const { id } = req.query;: Les paramètres dynamiques de l'URL (entre crochets[]) sont accessibles viareq.query.- Cet endpoint peut être appelé par exemple par
/api/todos/1pour récupérer, mettre à jour ou supprimer la tâche avec l'ID 1.
5.2 Middleware dans les API Routes
Next.js ne fournit pas de middleware Express-like natif pour les API Routes. Cependant, vous pouvez implémenter des logiques de middleware manuellement en composant des fonctions.
Exemple simple de "middleware" d'authentification :
// pages/api/secure-data.js
function withAuth(handler) {
return async (req, res) => {
// Vérifier si un token d'authentification est présent dans les headers
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ message: 'Accès non autorisé. Token manquant.' });
}
const token = authHeader.split(' ')[1];
// Ici, vous vérifierez le token (ex: avec JWT.verify)
// Pour cet exemple, nous allons juste simuler une validation
if (token !== 'MY_SECRET_TOKEN') {
return res.status(403).json({ message: 'Token invalide.' });
}
// Si l'authentification réussit, passer la main au handler original
return handler(req, res);
};
}
// Votre API Route sécurisée
function secureDataHandler(req, res) {
if (req.method === 'GET') {
res.status(200).json({ data: 'Ceci est une donnée ultra-secrète !' });
} else {
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
// Exporter le handler sécurisé
export default withAuth(secureDataHandler);
5.3 Variables d'Environnement
Pour des informations sensibles (clés d'API, chaînes de connexion à la base de données), utilisez des variables d'environnement. Next.js les charge automatiquement depuis des fichiers .env.local.
- Créez un fichier
.env.localà la racine de votre projet :DB_CONNECTION_STRING=mongodb://localhost:27017/mydb API_SECRET_KEY=supersecretkey123 - Accédez-y dans votre API Route :
process.env.DB_CONNECTION_STRING. Attention : Les variables d'environnement préfixées parNEXT_PUBLIC_sont exposées au client. N'utilisez pas ce préfixe pour les secrets côté serveur.
5.4 Gestion des Erreurs Robuste
Toujours anticiper les erreurs et renvoyer des réponses significatives au client.
- Utilisez les codes de statut HTTP appropriés (
4xxpour les erreurs client,5xxpour les erreurs serveur). - Incluez un message d'erreur clair dans le corps de la réponse JSON.
- Utilisez des blocs
try...catchpour les opérations potentiellement échouantes (accès à la base de données, appels API externes).
5.5 Sécurité
- Validation des entrées : Ne faites jamais confiance aux données provenant du client (
req.body,req.query,req.headers). Validez toujours toutes les entrées pour prévenir les injections SQL, les XSS, etc. (librairies commejoiouyuppeuvent aider). - Authentification/Autorisation : Assurez-vous que seul les utilisateurs autorisés peuvent accéder à certaines ressources ou effectuer certaines actions.
- CORS (Cross-Origin Resource Sharing) : Si votre front-end est sur un domaine différent de votre API (ce qui est rare avec les API Routes Next.js mais peut arriver si vous les exposez publiquement à d'autres clients), vous devrez gérer les en-têtes CORS.
5.6 Déploiement
Lorsque vous déployez votre application Next.js sur Vercel (la plateforme recommandée par Next.js), vos API Routes sont automatiquement transformées en fonctions serverless. Cela signifie que vous n'avez pas à vous soucier de la gestion de serveurs, de la mise à l'échelle ou de l'équilibrage de charge ; Vercel s'en occupe.
Conclusion
Les API Routes de Next.js représentent un pilier fondamental pour la construction d'applications web full-stack performantes et scalables. Elles permettent aux développeurs de Next.js d'étendre la logique de leurs applications bien au-delà de la simple interface utilisateur, en intégrant des fonctionnalités côté serveur directement dans leur projet.
Nous avons exploré :
- Le concept et l'importance des API Routes.
- La création d'API Routes simples (
GET,POST). - La gestion des différentes méthodes HTTP.
- L'interaction entre le client Next.js et vos API Routes.
- Des concepts avancés tels que les routes dynamiques, les "middlewares" et les bonnes pratiques de sécurité.
En tirant parti des API Routes, vous pouvez désormais concevoir des applications Next.js capables de :
- Interagir avec des bases de données.
- Gérer l'authentification utilisateur.
- Intégrer des services tiers.
- Exécuter des logiques métier complexes côté serveur.
Continuez à expérimenter avec différents types de requêtes, de validations et de manipulations de données. La maîtrise des API Routes vous ouvrira les portes vers la création d'applications Next.js toujours plus puissantes et complètes.