APIs REST et sorting/filtering
Ce cours fait partiellement suite à celui de Node.
On va voir des manières de structurer nos APIs Rest ainsi que de filtrer et trier les données.
Statut HTTP
2XX : Requête réussie
Ici on est dans le cas où la requête a été réussie. Globalement, on utilisera les codes 200, 201 et 204.
- 200 : Requête réussie
- 201 : Entité créée
- 204 : Requête réussie mais pas de contenu de retour
3XX : Redirection
Ici on est dans le cas où la requête a été réussie mais que l'on doit rediriger l'utilisateur. Globalement, on utilisera les codes 301 et 302.
- 301 : Redirection permanente
- 302 : Redirection temporaire
4XX : Requête invalide
Ici on est dans le cas où la requête est invalide. Globalement, on utilisera les codes 400, 401, 403 et 404.
- 400 : Requête invalide
- 401 : Non authentifié
- 403 : Non autorisé
- 404 : Non trouvé
5XX : Erreur serveur
Ici on est dans le cas où l'erreur est due au serveur. Globalement, on utilisera les codes 500, 502 et 503.
- 500 : Erreur serveur
- 502 : Mauvais port
- 503 : Service indisponible
La sémantique des codes HTTP est très importante, et il faut la respecter. Ça permet à plein d'autres outils de fonctionner correctement, d'avoir des logs corrects, ainsi qu'à d'autres développeurs ou utilisateurs de l'API de comprendre ce qu'il se passe.
Schéma
Voici un schéma global d'exemple d'à quoi ressemble une API pour n'importe quelle entité, avec le statut HTTP correspondant.
GET /tasks-> 200GET /tasks/:id-> 200POST /tasks-> 201PUT /tasks/:id-> 200DELETE /tasks/:id-> 204
Il suffira de changer le nom de l'entité pour avoir une API pour n'importe quelle entité.
Sorting/Filtering
Ici on va utiliser une convention. Vous pouvez en utiliser d'autres, mais le plus important c'est de rester dans cette convention, rester cohérent.
- Filtering:
filter[field]=value(répétable) - Sorting:
sort=field,field2(séparé par des virgules) - Pagination:
page=1(page 1, offset pagination)
Exemple API
import { Hono } from 'hono';
import { db } from '../db/clientDb.js';
import { task } from '../db/schema.js';
import { and, asc, desc, eq } from 'drizzle-orm';
export const app = new Hono();
// ....
const pageSize = 50;
app.get('/tasks', async (c) => {
const url = new URL(c.req.url);
const status = url.searchParams.get('filter[status]');
const sortParam = url.searchParams.get('sort') ?? 'updatedAt';
const page = Number(url.searchParams.get('page') ?? '1');
// Map des colonnes triables pour rester typesafe
const sortableColumns = {
updatedAt: task.updatedAt,
status: task.status
};
const orderByExpressions = sortParam.split(',').map((token) => {
const isDesc = token.startsWith('-');
const key = isDesc ? token.slice(1) : token;
const column = sortableColumns[key] ?? task.order; // fallback sûr
return isDesc ? desc(column) : asc(column);
});
const whereParts = [];
if (status) whereParts.push(eq(task.status, status));
const items = await db
.select({
id: task.id,
title: task.title,
description: task.description,
status: task.status,
updatedAt: task.updatedAt
})
.from(task)
.where(whereParts.length ? and(...whereParts) : undefined)
.orderBy(...orderByExpressions)
.limit(pageSize)
.offset((page - 1) * pageSize);
return c.json({ data: items });
});