AdonisJS : Découvrez le Laravel de JavaScript - Le Framework Full-Stack qui Révolutionne Node.js

Découvrez AdonisJS 6, le framework Node.js TypeScript-first qui s'inspire de Laravel. Un écosystème complet avec ORM, authentification, validation, et bien plus pour créer des applications web modernes et des APIs robustes rapidement.

AdonisJS : Découvrez le Laravel de JavaScript - Le Framework Full-Stack qui Révolutionne Node.js

Si vous êtes développeur PHP et que vous appréciez Laravel pour sa simplicité et ses fonctionnalités complètes, ou si vous cherchez un framework Node.js robuste pour vos projets, laissez-moi vous présenter AdonisJS - souvent surnommé "le Laravel de JavaScript".

AdonisJS est un framework TypeScript-first pour créer des applications web et des serveurs API, qui apporte à l'écosystème Node.js la philosophie et l'élégance qu'on retrouve dans Laravel. Avec la sortie récente d'AdonisJS v6, ce framework a atteint une maturité impressionnante.

Pourquoi AdonisJS est-il Considéré comme le Laravel de JavaScript ?

1. Philosophie "Convention over Configuration"

Comme Laravel, AdonisJS privilégie les conventions sensées plutôt que la configuration excessive. Vous pouvez commencer à développer immédiatement sans passer des heures à configurer votre projet.

2. Écosystème Complet

AdonisJS fournit tout ce dont vous avez besoin :

  • ORM Lucid (équivalent d'Eloquent)
  • Système d'authentification intégré
  • Validation de données robuste
  • Gestion des emails et notifications
  • CLI puissant pour la génération de code
  • Tests intégrés

3. Architecture MVC Claire

Une structure familière pour les développeurs venant de Laravel ou d'autres frameworks MVC.

Les Nouveautés d'AdonisJS 6

La version 6 d'AdonisJS adopte complètement les modules ECMAScript (ESM), offrant de meilleures performances et une compatibilité avec les standards modernes.

Principales Améliorations

  • Support ESM natif pour une meilleure performance
  • IoC Container simplifié et plus puissant
  • TypeScript amélioré avec une meilleure inférence de types
  • CLI modernisé avec de nouvelles commandes
  • Architecture modulaire plus flexible

Installation et Premiers Pas

Création d'un Nouveau Projet

# Installation globale du CLI (optionnel)
npm i -g @adonisjs/cli

# Création d'un nouveau projet
npm init adonisjs@latest mon-projet-adonis

# Ou directement avec npx
npx create-adonisjs@latest mon-projet-adonis

Structure du Projet

mon-projet-adonis/
├── app/
│   ├── controllers/
│   ├── middleware/
│   ├── models/
│   ├── services/
│   └── validators/
├── config/
├── database/
│   ├── migrations/
│   └── seeders/
├── public/
├── resources/
│   └── views/
├── routes/
├── tests/
├── ace.js
├── server.ts
└── package.json

Configuration de Base

// server.ts
import { Ignitor, prettyPrintError } from '@adonisjs/core'

const ignitor = new Ignitor(new URL('./', import.meta.url))

try {
  await ignitor.httpServer().start()
} catch (error) {
  process.exitCode = 1
  prettyPrintError(error)
}

L'ORM Lucid : L'Eloquent de JavaScript

Lucid est l'ORM d'AdonisJS, inspiré d'Eloquent. Il offre une syntaxe expressive et intuitive.

Création d'un Modèle

node ace make:model User
node ace make:migration create_users_table

Modèle User

// app/models/user.ts
import { DateTime } from 'luxon'
import { BaseModel, column, hasMany } from '@adonisjs/lucid/orm'
import type { HasMany } from '@adonisjs/lucid/types/relations'
import Post from './post.js'

export default class User extends BaseModel {
  @column({ isPrimary: true })
  declare id: number

  @column()
  declare email: string

  @column({ serializeAs: null })
  declare password: string

  @column()
  declare fullName: string

  @column.dateTime({ autoCreate: true })
  declare createdAt: DateTime

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  declare updatedAt: DateTime

  @hasMany(() => Post)
  declare posts: HasMany<typeof Post>
}

Migration

// database/migrations/create_users_table.ts
import { BaseSchema } from '@adonisjs/lucid/schema'

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

  async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments('id')
      table.string('email').notNullable().unique()
      table.string('password').notNullable()
      table.string('full_name').notNullable()
      table.timestamp('created_at')
      table.timestamp('updated_at')
    })
  }

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

