12k
All articles

Comment créer une API CRUD avec AdonisJS

Créez une API CRUD avec AdonisJS v7 : routes posts, modèles Lucid, validation VineJS et réponses JSON testées avec curl.

OpenReplay Team
OpenReplay Team
Comment créer une API CRUD avec AdonisJS

AdonisJS est un framework Node.js TypeScript-first, inspiré de Laravel, qui intègre nativement le routage, un ORM (Lucid), la validation (VineJS), l’authentification et une CLI (ace) — ce qui en fait un choix solide pour les API CRUD lorsqu’on préfère des conventions plutôt que de tout câbler manuellement. Ce guide pratique construit un endpoint CRUD complet pour une ressource posts avec AdonisJS v7, en couvrant chaque étape, du scaffold à une requête curl testée, y compris la validation VineJS et les réponses d’erreur structurées.

AdonisJS v7 se distingue de la v6 sur trois points que ce tutoriel prend en compte : la configuration (Node.js 24+, npm create adonisjs@latest) ; le starter API (un monorepo Turborepo avec le backend sous apps/backend/, avec Lucid ORM et l’authentification préconfigurés) ; et les patterns de code (routage via #generated/controllers, modèles Lucid étendant les classes de schéma générées depuis apps/backend/database/schema.ts). Les exemples ci-dessous suivent cette structure v7 tout au long du guide.

Points clés à retenir

  • Le starter API AdonisJS v7 se scaffold avec npm create adonisjs@latest my-api -- --kit=api, et non avec l’ancienne syntaxe v6 npm init adonisjs@latest ... -- -K=api.
  • AdonisJS v7 requiert Node.js 24+ et npm 11+, pensez donc à mettre à jour votre environnement local avant de scaffolder un projet.
  • Les nouveaux starter kits v7 incluent Lucid ORM, SQLite, l’authentification, VineJS et la configuration des tests prêts à l’emploi — inutile d’ajouter Lucid manuellement dans un nouveau starter API.
  • Les exemples de routage v7 actuels utilisent l’import barrel généré pour les contrôleurs, import { controllers } from '#generated/controllers', puis lient les routes avec des handlers de style [controllers.Posts, 'index'].
  • Les échecs de validation VineJS sur des requêtes API retournent un HTTP 422 avec un tableau errors structuré contenant les noms de champs, les règles en échec et les messages — la structure qu’attendent les bibliothèques de formulaires frontend pour l’affichage d’erreurs au niveau des champs.
  • La méthode Post.findOrFail(id) de Lucid lève une exception « non trouvé » qu’AdonisJS transforme automatiquement en réponse JSON 404, sans vérification manuelle de nullité.

Prérequis

AdonisJS v7 requiert Node.js 24 ou supérieur et npm 11 ou supérieur. Vérifiez vos versions locales avant de scaffolder :

node --version   # doit être >= 24.x
npm --version    # doit être >= 11.x

Les starter kits v7 incluent Lucid configuré avec SQLite par défaut, il n’est donc pas nécessaire de démarrer avec PostgreSQL pour suivre ce tutoriel. SQLite écrit dans un fichier local et convient parfaitement à un guide CRUD jetable. Si vous migrez ensuite vers PostgreSQL, MySQL ou une autre base de données SQL, Lucid prend en charge ces drivers ; le code des contrôleurs et de la validation ci-dessous reste identique.

Scaffolder le projet avec le starter kit API

Le starter API AdonisJS v7 se scaffold avec npm create adonisjs@latest <name> -- --kit=api. Exécutez cette commande maintenant :

npm create adonisjs@latest my-api -- --kit=api

Le flag --kit=api sélectionne le starter kit API. Il vous fournit un projet orienté backend avec des paramètres par défaut adaptés aux API, notamment Lucid ORM, la validation VineJS, l’authentification, CORS, les tests et une base de données SQLite par défaut.

Déplacez-vous dans le projet et démarrez le serveur de développement :

cd my-api
npm run dev

Le backend tourne sur http://localhost:3333 par défaut. Le starter API inclut également des endpoints d’authentification sous /api/v1/auth, mais ce tutoriel garde les endpoints posts publics afin de se concentrer d’abord sur le CRUD.

Utiliser la configuration Lucid par défaut

Lucid est l’ORM SQL d’AdonisJS. Dans les anciens tutoriels, vous verrez souvent une étape de configuration manuelle comme celle-ci :

node ace add @adonisjs/lucid

N’exécutez pas cette commande dans un nouveau starter v7. Lucid est déjà installé et configuré, et le starter utilise SQLite par défaut. Vous pouvez passer directement à la création d’une table.

Générer une migration

Une migration est une modification de schéma versionnée. Générez une migration pour la table posts :

node ace make:migration posts

La commande crée un fichier horodaté sous database/migrations/. Ouvrez-le et définissez les colonnes de la table :

import { BaseSchema } from '@adonisjs/lucid/schema'

export default class extends BaseSchema {
  protected tableName = 'posts'

  async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments('id')
      table.string('title').notNullable()
      table.text('body').notNullable()
      table.timestamp('created_at')
      table.timestamp('updated_at')
    })
  }

  async down() {
    this.schema.dropTable(this.tableName)
  }
}

