Vous pouvez créer une API avec l’IA en un après-midi : demandez à un agent de code comme Claude Code ou Codex « une API REST de tâches avec des utilisateurs » et vous obtenez des routes, une base de données et un serveur qui tourne. Que cette API survive à ses premiers vrais utilisateurs est une autre question. Entrées non validées, endpoints qui livrent les données des autres et migrations que personne n’a lues : ce sont les problèmes habituels. Cet article détaille la méthode que je suis pour construire un backend REST avec des agents, du choix de la stack au déploiement, et les endroits où il faut ralentir.
Il fait partie de la série commencée avec créer un SaaS avec l’IA. L’article sur le SaaS couvre le produit ; celui-ci couvre le backend en dessous, appelé par une application mobile, un frontend ou d’autres services. Si cette façon de travailler est nouvelle pour vous, comment faire du vibe coding est un bon échauffement.
1. Définir l’API avant d’écrire du code
Une API est un contrat. Dès qu’une application mobile est publiée en s’appuyant dessus, renommer un champ coûte cher. Avant que l’agent n’écrive du code, notez donc :
- Les ressources : les noms. Par exemple
users,projects,tasks,comments. - Les endpoints :
GET /v1/projects,POST /v1/projects,GET /v1/projects/:id/tasks,PATCH /v1/tasks/:id, etc. - Qui peut faire quoi : « Les membres d’un projet peuvent lire ses tâches ; seul le propriétaire peut supprimer le projet. »
- Le format d’erreur : une seule forme pour toutes les erreurs, avec un statut HTTP, un code lisible par une machine et un message.
- La pagination : par curseur ou par page, taille par défaut et taille maximale.
Limitez la première version aux endpoints dont un vrai client a besoin. Une API d’administration, des webhooks et l’import en masse peuvent attendre.
2. Mettre les règles dans un fichier de spec
Les agents ne se souviennent pas des conversations précédentes. Claude Code lit CLAUDE.md au début de chaque session ; Codex et beaucoup d’autres agents lisent AGENTS.md. Mettez-y vos décisions :
# Tasks API
## Stack
- Node.js LTS, TypeScript, Fastify
- PostgreSQL, Prisma for schema and migrations
- Zod for request validation; OpenAPI generated from the same schemas
- Vitest; tests use app.inject() against a test database
## Rules
- All routes under /v1. JSON only.
- Every request body, query and param is validated with a schema.
- Errors: { "error": { "code": "...", "message": "..." } }, never a stack trace.
- Every query on tasks and projects checks project membership.
- Never return password hashes or tokens; use explicit response schemas.
- Database changes only through migration files.
- List endpoints: cursor pagination, default 20, max 100.
- Secrets come from environment variables; never in code or logs.
- Ask before adding a dependency.
- After every task: pnpm lint && pnpm test
## Out of scope (for now)
Admin API, file uploads, webhooks, GraphQL.
Chaque règle est là parce que les agents se trompent sans elle. « Explicit response schemas » empêche l’agent de renvoyer toute la ligne de l’utilisateur, hash du mot de passe compris. « Max 100 » empêche quelqu’un de demander un million de lignes. Pour aller plus loin sur ce fichier, voir CLAUDE.md et AGENTS.md.
3. Choisir une stack ennuyeuse
L’agent écrit un meilleur code sur une stack qu’il a vue des milliers de fois. Deux choix sûrs :
| TypeScript | Python | |
|---|---|---|
| Framework | Fastify (ou Express, Hono) | FastAPI |
| Validation | Zod ou JSON Schema | Pydantic |
| Accès à la base | Prisma ou Drizzle | SQLAlchemy |
| Migrations | Prisma Migrate, Drizzle Kit | Alembic |
| OpenAPI | @fastify/swagger | Intégré |
| Tests | Vitest + app.inject() | pytest + TestClient |
Les deux fonctionnent. Choisissez le langage que vous savez lire, car vous allez lire beaucoup de diffs. Dans les exemples, j’utilise TypeScript, Fastify, Prisma et PostgreSQL.
Lancez Postgres en local avec Docker, pour que l’agent n’ait jamais besoin d’une base partagée :
docker run --name tasks-db -e POSTGRES_PASSWORD=dev -p 5432:5432 -d postgres:16
mkdir tasks-api && cd tasks-api && git init
4. D’abord un plan, puis une ressource à la fois
Mon premier prompt demande un plan, pas du code :
Read CLAUDE.md. Don't write code yet.
Propose:
1. The Prisma schema (models, relations, indexes, unique constraints)
2. The full endpoint list with request and response shapes
3. How authorization works for each endpoint
4. 6–8 implementation steps, each ending with tests that pass
List open questions at the end.
Corrigez le plan, répondez aux questions, mettez la spec à jour. Ensuite, avancez étape par étape :
Implement step 2: the projects resource (list, create, get, update,
delete). Validate input with Zod, use explicit response schemas,
check membership on every query. Write tests for the happy path,
invalid input, and a second user who is not a member. Run the tests,
show me the result, then stop.
« Then stop » est là pour que je puisse lire le diff et commiter avant l’étape suivante. Quand l’agent dérive, je reviens d’un commit en arrière au lieu de démêler cinq ressources.
5. Schéma et migrations
Le schéma est la partie la plus difficile à changer ensuite, lisez-le donc attentivement :
- Les contraintes dans la base, pas seulement dans le code.
NOT NULL, e-mails uniques, clés étrangères avec un comportementON DELETEchoisi délibérément. - Des index pour les requêtes que vous exécutez. Chaque clé étrangère sur laquelle vous filtrez et chaque colonne sur laquelle vous triez dans un endpoint de liste.
- Des horodatages (
createdAt,updatedAt) sur chaque table ; ils aident au débogage et à la pagination par curseur. - Les montants en entiers ou en décimaux, jamais en virgule flottante.
Chaque changement passe par une migration :
npx prisma migrate dev --name add_tasks # create and apply locally
npx prisma migrate reset # rebuild the dev database from zero
npx prisma migrate deploy # apply pending migrations in production
Dès qu’il y a de vraies données, lisez chaque fichier de migration écrit par l’agent. Arrêtez-vous et posez des questions devant DROP COLUMN, DROP TABLE, les renommages (Prisma peut les transformer en suppression puis ajout), les UPDATE ou DELETE sans WHERE, et les nouvelles colonnes NOT NULL sans valeur par défaut sur de grosses tables.
6. Authentification : utiliser ce qui existe
Ne laissez pas l’agent inventer le stockage des mots de passe ou des formats de token. Utilisez un fournisseur d’authentification hébergé ou une bibliothèque reconnue, et limitez le travail de l’agent à l’intégration :
- Les mots de passe hachés avec argon2 ou bcrypt via une bibliothèque maintenue, jamais stockés ni journalisés en clair.
- Sessions ou tokens : pour un client navigateur sur le même site, un cookie de session
HttpOnly,Secure,SameSiteest le choix sûr le plus simple. Pour des clients mobiles ou tiers, des tokens d’accès de courte durée avec des refresh tokens. - Vérifiez correctement les tokens. Signature, expiration, émetteur et audience ; refusez l’algorithme
none. Demandez à l’agent de vous montrer exactement où cela se passe. - Limitez le débit de la connexion et de la réinitialisation du mot de passe, pour que personne ne puisse essayer des milliers de mots de passe.
7. Autorisations : le bug que les agents écrivent le plus
L’authentification répond à « qui est-ce ? ». L’autorisation répond à « a-t-il le droit de toucher cet enregistrement ? ». Les API écrites par des agents réussissent en général la première et ratent la seconde. Le cas classique : GET /v1/tasks/:id vérifie que le token est valide, puis charge la tâche par son ID. N’importe quel utilisateur connecté peut lire n’importe quelle tâche en changeant le numéro. L’OWASP place ce problème, le broken object level authorization, en tête de son API Security Top 10.
La solution est une habitude, pas une bibliothèque :
- Chaque requête qui charge un enregistrement par ID filtre aussi selon ce à quoi l’appelant a accès :
where: { id, project: { members: { some: { userId } } } }. - « Introuvable » et « pas à vous » renvoient le même
404, pour qu’on ne puisse pas sonder les ID. - Testez avec deux utilisateurs chaque endpoint qui prend un ID. Ce sont les tests les plus précieux du projet.
Une erreur voisine est l’assignation de masse (mass assignment) : l’agent passe le corps de la requête directement à update(), et un utilisateur envoie "role": "admin" ou "ownerId": "...". Des schémas de validation qui ne listent que les champs autorisés l’empêchent.
Demandez à l’agent : « Liste chaque endpoint et la vérification d’autorisation qu’il effectue. Signale tout endpoint qui charge un enregistrement par ID sans filtrer selon l’appelant. »
8. Validation et erreurs
Toute entrée qui franchit la frontière est validée : corps, query string, paramètres de chemin, en-têtes que vous utilisez. Avec Zod et Fastify, la route déclare ses schémas, et les requêtes invalides sont rejetées avec un 400 avant que votre code ne s’exécute.
- Rejetez les champs inconnus au lieu de les ignorer en silence ; les bugs côté client apparaissent plus tôt.
- Limitez les tailles : longueur des chaînes, longueur des tableaux, taille du corps de la requête, taille de page.
- Une seule forme d’erreur partout, et un gestionnaire d’erreurs global qui journalise les détails côté serveur mais ne renvoie au client que
codeetmessage. Des stack traces et des erreurs SQL dans les réponses donnent une carte aux attaquants. - Uniquement des requêtes paramétrées. L’ORM le fait par défaut ; méfiez-vous des fonctions SQL brutes qui concatènent des chaînes, comme
$queryRawUnsafede Prisma.
9. Les tests qui comptent
app.inject() de Fastify envoie des requêtes à l’application sans ouvrir de port : les tests d’API sont rapides. Pas besoin de 100 % de couverture ; il vous faut ceux-ci :
- Le cas nominal de chaque endpoint, avec vérification du code de statut et de la forme de la réponse.
- Une entrée invalide renvoie
400avec votre format d’erreur. - Pas de token renvoie
401; l’enregistrement de quelqu’un d’autre renvoie404. - La pagination respecte le maximum.
- Les migrations s’appliquent depuis zéro sur une base vide.
Les tests tournent sur une base de test séparée, réinitialisée entre deux exécutions. Ne laissez jamais la suite de tests approcher des identifiants de production.
Surveillez l’agent qui « corrige » un test en échec en modifiant l’assertion. Si un fichier de test apparaît dans un diff que vous n’avez pas demandé, demandez pourquoi. La relecture de code par l’IA propose une checklist pour ça.
10. Une doc OpenAPI issue des mêmes schémas
Une documentation écrite à la main finit par diverger. Générez-la à partir des schémas de validation, pour qu’elle décrive exactement ce que le code accepte. Dans Fastify, @fastify/swagger construit un document OpenAPI à partir des schémas de vos routes et @fastify/swagger-ui le sert ; FastAPI le fait nativement.
Add @fastify/swagger and @fastify/swagger-ui. Generate the OpenAPI
document from the existing route schemas, add descriptions and
examples, document the error format and auth. Serve the UI at /docs
only when NODE_ENV is not production.
Le fichier OpenAPI sert au-delà de la documentation : les agents frontend peuvent en générer un client typé, et d’autres outils peuvent y découvrir vos endpoints.
11. Déployer
Une petite API tourne très bien en conteneur sur une plateforme comme Fly.io, Railway ou Render, ou sur n’importe quel serveur avec Docker. Une checklist :
- Un Dockerfile avec un build multi-étapes et un utilisateur non root. Demandez-le à l’agent, puis lisez-le.
- Les migrations comme une étape séparée (
prisma migrate deploy) avant le démarrage de la nouvelle version, pas dans chaque instance de l’application au démarrage. - Des variables d’environnement pour l’URL de la base, les secrets et les origines CORS autorisées, définies dans la plateforme, pas dans l’image.
- Un endpoint de santé (
/health) que la plateforme peut vérifier. - CORS limité à vos vrais frontends ; jamais
*avec des identifiants. - HTTPS uniquement, des en-têtes de sécurité (par exemple avec
@fastify/helmet) et des logs de requêtes sans mots de passe, tokens ni données personnelles. - Des sauvegardes activées pour la base de production, et une restauration testée au moins une fois.
- Un suivi des erreurs comme Sentry, pour apprendre l’existence des
500avant que vos utilisateurs ne vous les signalent.
Les erreurs typiques de l’IA
- Renvoyer des lignes entières de la base. Hash des mots de passe, indicateurs internes et e-mails d’autres utilisateurs dans le JSON. Les schémas de réponse explicites règlent le problème.
- Inventer des API. Des fonctions de bibliothèque qui n’existent pas ou qui ont changé il y a plusieurs versions. Donnez à l’agent une documentation à jour, et recollez-lui les erreurs telles quelles.
- Pas de pagination. Un
findMany()sans limite fonctionne jusqu’à ce que la table atteigne 200 000 lignes. - Requêtes N+1. Une requête par élément dans une boucle. Demandez à l’agent de journaliser les requêtes en développement et cherchez les répétitions.
- Erreurs avalées. Un
catch {}qui renvoie quand même200. - Des secrets dans le code. Un secret JWT codé en dur « juste pour l’instant » qui part en production.
- Un périmètre qui gonfle. « J’ai aussi ajouté un endpoint d’administration. » Annulez-le ou déplacez-le dans sa propre branche.
Sécurité : court mais sérieux
- Gardez
.envdans.gitignore; renouvelez toute clé qui a été commitée un jour. - Ne collez pas de clés d’API dans le chat ; l’agent n’a besoin que du nom de la variable.
- Pointez l’agent vers une base locale ou de staging, jamais vers la production.
- Lisez les commandes que lance l’agent, surtout les commandes de base de données,
rm, les force push etcurl … | sh. - Lancez
pnpm audit(ou l’équivalent de votre gestionnaire de paquets) de temps en temps et donnez le résultat à l’agent. - Limitez le débit des endpoints publics, pas seulement de la connexion.
Pour en savoir plus sur la sécurité des agents sur votre machine, lisez la sécurité des agents de code IA.
Quand faire appel à un développeur humain
Quelques heures de relecture par un développeur backend expérimenté sont vite rentabilisées :
- Avant l’inscription de vrais utilisateurs, pour l’authentification et les autorisations
- Quand l’API stocke des données personnelles, de santé ou de paiement
- Quand un partenaire ou un client entreprise va s’y intégrer
- Quand les performances s’effondrent : requêtes lentes, index manquants, limites de connexions
- Quand vous et l’agent avez échoué trois fois à corriger le même bug
Facilitez-lui la tâche : le fichier de spec, le document OpenAPI, le dossier des migrations et une suite de tests au vert.
Comment je procède avec AgentVera
Tout ce qui précède fonctionne dans un seul terminal. Avec plusieurs agents, AgentVera aide à ces endroits :
- Travail en parallèle dans des worktrees. Dans le workspace, je fais tourner un agent sur l’API et un autre sur le frontend ou les tests, chacun dans son propre git worktree pour qu’ils ne se marchent pas sur les fichiers. La méthode est décrite dans les git worktrees pour des agents en parallèle.
- Tester les endpoints sans autre outil. Le client API trouve les endpoints du projet à partir des fichiers OpenAPI et des routes Express, Fastify, Hono, Next.js, FastAPI, Django, NestJS et d’autres. J’envoie une requête avec paramètres, en-têtes et corps, et si la réponse est une erreur, je transmets la requête et la réponse à un agent en un clic.
- Voir ce qui est arrivé dans la base. Le gestionnaire de base de données ouvre le Postgres local pour que je vérifie les lignes créées par l’agent, et DB Guard évalue le risque de chaque migration quand l’agent termine son tour et peut l’essayer sur une base fantôme.
- Les conteneurs. La page Docker affiche le conteneur Postgres et ses logs à côté des agents.
- Relire les tours. La relecture de code vérifie chaque tour terminé avant que je fusionne.
Ces outils accélèrent la boucle ; la spec, les tests et votre propre relecture restent ce qui rend l’API sûre.
Pour conclure
Pour créer une API avec l’IA, fixez d’abord le contrat, écrivez les règles dans CLAUDE.md ou AGENTS.md, choisissez une stack courante et faites construire une ressource à la fois, avec des tests. Validez chaque entrée, ne modifiez la base que par des migrations, vérifiez la propriété de chaque enregistrement, générez l’OpenAPI à partir des mêmes schémas et lancez les migrations comme une étape de déploiement séparée. Faites relire le tout par un humain avant l’arrivée des vrais utilisateurs. Si vous voulez faire tourner côte à côte les agents backend, frontend et de test, téléchargez AgentVera gratuitement.