Routage Expressif et Puissant

Le système de routage d'AdonisJS est à la fois simple et puissant.

Routes de Base

// routes/web.ts
import router from '@adonisjs/core/services/router'
import { middleware } from './kernel.js'

// Route simple
router.get('/', async () => {
  return { hello: 'world' }
})

// Routes avec paramètres
router.get('/users/:id', 'UsersController.show')
router.post('/users', 'UsersController.store')

// Groupes de routes
router.group(() => {
  router.get('/profile', 'UsersController.profile')
  router.put('/profile', 'UsersController.updateProfile')
  router.delete('/account', 'UsersController.destroy')
}).prefix('/api/v1').middleware(middleware.auth())

// Routes avec middleware
router.group(() => {
  router.resource('/posts', 'PostsController')
}).middleware([middleware.auth(), middleware.verified()])

Route Resource (comme Laravel)

// Génère automatiquement les routes CRUD
router.resource('/posts', 'PostsController')

// Équivalent à :
// GET    /posts          -> PostsController.index
// GET    /posts/create   -> PostsController.create  
// POST   /posts          -> PostsController.store
// GET    /posts/:id      -> PostsController.show
// GET    /posts/:id/edit -> PostsController.edit
// PUT    /posts/:id      -> PostsController.update
// DELETE /posts/:id      -> PostsController.destroy

Controllers Robustes

Les controllers dans AdonisJS suivent le pattern MVC classique.

Création d'un Controller

node ace make:controller PostsController --resource

Controller Complet

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

export default class PostsController {
  /**
   * Afficher la liste des posts
   */
  async index({ request, response }: HttpContext) {
    const page = request.input('page', 1)
    const limit = request.input('limit', 10)

    const posts = await Post.query()
      .preload('author')
      .paginate(page, limit)

    return response.ok(posts)
  }

  /**
   * Afficher un post spécifique
   */
  async show({ params, response }: HttpContext) {
    const post = await Post.query()
      .where('id', params.id)
      .preload('author')
      .preload('comments', (query) => {
        query.preload('user')
      })
      .firstOrFail()

    return response.ok(post)
  }

  /**
   * Créer un nouveau post
   */
  async store({ request, response, auth }: HttpContext) {
    const payload = await request.validateUsing(createPostValidator)
    
    const post = await Post.create({
      ...payload,
      userId: auth.user!.id,
    })

    await post.load('author')
    return response.created(post)
  }

  /**
   * Mettre à jour un post
   */
  async update({ params, request, response, bouncer }: HttpContext) {
    const post = await Post.findOrFail(params.id)
    await bouncer.authorize('updatePost', post)

    const payload = await request.validateUsing(updatePostValidator)
    post.merge(payload)
    await post.save()

    return response.ok(post)
  }

  /**
   * Supprimer un post
   */
  async destroy({ params, response, bouncer }: HttpContext) {
    const post = await Post.findOrFail(params.id)
    await bouncer.authorize('deletePost', post)

    await post.delete()
    return response.noContent()
  }
}

Validation de Données Robuste

AdonisJS utilise VineJS pour la validation, offrant une API moderne et type-safe.

Création d'un Validator

node ace make:validator CreatePost

Validator Personnalisé

// app/validators/post.ts
import vine from '@vinejs/vine'

export const createPostValidator = vine.compile(
  vine.object({
    title: vine.string().minLength(3).maxLength(100),
    content: vine.string().minLength(10),
    categoryId: vine.number().exists(async (db, value) => {
      const category = await db.from('categories').where('id', value).first()
      return !!category
    }),
    tags: vine.array(vine.string()).optional(),
    publishedAt: vine.date().optional(),
    isPublished: vine.boolean().optional(),
  })
)