Le chemin d’import reste @adonisjs/lucid/schema. Le changement important de la v7 intervient après l’exécution de la migration : Lucid génère des classes de schéma à partir de votre base de données afin que votre modèle applicatif puisse hériter de définitions de colonnes typées.

Exécuter la migration et créer le modèle

Appliquez le schéma à la base de données :

node ace migration:run

Lucid crée la table posts et génère database/schema.ts. Ce fichier généré contient une classe PostsSchema avec des définitions de colonnes typées pour la table. Ne modifiez pas database/schema.ts directement ; il est régénéré après chaque migration.

Créez maintenant le modèle :

node ace make:model Post

Ouvrez app/models/post.ts et faites en sorte que le modèle étende la classe de schéma générée exportée depuis database/schema.ts :

import { PostSchema } from '#database/schema'

export default class Post extends PostSchema {}

C’est le pattern de modèle Lucid en v7. Les décorateurs de colonnes se trouvent dans la classe de schéma générée, tandis que votre modèle applicatif est l’endroit où vous ajouterez ultérieurement les relations, les hooks, les query scopes et les méthodes personnalisées.

Générer un contrôleur

node ace make:controller posts --resource scaffold un contrôleur resourceful pour la ressource posts. Exécutez-le maintenant :

node ace make:controller posts --resource

Le flag --resource scaffold les méthodes REST conventionnelles : index, create, store, show, edit, update et destroy. Pour une API JSON, vous n’en avez besoin que de cinq :

MéthodeRôle
indexLister tous les posts
storeCréer un nouveau post à partir du corps de la requête
showRetourner un post unique par identifiant
updateModifier un post existant par identifiant
destroySupprimer un post par identifiant

Vous pouvez supprimer les méthodes create et edit générées, car elles existent pour les formulaires HTML rendus côté serveur.

Déclarer les routes

La documentation v7 actuelle montre les contrôleurs importés via le fichier barrel généré, #generated/controllers. Ouvrez start/routes.ts et ajoutez les routes posts :

import router from '@adonisjs/core/services/router'
import { controllers } from '#generated/controllers'

router
  .group(() => {
    router.get('/posts', [controllers.Posts, 'index'])
    router.post('/posts', [controllers.Posts, 'store'])
    router.get('/posts/:id', [controllers.Posts, 'show'])
    router.route('/posts/:id', ['PUT', 'PATCH'], [controllers.Posts, 'update'])
    router.delete('/posts/:id', [controllers.Posts, 'destroy'])
  })
  .prefix('/api/v1')

Cela enregistre les endpoints API sous /api/v1/posts, conformément au style d’API versionnée utilisé par le starter v7.

Méthode HTTPCheminMéthode du contrôleurDescription
GET/api/v1/postsindexLister tous les posts
POST/api/v1/postsstoreCréer un post
GET/api/v1/posts/:idshowRécupérer un post
PUT/api/v1/posts/:idupdateMettre à jour un post
PATCH/api/v1/posts/:idupdateMise à jour partielle d’un post
DELETE/api/v1/posts/:iddestroySupprimer un post

AdonisJS documente toujours router.resource() pour les routes RESTful, mais une route resource simple enregistre sept routes, dont GET /posts/create et GET /posts/:id/edit. La liste de routes explicite ci-dessus maintient une surface d’API JSON uniquement et évite d’exposer accidentellement des routes destinées à l’affichage de formulaires.

Vérifiez les routes actives avec :

node ace list:routes

Si les routes n’apparaissent pas, redémarrez le serveur de développement afin que le fichier barrel des contrôleurs générés soit actualisé.

