Introduzione ad AdonisJS
AdonisJS è un framework Node.js completo, scritto in TypeScript, pensato per costruire applicazioni web e API robuste e manutenibili. A differenza di molti framework minimalisti dell'ecosistema JavaScript, AdonisJS adotta un approccio "batteries included": offre fin da subito un ORM, un sistema di autenticazione, la validazione dei dati, un motore di template, un router espressivo e un'infrastruttura solida per i test. Chi proviene da Laravel, Ruby on Rails o Django troverà molte convenzioni familiari.
Perché scegliere AdonisJS
L'ecosistema Node.js è storicamente dominato da framework minimali come Express o Fastify, che lasciano allo sviluppatore la scelta di ogni singolo componente. Questo approccio offre grande libertà, ma comporta anche un notevole lavoro di integrazione e un rischio di frammentazione quando il progetto cresce. AdonisJS propone invece uno stack coerente e ben integrato, in cui tutti i moduli sono progettati per funzionare insieme e seguono le stesse convenzioni. Il risultato è una maggiore produttività, soprattutto nei progetti di medie e grandi dimensioni.
Tra i punti di forza principali troviamo il supporto nativo a TypeScript, un sistema di Dependency Injection basato su decoratori, un ORM chiamato Lucid ispirato ad Active Record, un router potente con supporto a middleware e gruppi di rotte, un sistema di validazione dichiarativo e una CLI ricca di comandi per generare file e gestire il ciclo di vita dell'applicazione.
Installazione e creazione di un progetto
Per iniziare con AdonisJS è sufficiente avere installato Node.js in una versione recente (18 o superiore). La creazione di un nuovo progetto avviene tramite il comando npm init seguito dal pacchetto ufficiale:
npm init adonisjs@latest my-app
Durante l'installazione la CLI chiede di scegliere uno starter kit. Le opzioni principali sono web per applicazioni full-stack con rendering lato server, api per progetti API-only e slim per una configurazione minimale. Dopo aver selezionato lo starter, il comando installa le dipendenze e prepara la struttura del progetto.
Una volta completata l'installazione, è possibile avviare il server di sviluppo con il seguente comando:
cd my-app
node ace serve --watch
Il comando ace è la CLI interna di AdonisJS, analoga ad artisan in Laravel. Il flag --watch abilita il ricaricamento automatico ad ogni modifica dei file sorgente.
Struttura del progetto
La struttura delle cartelle generata da AdonisJS segue convenzioni precise. Nella cartella app si trovano i controller, i modelli, i middleware e le classi di validazione. La cartella config contiene i file di configurazione per database, sessioni, autenticazione e altri servizi. La cartella start ospita i file eseguiti all'avvio dell'applicazione, tra cui la definizione delle rotte. La cartella database contiene migrazioni, seeder e factory. Infine resources raccoglie i template e gli asset frontend.
Definizione delle rotte
Le rotte vengono registrate nel file start/routes.ts. Il router di AdonisJS supporta tutti i verbi HTTP standard, parametri dinamici, gruppi, prefissi e middleware. Un esempio basilare:
import router from '@adonisjs/core/services/router'
// Rotta semplice con risposta inline
router.get('/', async () => {
return { hello: 'world' }
})
// Rotta con parametro dinamico
router.get('/users/:id', async ({ params }) => {
return { userId: params.id }
})
Per progetti più strutturati è buona pratica legare le rotte a metodi di controller anziché scrivere la logica inline. AdonisJS supporta il lazy loading dei controller tramite import dinamici, migliorando i tempi di avvio:
import router from '@adonisjs/core/services/router'
// Caricamento pigro del controller
const UsersController = () => import('#controllers/users_controller')
router.get('/users', [UsersController, 'index'])
router.post('/users', [UsersController, 'store'])
router.get('/users/:id', [UsersController, 'show'])
Controller
I controller sono classi TypeScript collocate nella cartella app/controllers. Possono essere generati rapidamente tramite la CLI:
node ace make:controller User
Ogni metodo del controller riceve come argomento un oggetto HttpContext che contiene la richiesta, la risposta, i parametri, la sessione e altri servizi utili. Un esempio di controller che gestisce operazioni CRUD di base:
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
export default class UsersController {
// Restituisce l'elenco completo degli utenti
async index({ response }: HttpContext) {
const users = await User.all()
return response.ok(users)
}
// Crea un nuovo utente a partire dal corpo della richiesta
async store({ request, response }: HttpContext) {
const data = request.only(['email', 'fullName', 'password'])
const user = await User.create(data)
return response.created(user)
}
// Recupera un singolo utente tramite identificativo
async show({ params, response }: HttpContext) {
const user = await User.findOrFail(params.id)
return response.ok(user)
}
}
Il database con Lucid ORM
Lucid è l'ORM ufficiale di AdonisJS ed è fortemente ispirato al pattern Active Record. Ogni modello rappresenta una tabella del database e fornisce metodi per eseguire query, creare relazioni e gestire il ciclo di vita dei record. La configurazione del database si trova in config/database.ts e supporta PostgreSQL, MySQL, SQLite e altri dialetti.
Un modello tipico viene definito estendendo la classe BaseModel:
import { DateTime } from 'luxon'
import { BaseModel, column, hasMany } from '@adonisjs/lucid/orm'
import type { HasMany } from '@adonisjs/lucid/types/relations'
import Post from '#models/post'
export default class User extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column()
declare email: string
@column()
declare fullName: string
// Relazione uno-a-molti con i post dell'utente
@hasMany(() => Post)
declare posts: HasMany<typeof Post>
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime
}
Le migrazioni consentono di versionare lo schema del database. Possono essere generate con la CLI e poi eseguite tramite un comando dedicato:
node ace make:migration users
node ace migration:run
Validazione delle richieste
AdonisJS include VineJS, una libreria di validazione veloce e type-safe. Gli schemi di validazione vengono definiti come oggetti e possono essere riutilizzati in più punti dell'applicazione:
import vine from '@vinejs/vine'
// Schema di validazione per la creazione di un utente
export const createUserValidator = vine.compile(
vine.object({
email: vine.string().email(),
fullName: vine.string().minLength(2).maxLength(100),
password: vine.string().minLength(8),
})
)
Lo schema può essere applicato all'interno di un controller tramite il metodo validateUsing:
async store({ request, response }: HttpContext) {
// Valida e tipizza automaticamente i dati della richiesta
const payload = await request.validateUsing(createUserValidator)
const user = await User.create(payload)
return response.created(user)
}
Autenticazione
Il pacchetto ufficiale @adonisjs/auth offre diverse strategie di autenticazione, tra cui sessioni, token di accesso e autenticazione di base. Dopo aver installato il modulo e configurato una guardia, è possibile proteggere le rotte con un middleware dedicato:
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'
// Gruppo di rotte protette dal middleware di autenticazione
router
.group(() => {
router.get('/dashboard', '#controllers/dashboard_controller.index')
router.get('/profile', '#controllers/profile_controller.show')
})
.use(middleware.auth())
Test
AdonisJS integra Japa come test runner ufficiale. I test possono essere unitari o funzionali e sfruttano un client HTTP per simulare le richieste all'applicazione. Un test funzionale di esempio:
import { test } from '@japa/runner'
test.group('Users', () => {
test('should return the list of users', async ({ client }) => {
// Richiesta HTTP simulata verso l'endpoint
const response = await client.get('/users')
response.assertStatus(200)
response.assertBodyContains([])
})
})
I test si avviano con il comando node ace test, che configura automaticamente l'ambiente di testing e isola il database tramite transazioni.
Conclusioni
AdonisJS rappresenta una scelta matura per chi desidera costruire applicazioni Node.js strutturate senza rinunciare all'ergonomia di TypeScript. La sua filosofia "batteries included" riduce il tempo dedicato alle decisioni infrastrutturali e permette di concentrarsi sulla logica di business. Le convenzioni chiare, la documentazione curata e l'integrazione tra i moduli ne fanno uno strumento ideale per progetti che richiedono scalabilità, manutenibilità e un'esperienza di sviluppo paragonabile a quella offerta dai framework più affermati di altri linguaggi. Chi si avvicina per la prima volta troverà una curva di apprendimento ragionevole, mentre gli sviluppatori esperti potranno apprezzare la flessibilità e la qualità complessiva dell'architettura.