export const updatePostValidator = vine.compile(
  vine.object({
    title: vine.string().minLength(3).maxLength(100).optional(),
    content: vine.string().minLength(10).optional(),
    categoryId: vine.number().exists(async (db, value) => {
      const category = await db.from('categories').where('id', value).first()
      return !!category
    }).optional(),
    tags: vine.array(vine.string()).optional(),
    publishedAt: vine.date().optional(),
    isPublished: vine.boolean().optional(),
  })
)

Authentification Simplifiée

AdonisJS propose plusieurs méthodes d'authentification prêtes à l'emploi.

Configuration de l'Authentification

node ace add @adonisjs/auth

Méthodes d'Authentification Disponibles

// config/auth.ts
import { defineConfig } from '@adonisjs/auth'
import { sessionGuard, sessionUserProvider } from '@adonisjs/auth/session'
import { jwtGuard, jwtUserProvider } from '@adonisjs/auth/jwt'

const authConfig = defineConfig({
  default: 'web',
  guards: {
    // Authentification par session (web)
    web: sessionGuard({
      useRememberMeTokens: true,
      provider: sessionUserProvider({
        model: () => import('#models/user'),
      }),
    }),
    
    // Authentification JWT (API)
    api: jwtGuard({
      provider: jwtUserProvider({
        model: () => import('#models/user'),
      }),
    }),
  },
})

export default authConfig

Controller d'Authentification

// app/controllers/auth_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
import { loginValidator, registerValidator } from '#validators/auth'

export default class AuthController {
  async register({ request, response }: HttpContext) {
    const payload = await request.validateUsing(registerValidator)
    
    const user = await User.create(payload)
    return response.created({ user })
  }

  async login({ request, response, auth }: HttpContext) {
    const { email, password } = await request.validateUsing(loginValidator)
    
    const user = await User.verifyCredentials(email, password)
    const token = await auth.use('api').generate(user)
    
    return response.ok({
      user,
      token: token.value!.release(),
    })
  }

  async logout({ response, auth }: HttpContext) {
    await auth.use('api').revoke()
    return response.ok({ message: 'Déconnexion réussie' })
  }

  async me({ response, auth }: HttpContext) {
    await auth.check()
    return response.ok({ user: auth.user })
  }
}

Middleware Puissants

Les middleware permettent de traiter les requêtes à différents niveaux.

Middleware d'Autorisation

// app/middleware/admin_middleware.ts
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'

export default class AdminMiddleware {
  async handle({ auth, response }: HttpContext, next: NextFn) {
    await auth.check()
    
    if (!auth.user?.isAdmin) {
      return response.forbidden({
        message: 'Accès refusé. Droits administrateur requis.'
      })
    }

    return await next()
  }
}

Middleware de Rate Limiting

node ace add @adonisjs/limiter
// app/middleware/throttle_middleware.ts
import { throttle } from '@adonisjs/limiter/services/main'

export const apiThrottle = throttle({
  key: (ctx) => ctx.request.ip(),
  allowedRequests: 100,
  blockDurationInMs: 60 * 1000, // 1 minute
})

CLI Ace : L'Artisan d'AdonisJS

Le CLI Ace offre de nombreuses commandes pour accélérer le développement.

Commandes Essentielles

# Créer des éléments
node ace make:controller UserController
node ace make:model Post
node ace make:middleware Auth
node ace make:validator CreateUser
node ace make:service EmailService

# Base de données
node ace migration:run
node ace migration:rollback
node ace db:seed
node ace db:fresh

# Développement
node ace serve --watch
node ace build
node ace repl

# Tests
node ace test
node ace test --watch

Commande Personnalisée

// commands/send_newsletter.ts
import { BaseCommand } from '@adonisjs/ace'
import type { CommandOptions } from '@adonisjs/ace/types'

export default class SendNewsletter extends BaseCommand {
  static commandName = 'newsletter:send'
  static description = 'Envoyer la newsletter aux abonnés'

  static options: CommandOptions = {
    startApp: true,
  }

  async run() {
    const { default: User } = await import('#models/user')
    const { default: MailService } = await import('#services/mail_service')

    const subscribers = await User.query().where('isSubscribed', true)

    this.logger.info(`Envoi de la newsletter à ${subscribers.length} abonnés`)

    for (const user of subscribers) {
      await MailService.sendNewsletter(user)
      this.logger.info(`Newsletter envoyée à ${user.email}`)
    }

    this.logger.success('Newsletter envoyée avec succès !')
  }
}

