Wie man eine CRUD-API mit AdonisJS erstellt
Erstellen Sie eine CRUD-API mit AdonisJS v7: Posts-Routen, Lucid-Modelle, VineJS-Validierung und per curl getestete JSON-Antworten.
AdonisJS ist ein TypeScript-first, von Laravel inspiriertes Node.js-Framework, das Routing, ein ORM (Lucid), Validierung (VineJS), Authentifizierung und eine CLI (ace) in einem Paket vereint – was es zu einer starken Wahl für CRUD-APIs macht, wenn man Konventionen statt manuellem Verdrahten bevorzugt. Diese Anleitung erstellt einen vollständigen posts-CRUD-Endpunkt mit AdonisJS v7 und deckt jeden Schritt vom Scaffolding bis zu einem getesteten curl-Request ab, einschließlich VineJS-Validierung und strukturierter Fehlerantworten.
AdonisJS v7 unterscheidet sich von v6 in drei Punkten, die dieses Tutorial berücksichtigt: Setup (Node.js 24+, npm create adonisjs@latest); das API-Starter-Kit (ein Turborepo-Monorepo mit dem Backend unter apps/backend/, mit vorkonfiguriertem Lucid ORM und Authentifizierung); und Code-Muster (Routing über #generated/controllers, Lucid-Models, die generierte Schema-Klassen aus apps/backend/database/schema.ts erweitern). Die folgenden Beispiele folgen durchgehend dieser v7-Struktur.
Wichtige Erkenntnisse
- Das aktuelle AdonisJS v7 API-Starter-Kit wird mit
npm create adonisjs@latest my-api -- --kit=apieingerichtet, nicht mit der alten v6-Formnpm init adonisjs@latest ... -- -K=api. - AdonisJS v7 erfordert Node.js 24+ und npm 11+; aktualisieren Sie daher Ihre lokale Laufzeitumgebung, bevor Sie ein Projekt scaffolden.
- Neue v7-Starter-Kits enthalten Lucid ORM, SQLite, Authentifizierung, VineJS und ein Test-Setup direkt ab Werk, sodass Lucid in einem frischen API-Starter nicht manuell hinzugefügt werden muss.
- Aktuelle v7-Routing-Beispiele verwenden den generierten Controller-Barrel-Import
import { controllers } from '#generated/controllers'und binden Routen mit Handler-Syntax im Stil[controllers.Posts, 'index']. - VineJS-Validierungsfehler bei API-Requests geben HTTP 422 mit einem strukturierten
errors-Array zurück, das Feldnamen, fehlgeschlagene Regeln und Meldungen enthält – genau das Format, das Frontend-Formularbibliotheken für die feldgenaue Fehleranzeige erwarten. - Lucids
Post.findOrFail(id)wirft eine Not-Found-Exception, die AdonisJS automatisch als 404-JSON-Antwort rendert, ohne manuelle Null-Prüfung.
Voraussetzungen
AdonisJS v7 erfordert Node.js 24 oder höher und npm 11 oder höher. Überprüfen Sie Ihre lokalen Versionen, bevor Sie scaffolden:
node --version # muss >= 24.x sein
npm --version # muss >= 11.x sein
Die v7-Starter-Kits enthalten Lucid standardmäßig mit SQLite konfiguriert, sodass Sie für dieses Tutorial nicht mit PostgreSQL beginnen müssen. SQLite schreibt in eine lokale Datei und ist ideal für eine unkomplizierte CRUD-Übung. Wenn Sie später zu PostgreSQL, MySQL oder einer anderen SQL-Datenbank wechseln, unterstützt Lucid weiterhin diese Treiber; der Controller- und Validierungscode unten bleibt dabei unverändert.
Projekt mit dem API-Starter-Kit scaffolden
Das AdonisJS v7 API-Starter-Kit wird mit npm create adonisjs@latest <name> -- --kit=api eingerichtet. Führen Sie den Befehl jetzt aus:
npm create adonisjs@latest my-api -- --kit=api
Das Flag --kit=api wählt das API-Starter-Kit aus. Es liefert ein backend-orientiertes Projekt mit API-freundlichen Standardeinstellungen, einschließlich Lucid ORM, VineJS-Validierung, Authentifizierung, CORS, Testing und einer standardmäßigen SQLite-Datenbank.
Wechseln Sie in das Projektverzeichnis und starten Sie den Entwicklungsserver:
cd my-api
npm run dev
Das Backend läuft standardmäßig auf http://localhost:3333. Das API-Starter-Kit enthält außerdem Authentifizierungsendpunkte unter /api/v1/auth, aber dieses Tutorial hält die posts-Endpunkte öffentlich zugänglich, damit Sie sich zunächst auf CRUD konzentrieren können.
Das Standard-Lucid-Setup verwenden
Discover how at OpenReplay.com.
Lucid ist das SQL-ORM von AdonisJS. In älteren Tutorials sehen Sie häufig einen manuellen Einrichtungsschritt wie diesen:
node ace add @adonisjs/lucid
Führen Sie diesen Befehl in einem frischen v7 API-Starter nicht aus. Lucid ist bereits installiert und konfiguriert, und das Starter-Kit verwendet standardmäßig SQLite. Sie können direkt mit der Erstellung einer Tabelle fortfahren.
Eine Migration generieren
Eine Migration ist eine versionierte Schemaänderung. Generieren Sie eine Migration für die posts-Tabelle:
node ace make:migration posts
Der Befehl erstellt eine Datei mit Zeitstempel unter database/migrations/. Öffnen Sie diese und definieren Sie die Spalten der Tabelle:
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)
}
}
Der Import-Pfad ist weiterhin @adonisjs/lucid/schema. Die wichtige v7-Neuerung kommt nach dem Ausführen der Migration: Lucid generiert Schema-Klassen aus Ihrer Datenbank, sodass Ihr App-Model typisierte Spaltendefinitionen erben kann.
Migration ausführen und das Model erstellen
Wenden Sie das Schema auf die Datenbank an:
node ace migration:run
Lucid erstellt die posts-Tabelle und generiert database/schema.ts. Diese generierte Datei enthält eine PostsSchema-Klasse mit typisierten Spaltendefinitionen für die Tabelle. Bearbeiten Sie database/schema.ts nicht direkt; sie wird nach Migrationen neu generiert.
Erstellen Sie nun das Model:
node ace make:model Post
Öffnen Sie app/models/post.ts und lassen Sie das Model die generierte Schema-Klasse erweitern, die aus database/schema.ts exportiert wird:
import { PostSchema } from '#database/schema'
export default class Post extends PostSchema {}
Das ist das v7-Lucid-Model-Muster. Die Spalten-Dekoratoren befinden sich in der generierten Schema-Klasse, während Ihr App-Model der Ort ist, an dem Sie später Beziehungen, Hooks, Query-Scopes und benutzerdefinierte Methoden hinzufügen.
Einen Controller generieren
node ace make:controller posts --resource erstellt einen ressourcenorientierten Controller für die posts-Ressource. Führen Sie den Befehl jetzt aus:
node ace make:controller posts --resource
Das Flag --resource erstellt die konventionellen REST-Methoden: index, create, store, show, edit, update und destroy. Für eine JSON-API benötigen Sie nur fünf davon:
| Methode | Zweck |
|---|---|
index | Alle Posts auflisten |
store | Einen neuen Post aus dem Request-Body erstellen |
show | Einen einzelnen Post anhand der ID zurückgeben |
update | Einen vorhandenen Post anhand der ID ändern |
destroy | Einen Post anhand der ID löschen |
Die generierten Methoden create und edit können gelöscht werden, da diese für server-gerenderte HTML-Formulare vorgesehen sind.
Routen verdrahten
Aktuelle v7-Dokumentation zeigt, dass Controller über die generierte Barrel-Datei #generated/controllers importiert werden. Öffnen Sie start/routes.ts und fügen Sie die posts-Routen hinzu:
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')
Damit werden die API-Endpunkte unter /api/v1/posts registriert, passend zum versionierten API-Stil des v7 API-Starter-Kits.
| Methode | Pfad | Controller-Methode | Beschreibung |
|---|---|---|---|
| GET | /api/v1/posts | index | Alle Posts auflisten |
| POST | /api/v1/posts | store | Einen Post erstellen |
| GET | /api/v1/posts/:id | show | Einen Post abrufen |
| PUT | /api/v1/posts/:id | update | Einen Post aktualisieren |
| PATCH | /api/v1/posts/:id | update | Einen Post teilweise aktualisieren |
| DELETE | /api/v1/posts/:id | destroy | Einen Post löschen |
AdonisJS dokumentiert weiterhin router.resource() für RESTful-Routen, aber eine einfache Ressourcen-Route registriert sieben Routen, einschließlich GET /posts/create und GET /posts/:id/edit. Die obige explizite Routenliste beschränkt die API-Oberfläche auf JSON-only und verhindert das versehentliche Exponieren von Formular-Routen.
Überprüfen Sie die aktiven Routen mit:
node ace list:routes
Sollten die Routen nicht erscheinen, starten Sie den Entwicklungsserver neu, damit die generierte Controller-Barrel-Datei aktualisiert wird.
VineJS-Validierung hinzufügen
VineJS ist die von AdonisJS verwendete Validierungsbibliothek. Generieren Sie eine Validator-Datei:
node ace make:validator post
Öffnen Sie app/validators/post.ts und definieren Sie Validatoren für das Erstellen und Aktualisieren von 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(),
})
Der aktuelle AdonisJS-Validierungsleitfaden verwendet vine.create() zur Definition von Validatoren. In Controllern führt request.validateUsing() den Validator gegen den Request-Body aus und gibt die typisierte, validierte Payload zurück.
Wenn request.validateUsing() bei einem API-Request fehlschlägt, gibt AdonisJS HTTP 422 mit einem strukturierten JSON-errors-Array zurück. Jeder Fehler enthält den Feldnamen, die fehlgeschlagene Regel und eine Meldung. Das ist das Format, das Frontend-Code direkt auf den Formularfeld-Zustand abbilden kann.
Session-Replays von Frontends, die CRUD-APIs konsumieren, zeigen häufig ein spezifisches Fehlermuster: Ein Formular wird abgeschickt, der Server gibt 422 zurück, aber in der Benutzeroberfläche erscheint keine feldgenaue Fehlermeldung. Die Ursache ist oft, dass der Server statt strukturierter Feldfehler eine einfache Zeichenkette zurückgegeben hat. VineJS liefert die strukturierte Form standardmäßig – bewahren Sie diese also. Fangen Sie die Validierungsausnahme nicht ab und werfen Sie sie nicht als abgeflachte Zeichenkette erneut, es sei denn, Sie erhalten dabei den feldgenauen Vertrag aufrecht.
Die Controller-Methoden implementieren
Jede Controller-Aktion ist ein kurzer Handler. Die beiden relevanten Fehlerpfade – ein fehlendes Datensatz und ungültige Eingaben – werden von Lucid und VineJS ohne manuelle Verzweigung behandelt.
Öffnen Sie app/controllers/posts_controller.ts und ersetzen Sie dessen Inhalt:
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()
}
}
Einige Details sind dabei wichtig:
storegibtresponse.created(post)zurück, was HTTP 201 setzt.destroygibtresponse.noContent()zurück, was HTTP 204 setzt.await post.delete()wird mitawaitaufgerufen, sodass die Antwort nicht zurückgegeben wird, bevor das Löschen abgeschlossen ist.Post.findOrFail(params.id)wirft eine Exception, wenn keine Zeile übereinstimmt. AdonisJS wandelt diese Exception automatisch in eine 404-Antwort um.
Mit curl testen
Eine AdonisJS v7 CRUD-API kann mit curl vollständig getestet werden – jede Ressourcen-Aktion (index, store, show, update, destroy) entspricht einem einzelnen HTTP-Request unter /api/v1/posts. Erstellen Sie bei laufendem npm run dev einen Post:
curl -X POST http://localhost:3333/api/v1/posts \
-H "Content-Type: application/json" \
-d '{"title": "First post", "body": "Hello from Adonis"}'
Eine erfolgreiche store-Antwort gibt HTTP 201 mit dem erstellten Datensatz zurück:
{
"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
}
Lösen Sie nun Validierungsfehler aus, indem Sie einen zu kurzen Titel und einen leeren Body senden:
curl -X POST http://localhost:3333/api/v1/posts \
-H "Content-Type: application/json" \
-d '{"title": "ok", "body": ""}'
Die Antwort ist HTTP 422 mit einem strukturierten VineJS-Fehler-Array:
{
"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"
}
]
}
Das ist der Vertrag, den ein Frontend auf den Formularstatus abbildet: Iterieren Sie über errors, schlüsseln Sie nach field auf und rendern Sie message neben dem entsprechenden Eingabefeld.
Rufen Sie einen nicht vorhandenen Datensatz ab, um den 404-Pfad zu sehen:
curl -i http://localhost:3333/api/v1/posts/9999
findOrFail erzeugt eine 404-Antwort, anstatt null mit einem irreführenden 200-Status zurückzugeben.
Die übrigen CRUD-Requests folgen demselben Muster:
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"}' # partial update
curl -X DELETE http://localhost:3333/api/v1/posts/1 # destroy → 204
Wie es weitergeht
Eine AdonisJS v7 CRUD-API benötigt drei weitere Bausteine, bevor sie produktionsreif ist: Authentifizierung, Paginierung und Tests.
Für die Authentifizierung enthält das API-Starter-Kit bereits Auth-Routen und ein auf Access-Tokens ausgerichtetes Setup. Schützen Sie die posts-Routen mit dem Auth-Middleware, wenn diese nur für eingeloggte Benutzer zugänglich sein sollen. Falls Sie Auth zu einem bestehenden Nicht-Starter-Projekt hinzufügen, lautet der dokumentierte Befehl:
node ace add @adonisjs/auth --guard=access_tokens
Für große Datenkollektionen ersetzen Sie Post.all() in index durch Lucid-Paginierung, sodass der Endpunkt jeweils eine Seite statt der gesamten Tabelle zurückgibt.
Für Tests verwenden Sie Japa, den vom Starter-Kit konfigurierten Test-Runner. Das v7 API-Starter-Kit verwendet:
npm run test
Beginnen Sie mit dem Testen der drei Verträge, die für das Frontend am wichtigsten sind: Ein erfolgreicher Create-Request gibt 201 zurück, Validierungsfehler geben 422 mit feldgenauen Fehlern zurück, und fehlende Datensätze geben 404 zurück.
Häufig gestellte Fragen
Warum verwendet dieses v7-Tutorial explizite Routen statt router.resource().apiOnly()?
Die aktuellen AdonisJS v7-Routing-Dokumentationen dokumentieren router.resource() prominent, aber eine einfache Ressourcen-Route registriert sieben Routen, einschließlich GET /posts/create und GET /posts/:id/edit für HTML-Formulare. Für eine reine JSON-API machen explizite GET-, POST-, PUT-, PATCH- und DELETE-Routen die öffentliche API-Oberfläche transparent und verhindern das versehentliche Registrieren von Formular-Routen.
Warum erstellt node ace make:controller --resource sieben Methoden, wenn diese API nur fünf verwendet?
Das Flag --resource erstellt den vollständigen RESTful-Controller-Satz: index, create, store, show, edit, update und destroy. Die Methoden create und edit dienen dem Rendern von HTML-Formularen in server-gerenderten Anwendungen. Eine JSON-API benötigt nur index, store, show, update und destroy, sodass die ungenutzten Methoden create und edit bedenkenlos gelöscht werden können.
Welche HTTP-Methoden sollte der Update-Endpunkt unterstützen?
Dieses Tutorial bildet sowohl PUT als auch PATCH unter /api/v1/posts/:id auf dieselbe update-Controller-Methode ab. PUT signalisiert typischerweise eine vollständige Ersetzung der Ressource und PATCH eine teilweise Aktualisierung, aber viele CRUD-APIs behandeln beide durch dieselbe Validierungs- und Merge-Logik. Führen Sie node ace list:routes aus, um die aktive Zuordnung in Ihrer Anwendung zu bestätigen.
Funktioniert ein AdonisJS v6-Tutorial mit v7?
Nicht zuverlässig. Ein Großteil der Controller-Logik sieht noch vertraut aus, aber v7 ändert wichtige Tutorial-Details: Die Laufzeitanforderung ist Node.js 24+, das Scaffolding verwendet npm create adonisjs@latest, Starter-Kits enthalten Lucid und Auth standardmäßig, aktuelle Routing-Beispiele verwenden #generated/controllers, und Lucid-Models erweitern generierte Schema-Klassen aus #database/schema. Behandeln Sie v6-Tutorials nur als konzeptionelle Referenz.
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