Créer et Utiliser les API Routes d'Astro
Contexte du cours : Maîtrisez Astro : Créez des Sites Web Ultra-Performants et SEO-Friendly avec l'Architecture en Îles
Dans notre parcours pour maîtriser Astro et construire des sites web performants, nous avons exploré comment Astro excelle dans la génération de HTML statique et l'intégration de composants UI isolés via son architecture en îles. Cependant, un site web moderne ne se limite pas toujours à la seule présentation de contenu statique. Souvent, nous avons besoin de :
- Récupérer des données dynamiquement depuis une base de données ou un service externe.
- Gérer des soumissions de formulaires.
- Implémenter une logique côté serveur (authentification, traitement d'informations).
- Créer des points de terminaison pour des applications front-end.
C'est là qu'interviennent les API Routes d'Astro, une fonctionnalité puissante qui transforme Astro d'un simple générateur de site statique en un framework capable de gérer des requêtes serveur complètes. Elles vous permettent de construire des backends légers ou des micro-services directement au sein de votre projet Astro, en tirant parti de la même infrastructure de routage basée sur les fichiers.
Introduction aux API Routes d'Astro
Les API Routes dans Astro sont des fichiers TypeScript ou JavaScript situés dans votre répertoire src/pages/ qui exportent des fonctions correspondant aux méthodes HTTP. Lorsqu'une requête arrive sur le chemin de ces fichiers, Astro exécute la fonction exportée et renvoie la réponse que vous définissez.
Pourquoi utiliser les API Routes ?
- Logique Côté Serveur : Exécutez du code qui ne devrait pas être exposé au client (accès à des bases de données, clés API, etc.).
- Récupération de Données (Server-Side Data Fetching) : Récupérez des données avant même que le client ne les demande, ou servez des données JSON à des composants client hydratés.
- Gestion des Formulaires : Traitez les soumissions de formulaires sans avoir besoin d'un backend Node.js, Express ou PHP séparé.
- Points de Terminaison API : Créez des endpoints pour des applications client-side (SPA, applications mobiles) ou d'autres services.
- Simplicité : Intégration transparente avec le système de routage existant d'Astro, réduisant la complexité du projet.
Comment ça fonctionne ?
Astro utilise un système de routage basé sur les fichiers, ce qui signifie que chaque fichier *.astro, *.md, ou maintenant *.js / *.ts / *.mjs dans src/pages/ devient une route. Pour les API Routes, vous créerez des fichiers JavaScript/TypeScript dans src/pages/ qui exportent des fonctions nommées d'après les méthodes HTTP (par exemple, GET, POST).
Prérequis
Avant de plonger dans la création d'API Routes, assurez-vous d'avoir :
- Une installation fonctionnelle d'Astro.
- Des connaissances de base en JavaScript/TypeScript, notamment la syntaxe ES Modules (
export,import). - Une compréhension des méthodes HTTP (
GET,POST,PUT,DELETE, etc.) et des codes de statut HTTP.
Comprendre les API Routes d'Astro
Les Fichiers de Route (Route Files)
Une API Route est un fichier JavaScript (.js), TypeScript (.ts), ou Module JavaScript (.mjs) situé dans le dossier src/pages/.
Exemple de structure :
src/
└── pages/
├── index.astro
├── about.astro
└── api/
├── produits.ts <-- Cette route répondra à /api/produits
└── utilisateurs/
└── [id].ts <-- Cette route répondra à /api/utilisateurs/:id
Les Méthodes HTTP Supportées
Dans chaque fichier de route API, vous devez exporter une fonction qui correspond à la méthode HTTP que vous souhaitez gérer. Astro supporte les méthodes HTTP standard :
export async function GET()export async function POST()export async function PUT()export async function DELETE()export async function PATCH()export async function OPTIONS()export async function HEAD()
Chaque fonction exportée reçoit un objet APIContext comme premier argument.
L'Objet APIContext
L'APIContext est un objet puissant qui contient des informations sur la requête entrante et permet d'accéder à divers utilitaires. Voici ses propriétés clés :
request: L'objetRequeststandard du Web Platform API. Il contient toutes les informations sur la requête HTTP entrante (URL, headers, corps de la requête, méthode, etc.).params: Un objet contenant les paramètres dynamiques de l'URL (par exemple,iddans/api/produits/[id].ts).url: L'objetURLstandard du Web Platform API représentant l'URL complète de la requête.cookies: Un objetAstro.cookiespour lire et écrire des cookies.redirect: Une fonction pour rediriger l'utilisateur vers une autre URL.locals: Un objet pour stocker des données spécifiques à la requête, accessibles via les middlewares.
Retourner des Réponses
Une API Route doit retourner un objet Response standard du Web Platform API. C'est ainsi que vous envoyez des données, des en-têtes et des codes de statut au client.
Exemples courants de Response :
- Réponse JSON : C'est le cas le plus fréquent pour les API.
Ou, plus simplement avec la fonction utilitairereturn new Response(JSON.stringify({ message: "Succès" }), { status: 200, headers: { "Content-Type": "application/json" } });json()d'Astro pour les routes API :import { json } from "astro/runtime"; export async function GET() { return json({ message: "Succès" }); // Définit automatiquement Content-Type et status 200 } - Réponse Texte :
return new Response("Hello, World!", { status: 200, headers: { "Content-Type": "text/plain" } }); - Redirection :
return new Response(null, { status: 302, headers: { Location: "/nouvelle-page" } }); // Ou en utilisant l'utilitaire redirect de l'APIContext: // export async function GET({ redirect }) { // return redirect("/nouvelle-page"); // }
Mise en Pratique : Créer une API Route Simple (GET)
Nous allons créer une API Route simple qui renvoie une liste de produits fictifs.
-
Créez le fichier de la route : Dans votre projet Astro, créez un nouveau fichier
src/pages/api/produits.ts. -
Ajoutez le code suivant :
// src/pages/api/produits.ts import type { APIContext } from "astro"; // Simule une base de données ou une source de données const produits = [ { id: "1", nom: "Clavier mécanique", prix: 120.00, stock: 50 }, { id: "2", nom: "Souris gaming", prix: 75.50, stock: 30 }, { id: "3", nom: "Écran 27 pouces", prix: 350.00, stock: 20 }, ]; /** * Gère les requêtes GET pour récupérer la liste des produits. * @param {APIContext} context - L'objet de contexte de l'API. * @returns {Response} Une réponse JSON contenant la liste des produits. */ export async function GET({ url }: APIContext): Promise<Response> { // Optionnel : Gérer les paramètres de requête (ex: /api/produits?limit=2) const limit = url.searchParams.get("limit"); let produitsFiltres = produits; if (limit) { const numLimit = parseInt(limit, 10); if (!isNaN(numLimit) && numLimit > 0) { produitsFiltres = produits.slice(0, numLimit); } } return new Response(JSON.stringify(produitsFiltres), { status: 200, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", // À adapter pour la production }, }); } -
Explication du code :
import type { APIContext } from "astro";: Importe le typeAPIContextpour une meilleure complétion et vérification de type si vous utilisez TypeScript.const produits = [...]: Nous définissons une simple liste de produits en mémoire pour cet exemple. En production, ce serait un appel à une base de données ou à un autre service.export async function GET({ url }: APIContext): Promise<Response>: C'est l'exportation principale. Astro détectera cette fonction et l'exécutera lorsqu'une requêteGETsera faite à/api/produits.- Nous déstructurons
urlde l'APIContextpour accéder aux paramètres de requête (?limit=2). - La fonction est
asynccar les opérations API (comme la récupération de données) sont généralement asynchrones. - Elle retourne une
Promise<Response>, indiquant qu'elle résoudra en un objetResponse.
- Nous déstructurons
url.searchParams.get("limit"): Permet de récupérer la valeur du paramètrelimitdans l'URL. Par exemple, pour?limit=2.new Response(JSON.stringify(produitsFiltres), {...}): Crée l'objetResponseà renvoyer au client.JSON.stringify(produitsFiltres): Convertit notre tableau JavaScript en une chaîne JSON.status: 200: Indique que la requête a été traitée avec succès (code "OK").headers: { "Content-Type": "application/json", ... }: Indique au navigateur que le contenu est du JSON.Access-Control-Allow-Originest ajouté pour le développement local si vous testez depuis une autre origine (par exemple, un client React s'exécutant sur un port différent). Attention : En production, il est crucial de sécuriser cette en-tête.
-
Testez la route :
- Lancez votre serveur de développement Astro :
npm run devouyarn dev. - Ouvrez votre navigateur et naviguez vers
http://localhost:4321/api/produits. - Vous devriez voir un JSON affichant la liste des produits.
- Essayez
http://localhost:4321/api/produits?limit=1pour voir le filtrage.
- Lancez votre serveur de développement Astro :
Gérer les Requêtes POST et les Données
Maintenant, ajoutons la capacité à ajouter un nouveau produit via une requête POST. Nous allons ajouter cette logique dans le même fichier src/pages/api/produits.ts.
-
Modifiez le fichier
src/pages/api/produits.ts:// src/pages/api/produits.ts import type { APIContext } from "astro"; // Simule une base de données ou une source de données interface Produit { id: string; nom: string; prix: number; stock: number; } const produits: Produit[] = [ { id: "1", nom: "Clavier mécanique", prix: 120.00, stock: 50 }, { id: "2", nom: "Souris gaming", prix: 75.50, stock: 30 }, { id: "3", nom: "Écran 27 pouces", prix: 350.00, stock: 20 }, ]; /** * Gère les requêtes GET pour récupérer la liste des produits. * ... (code GET inchangé) ... */ export async function GET({ url }: APIContext): Promise<Response> { const limit = url.searchParams.get("limit"); let produitsFiltres = produits; if (limit) { const numLimit = parseInt(limit, 10); if (!isNaN(numLimit) && numLimit > 0) { produitsFiltres = produits.slice(0, numLimit); } } return new Response(JSON.stringify(produitsFiltres), { status: 200, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, }); } /** * Gère les requêtes POST pour ajouter un nouveau produit. * @param {APIContext} context - L'objet de contexte de l'API. * @returns {Response} Une réponse JSON avec le nouveau produit ou un message d'erreur. */ export async function POST({ request }: APIContext): Promise<Response> { try { const nouveauProduitData: Partial<Produit> = await request.json(); // Tente de parser le corps de la requête en JSON // Validation simple if (!nouveauProduitData.nom || !nouveauProduitData.prix || nouveauProduitData.stock === undefined) { return new Response( JSON.stringify({ message: "Les champs 'nom', 'prix' et 'stock' sont requis." }), { status: 400, headers: { "Content-Type": "application/json" } } ); } if (typeof nouveauProduitData.nom !== 'string' || typeof nouveauProduitData.prix !== 'number' || typeof nouveauProduitData.stock !== 'number') { return new Response( JSON.stringify({ message: "Types de données invalides pour 'nom', 'prix' ou 'stock'." }), { status: 400, headers: { "Content-Type": "application/json" } } ); } // Générer un ID unique simple (pour cet exemple) const id = (produits.length + 1).toString(); const nouveauProduit: Produit = { id, nom: nouveauProduitData.nom, prix: nouveauProduitData.prix, stock: nouveauProduitData.stock, }; produits.push(nouveauProduit); // Ajoute le nouveau produit à notre liste simulée return new Response(JSON.stringify(nouveauProduit), { status: 201, // 201 Created : Indique qu'une nouvelle ressource a été créée headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, }); } catch (error) { console.error("Erreur lors de l'ajout du produit:", error); return new Response( JSON.stringify({ message: "Erreur interne du serveur lors du traitement de la requête." }), { status: 500, headers: { "Content-Type": "application/json" } } ); } } -
Explication du code
POST:export async function POST({ request }: APIContext): Promise<Response>: Astro appellera cette fonction lorsqu'une requêtePOSTsera faite à/api/produits. Nous déstructuronsrequestde l'APIContext.await request.json(): C'est la partie cruciale. L'objetrequest(qui est une instance deRequestdu Web API) possède des méthodes pour lire le corps de la requête :.json(): Pour les requêtes avecContent-Type: application/json..text(): Pour le texte brut..formData(): Pour les données de formulaire (Content-Type: application/x-www-form-urlencodedoumultipart/form-data).
- Validation : Nous ajoutons une validation simple pour s'assurer que les champs essentiels (
nom,prix,stock) sont présents et ont le bon type. Si la validation échoue, nous renvoyons unstatus: 400(Bad Request) avec un message d'erreur. - Création du produit : Un
idsimple est généré, et le nouveau produit est ajouté à notre tableauproduits(qui, rappelons-le, est en mémoire et sera réinitialisé au redémarrage du serveur). status: 201: Indique que la ressource a été créée avec succès.try...catch: Il est toujours bon d'envelopper votre logique dans un bloctry...catchpour gérer les erreurs inattendues et renvoyer unstatus: 500(Internal Server Error) si quelque chose tourne mal.
-
Testez la route
POST: Pour tester une requêtePOST, vous ne pouvez pas simplement utiliser le navigateur. Vous avez besoin d'un outil comme :- Postman
- Insomnia
- VScode avec l'extension REST Client
- Un simple formulaire HTML / JavaScript
Exemple avec VScode REST Client (fichier
request.http) :POST http://localhost:4321/api/produits Content-Type: application/json { "nom": "Casque audio sans fil", "prix": 150.00, "stock": 25 } ### GET http://localhost:4321/api/produitsExécutez la requête
POSTd'abord, puis la requêteGET. Vous devriez voir le nouveau produit ajouté à la liste.
Routes Dynamiques et Paramètres
Les API Routes supportent également les paramètres dynamiques, tout comme les pages Astro. Par exemple, pour récupérer, modifier ou supprimer un produit spécifique par son ID.
-
Créez le fichier de la route dynamique : Créez
src/pages/api/produits/[id].ts. Le[id]indique qu'il s'agit d'un paramètre dynamique. -
Ajoutez le code suivant (exemple GET par ID) :
// src/pages/api/produits/[id].ts import type { APIContext } from "astro"; // Simule la même source de données que produits.ts pour la cohérence const produits = [ { id: "1", nom: "Clavier mécanique", prix: 120.00, stock: 50 }, { id: "2", nom: "Souris gaming", prix: 75.50, stock: 30 }, { id: "3", nom: "Écran 27 pouces", prix: 350.00, stock: 20 }, ]; /** * Gère les requêtes GET pour récupérer un produit spécifique par son ID. * @param {APIContext} context - L'objet de contexte de l'API. * @returns {Response} Une réponse JSON avec le produit ou un message d'erreur. */ export async function GET({ params }: APIContext): Promise<Response> { const { id } = params; // Récupère l'ID du produit depuis les paramètres de l'URL const produit = produits.find((p) => p.id === id); if (!produit) { return new Response(JSON.stringify({ message: `Produit avec l'ID ${id} non trouvé.` }), { status: 404, // 404 Not Found : La ressource n'existe pas headers: { "Content-Type": "application/json" }, }); } return new Response(JSON.stringify(produit), { status: 200, headers: { "Content-Type": "application/json" }, }); } -
Explication du code :
export async function GET({ params }: APIContext): Nous déstructuronsparamsde l'APIContext.const { id } = params;: C'est ainsi que vous accédez à la valeur du paramètre[id]dans l'URL. Si l'URL était/api/produits/123,params.idserait"123".- La logique de recherche et de renvoi est similaire à l'exemple précédent, avec une gestion du cas où le produit n'est pas trouvé (renvoi d'un
status: 404).
-
Testez la route dynamique :
- Accédez à
http://localhost:4321/api/produits/1dans votre navigateur. Vous devriez voir les détails du clavier. - Accédez à
http://localhost:4321/api/produits/99pour tester le cas "produit non trouvé".
- Accédez à
Gestion des Erreurs et Bonnes Pratiques
- Utilisez les Bons Codes de Statut HTTP : C'est fondamental pour la communication API.
200 OK: Requête réussie.201 Created: Une nouvelle ressource a été créée.204 No Content: Requête réussie, mais pas de contenu à renvoyer (par exemple, pour une suppression réussie).400 Bad Request: La requête du client est mal formée ou invalide.401 Unauthorized: L'utilisateur n'est pas authentifié.403 Forbidden: L'utilisateur est authentifié mais n'a pas les permissions nécessaires.404 Not Found: La ressource demandée n'existe pas.405 Method Not Allowed: La méthode HTTP n'est pas supportée pour cette ressource.500 Internal Server Error: Une erreur inattendue est survenue côté serveur.
- Messages d'Erreur Clairs : Lorsque vous renvoyez des erreurs, incluez des messages explicites en JSON pour aider les développeurs client à comprendre ce qui s'est passé.
- Sécurité (Authentification/Autorisation) : Pour des applications réelles, vous devrez implémenter des mécanismes d'authentification (tokens, sessions) et d'autorisation pour protéger vos API Routes. Astro propose des middlewares qui sont parfaits pour cette tâche (non couvert en détail ici, mais c'est l'endroit où le faire).
- CORS (Cross-Origin Resource Sharing) : Si votre frontend est servi sur une origine différente de votre API (par exemple, localement sur des ports différents, ou un frontend sur
example.comet une API surapi.example.com), vous devrez gérer les en-têtes CORS (Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers). Soyez très précis avecAccess-Control-Allow-Originen production. - Modularisation : Pour des API Routes plus complexes, déplacez la logique métier (accès à la base de données, validation complexe) dans des fonctions ou des modules séparés (par exemple, dans
src/lib/ousrc/services/) et importez-les dans vos fichiers de route. Cela rend votre code plus propre, testable et maintenable. - Type Safety avec TypeScript : Utiliser TypeScript pour vos API Routes améliore considérablement la robustesse en détectant les erreurs de type à la compilation. Définissez des interfaces pour vos requêtes et réponses JSON.
Intégration avec des Composants Côté Client (Îles Astro)
Le grand avantage des API Routes est qu'elles peuvent être appelées facilement depuis n'importe quel composant côté client hydraté (React, Vue, Svelte, etc.) ou même depuis des scripts JavaScript bruts.
Exemple de composant React appelant notre API GET :
// src/components/ListeProduits.jsx
import React, { useEffect, useState } from 'react';
function ListeProduits() {
const [produits, setProduits] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchProduits() {
try {
const response = await fetch('/api/produits'); // Appel à notre API Route
if (!response.ok) {
throw new Error(`Erreur HTTP: ${response.status}`);
}
const data = await response.json();
setProduits(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchProduits();
}, []);
if (loading) return <p>Chargement des produits...</p>;
if (error) return <p>Erreur : {error}</p>;
return (
<div>
<h2>Nos Produits</h2>
<ul>
{produits.map((produit) => (
<li key={produit.id}>
{produit.nom} - {produit.prix}€ (Stock: {produit.stock})
</li>
))}
</ul>
</div>
);
}
export default ListeProduits;
Pour utiliser ce composant, vous l'importeriez dans une page Astro et le rendriez avec l'attribut client:load (ou autre stratégie d'hydratation) :
---
import ListeProduits from '../components/ListeProduits.jsx';
---
<html lang="fr">
<head>
<meta charset="utf-8" />
<title>Page Produits</title>
</head>
<body>
<h1>Bienvenue sur notre page de produits</h1>
<ListeProduits client:load />
</body>
</html>
Conclusion et Résumé
Les API Routes sont une extension puissante et naturelle du framework Astro. Elles vous permettent de :
- Ajouter de la logique côté serveur à vos projets Astro, brisant la barrière du "statique pur".
- Gérer dynamiquement des données, des soumissions de formulaires, et bien plus encore.
- Créer des points de terminaison REST directement au sein de votre architecture Astro, sans nécessiter un backend séparé.
- Profiter du système de routage basé sur les fichiers et de la simplicité de développement d'Astro.
En combinant les performances exceptionnelles d'Astro pour le rendu de contenu, son architecture en îles pour l'interactivité côté client, et maintenant ses API Routes pour la logique côté serveur, vous disposez d'un ensemble d'outils complet pour construire des applications web modernes, rapides et maintenables.
N'hésitez pas à expérimenter avec différentes méthodes HTTP, la gestion des erreurs et l'intégration avec des services externes pour maîtriser pleinement cette fonctionnalité essentielle.