Services et Injection de Dépendances

AdonisJS utilise un IoC Container puissant pour la gestion des dépendances.

Service Personnalisé

// app/services/email_service.ts
import { inject } from '@adonisjs/core'
import mail from '@adonisjs/mail/services/main'

@inject()
export default class EmailService {
  constructor(private logger: Logger) {}

  async sendWelcomeEmail(user: User) {
    try {
      await mail.send((message) => {
        message
          .to(user.email)
          .subject('Bienvenue sur notre plateforme !')
          .htmlView('emails/welcome', { user })
      })

      this.logger.info(`Email de bienvenue envoyé à ${user.email}`)
    } catch (error) {
      this.logger.error('Erreur lors de l\'envoi de l\'email', error)
      throw error
    }
  }

  async sendPasswordResetEmail(user: User, resetToken: string) {
    const resetUrl = `${env.get('APP_URL')}/reset-password?token=${resetToken}`

    await mail.send((message) => {
      message
        .to(user.email)
        .subject('Réinitialisation de votre mot de passe')
        .htmlView('emails/password_reset', { user, resetUrl })
    })
  }
}

Utilisation dans un Controller

// app/controllers/users_controller.ts
import { inject } from '@adonisjs/core'
import EmailService from '#services/email_service'

@inject()
export default class UsersController {
  constructor(private emailService: EmailService) {}

  async store({ request, response }: HttpContext) {
    const payload = await request.validateUsing(createUserValidator)
    const user = await User.create(payload)

    // Envoi de l'email de bienvenue
    await this.emailService.sendWelcomeEmail(user)

    return response.created(user)
  }
}

Tests Intégrés avec Japa

AdonisJS intègre Japa, un framework de tests moderne et puissant.

Test d'API

// tests/functional/posts.spec.ts
import { test } from '@japa/runner'
import { ApiClient } from '@japa/api-client'
import User from '#models/user'
import Post from '#models/post'

test.group('Posts API', (group) => {
  let user: User
  let authToken: string

  group.setup(async () => {
    user = await User.create({
      email: 'test@example.com',
      password: 'password123',
      fullName: 'Test User'
    })
    
    // Simulation de l'authentification
    const response = await new ApiClient().post('/api/auth/login').json({
      email: 'test@example.com',
      password: 'password123'
    })
    
    authToken = response.body().token
  })

  test('should get posts list', async ({ client }) => {
    const response = await client
      .get('/api/posts')
      .header('Authorization', `Bearer ${authToken}`)

    response.assertStatus(200)
    response.assertBodyContains({ meta: { total: 0 } })
  })

  test('should create a new post', async ({ client }) => {
    const postData = {
      title: 'Mon premier post',
      content: 'Contenu de mon premier post de test',
      categoryId: 1
    }

    const response = await client
      .post('/api/posts')
      .header('Authorization', `Bearer ${authToken}`)
      .json(postData)

    response.assertStatus(201)
    response.assertBodyContains({
      title: postData.title,
      content: postData.content
    })
  })

  test('should update a post', async ({ client }) => {
    const post = await Post.create({
      title: 'Post original',
      content: 'Contenu original',
      userId: user.id,
      categoryId: 1
    })

    const updateData = {
      title: 'Post mis à jour',
      content: 'Contenu mis à jour'
    }

    const response = await client
      .put(`/api/posts/${post.id}`)
      .header('Authorization', `Bearer ${authToken}`)
      .json(updateData)

    response.assertStatus(200)
    response.assertBodyContains(updateData)
  })
})

Test Unitaire

// tests/unit/email_service.spec.ts
import { test } from '@japa/runner'
import EmailService from '#services/email_service'
import User from '#models/user'

test.group('Email Service', () => {
  test('should send welcome email', async ({ assert }) => {
    const emailService = new EmailService()
    const user = new User()
    user.email = 'test@example.com'
    user.fullName = 'Test User'

    // Mock du service mail
    const mockSend = vi.fn()
    vi.mock('@adonisjs/mail/services/main', () => ({
      send: mockSend
    }))

    await emailService.sendWelcomeEmail(user)

    assert.isTrue(mockSend.calledOnce)
  })
})