Ajouter la validation VineJS

VineJS est la bibliothèque de validation utilisée par AdonisJS. Générez un fichier de validateur :

node ace make:validator post

Ouvrez app/validators/post.ts et définissez les validateurs pour la création et la mise à jour des posts :

import vine from '@vinejs/vine'

export const createPostValidator = vine.create({
  title: vine.string().trim().minLength(3),
  body: vine.string().trim().minLength(1),
})

export const updatePostValidator = vine.create({
  title: vine.string().trim().minLength(3).optional(),
  body: vine.string().trim().minLength(1).optional(),
})

Le guide de validation AdonisJS actuel utilise vine.create() pour définir les validateurs. Dans les contrôleurs, request.validateUsing() exécute le validateur sur le corps de la requête et retourne le payload validé et typé.

Lorsque request.validateUsing() échoue sur une requête API, AdonisJS retourne un HTTP 422 avec un tableau JSON errors structuré. Chaque erreur contient le nom du champ, la règle en échec et un message. C’est la structure que le code frontend peut mapper directement à l’état des champs de formulaire.

Les replays de session de frontends consommant des API CRUD font souvent apparaître un mode d’échec spécifique : un formulaire est soumis, le serveur retourne un 422, mais aucun message d’erreur au niveau du champ n’apparaît jamais dans l’interface. La cause racine est souvent que le serveur a retourné une chaîne de caractères libre plutôt que des erreurs de champ structurées. VineJS vous fournit la structure attendue par défaut, préservez-la donc. N’interceptez pas l’exception de validation pour la relancer sous forme de chaîne aplatie, sauf si vous préservez également le contrat au niveau des champs.

Implémenter les méthodes du contrôleur

Chaque action du contrôleur est un handler concis. Les deux cas d’échec qui importent — un enregistrement manquant et une entrée invalide — sont gérés par Lucid et VineJS sans branchement conditionnel manuel.

Ouvrez app/controllers/posts_controller.ts et remplacez son contenu :

import type { HttpContext } from '@adonisjs/core/http'
import Post from '#models/post'
import { createPostValidator, updatePostValidator } from '#validators/post'

export default class PostsController {
  async index({ response }: HttpContext) {
    const posts = await Post.all()
    return response.json(posts)
  }

  async store({ request, response }: HttpContext) {
    const payload = await request.validateUsing(createPostValidator)
    const post = await Post.create(payload)
    return response.created(post)
  }

  async show({ params, response }: HttpContext) {
    const post = await Post.findOrFail(params.id)
    return response.json(post)
  }

  async update({ params, request, response }: HttpContext) {
    const post = await Post.findOrFail(params.id)
    const payload = await request.validateUsing(updatePostValidator)

    await post.merge(payload).save()

    return response.json(post)
  }

  async destroy({ params, response }: HttpContext) {
    const post = await Post.findOrFail(params.id)
    await post.delete()

    return response.noContent()
  }
}

Quelques détails importants :

  • store retourne response.created(post), ce qui définit le statut HTTP 201.
  • destroy retourne response.noContent(), ce qui définit le statut HTTP 204.
  • await post.delete() est attendu, de sorte que la réponse ne soit pas retournée avant la fin de la suppression.
  • Post.findOrFail(params.id) lève une exception lorsqu’aucune ligne ne correspond. AdonisJS transforme automatiquement cette exception en réponse 404.

Tester avec curl

Une API CRUD AdonisJS v7 peut être exercée de bout en bout avec curl — chaque action de ressource (index, store, show, update, destroy) correspond à une seule requête HTTP sous /api/v1/posts. Avec npm run dev en cours d’exécution, créez un post :

curl -X POST http://localhost:3333/api/v1/posts \
  -H "Content-Type: application/json" \
  -d '{"title": "First post", "body": "Hello from Adonis"}'

Une réponse store réussie retourne HTTP 201 avec l’enregistrement créé :

{
  "title": "First post",
  "body": "Hello from Adonis",
  "createdAt": "2026-06-01T12:00:00.000+00:00",
  "updatedAt": "2026-06-01T12:00:00.000+00:00",
  "id": 1
}

Déclenchez maintenant des erreurs de validation en envoyant un titre trop court et un corps vide :

curl -X POST http://localhost:3333/api/v1/posts \
  -H "Content-Type: application/json" \
  -d '{"title": "ok", "body": ""}'