Déploiement et Production

Build pour la Production

# Build de l'application
node ace build

# Démarrage en production
cd build
npm ci --production
node bin/server.js

Configuration Docker

# Dockerfile
FROM node:20-alpine

WORKDIR /app

# Copie des fichiers de dépendances
COPY package*.json ./
RUN npm ci --only=production

# Copie du code source
COPY . .

# Build de l'application
RUN node ace build

# Exposition du port
EXPOSE 3333

# Commande de démarrage
CMD ["node", "build/bin/server.js"]

Variables d'Environnement

# .env.production
NODE_ENV=production
PORT=3333
APP_KEY=your-secret-app-key
DB_CONNECTION=pg
PG_HOST=localhost
PG_PORT=5432
PG_USER=postgres
PG_PASSWORD=password
PG_DB_NAME=production_db
REDIS_CONNECTION=main
REDIS_HOST=localhost
REDIS_PORT=6379
MAIL_MAILER=smtp

AdonisJS vs Express vs NestJS

Comparaison Rapide

FonctionnalitéAdonisJSExpressNestJS
ArchitectureMVC Full-StackMinimalisteArchitecture modulaire
TypeScript✅ Natif⚠️ Configuration✅ Natif
ORM✅ Lucid inclus❌ À ajouter⚠️ TypeORM/Prisma
Auth✅ Intégré❌ À implémenter⚠️ Packages tiers
Validation✅ VineJS inclus❌ À ajouter✅ Class-validator
CLI✅ Ace puissant❌ Basique✅ Très complet
Courbe d'apprentissage📈 Modérée📈 Facile📈 Difficile

Écosystème et Packages Officiels

AdonisJS propose un écosystème riche de packages officiels :

  • @adonisjs/lucid - ORM et Query Builder
  • @adonisjs/auth - Authentification
  • @adonisjs/mail - Envoi d'emails
  • @adonisjs/redis - Client Redis
  • @adonisjs/session - Gestion des sessions
  • @adonisjs/shield - Sécurité CSRF, CSP
  • @adonisjs/limiter - Rate limiting
  • @adonisjs/drive - Système de fichiers
  • @adonisjs/attachment-lite - Upload de fichiers

Cas d'Usage Idéaux

AdonisJS excelle dans ces scenarios :

1. APIs REST Robustes

Parfait pour créer des APIs avec authentification, validation et base de données.

2. Applications Full-Stack

Idéal quand vous voulez un backend et frontend intégrés.

3. Prototypage Rapide

Convention over configuration permet de démarrer rapidement.

4. Migration depuis Laravel

Courbe d'apprentissage douce pour les développeurs Laravel.

5. Applications d'Entreprise

Architecture solide et patterns éprouvés.

Communauté et Ressources

Conclusion

AdonisJS offre un ensemble complet de fonctionnalités prêtes à l'emploi tout en permettant d'installer uniquement les modules nécessaires, ce qui en fait un choix excellent pour les développeurs qui :

Viennent de Laravel et veulent retrouver cette productivité en JavaScript
Cherchent un framework complet plutôt que d'assembler de multiples packages
Privilégient TypeScript et la type-safety
Veulent une architecture MVC éprouvée
Ont besoin de fonctionnalités comme l'ORM, l'auth, la validation out-of-the-box

AdonisJS n'est peut-être pas le framework Node.js le plus populaire, mais il est certainement l'un des plus productifs et bien conçus. Si vous cherchez une alternative moderne à Express qui ne vous oblige pas à réinventer la roue à chaque projet, AdonisJS mérite définitivement votre attention.

La version 6 marque une étape importante dans la maturité du framework, avec l'adoption complète des modules ECMAScript (ESM) et des améliorations significatives en termes de performances et d'expérience développeur.

Prêt à essayer AdonisJS ? Commencez par créer un petit projet API et découvrez par vous-même pourquoi de plus en plus de développeurs adoptent ce "Laravel de JavaScript" !


Partagez vos expériences avec AdonisJS dans les commentaires et n'hésitez pas à poser vos questions !

Steven KOULO
Steven KOULODéveloppeur Fullstack

Besoin d'un développeur pour votre prochain projet ? Je suis disponible pour des missions freelance.

Me contacter