La réponse est HTTP 422 avec un tableau d’erreurs VineJS structuré :

{
  "errors": [
    {
      "field": "title",
      "rule": "minLength",
      "message": "The title field must have at least 3 characters"
    },
    {
      "field": "body",
      "rule": "minLength",
      "message": "The body field must have at least 1 characters"
    }
  ]
}

C’est le contrat qu’un frontend mappe à l’état du formulaire : itérer sur errors, indexer par field, et afficher message à côté du champ correspondant.

Récupérez un enregistrement inexistant pour observer le comportement 404 :

curl -i http://localhost:3333/api/v1/posts/9999

findOrFail produit une réponse 404 au lieu de retourner null avec un 200 trompeur.

Les requêtes CRUD restantes suivent le même schéma :

curl http://localhost:3333/api/v1/posts                    # index
curl http://localhost:3333/api/v1/posts/1                  # show
curl -X PUT http://localhost:3333/api/v1/posts/1 \
  -H "Content-Type: application/json" \
  -d '{"title": "Updated title"}'                         # update
curl -X PATCH http://localhost:3333/api/v1/posts/1 \
  -H "Content-Type: application/json" \
  -d '{"body": "Updated body"}'                            # mise à jour partielle
curl -X DELETE http://localhost:3333/api/v1/posts/1         # destroy → 204

Pour aller plus loin

Une API CRUD AdonisJS v7 nécessite trois compléments avant d’être prête pour la production : l’authentification, la pagination et les tests.

Pour l’authentification, le starter API inclut déjà des routes d’auth et une configuration orientée access tokens. Protégez les routes posts avec le middleware d’authentification lorsqu’elles ne doivent être accessibles qu’aux utilisateurs connectés. Si vous ajoutez l’auth à un projet existant qui n’est pas issu d’un starter, la commande documentée est :

node ace add @adonisjs/auth --guard=access_tokens

Pour les grandes collections, remplacez Post.all() dans index par la pagination Lucid afin que l’endpoint retourne une page à la fois plutôt que toute la table.

Pour les tests, utilisez Japa, le test runner configuré par le starter. Le starter API v7 utilise :

npm run test

Commencez par tester les trois contrats les plus importants pour le frontend : une création réussie retourne 201, les échecs de validation retournent 422 avec des erreurs au niveau des champs, et les enregistrements manquants retournent 404.

FAQ

Pourquoi ce tutoriel v7 utilise-t-il des routes explicites plutôt que router.resource().apiOnly() ?

La documentation de routage AdonisJS v7 actuelle met en avant router.resource(), mais une route resource simple enregistre sept routes, dont GET /posts/create et GET /posts/:id/edit pour les formulaires HTML. Pour une API JSON uniquement, une liste de routes explicites GET, POST, PUT, PATCH et DELETE rend la surface d'API publique évidente et évite d'enregistrer accidentellement des routes destinées à l'affichage de formulaires.

Pourquoi node ace make:controller --resource scaffold-t-il sept méthodes alors que cette API n'en utilise que cinq ?

Le flag --resource scaffold le jeu complet de méthodes du contrôleur RESTful : index, create, store, show, edit, update et destroy. Les méthodes create et edit existent pour afficher des formulaires HTML dans les applications rendues côté serveur. Une API JSON n'a besoin que de index, store, show, update et destroy, les méthodes create et edit inutilisées peuvent donc être supprimées sans risque.

Quelles méthodes HTTP l'endpoint update doit-il prendre en charge ?

Ce tutoriel mappe PUT et PATCH sur /api/v1/posts/:id vers la même méthode du contrôleur update. PUT signale généralement un remplacement complet de la ressource et PATCH une mise à jour partielle, mais de nombreuses API CRUD gèrent les deux via la même logique de validation et de fusion. Exécutez node ace list:routes pour confirmer le mapping actif dans votre application.

Un tutoriel AdonisJS v6 fonctionnera-t-il avec la v7 ?

Pas de manière fiable. Une grande partie de la logique des contrôleurs reste familière, mais la v7 modifie des détails importants : la version minimale requise est Node.js 24+, le scaffold utilise npm create adonisjs@latest, les starter kits incluent Lucid et l'auth par défaut, les exemples de routage actuels utilisent #generated/controllers, et les modèles Lucid étendent les classes de schéma générées depuis #database/schema. Considérez les tutoriels v6 uniquement comme des références conceptuelles.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.