September 27, 2026
AdonisJS.
Mengenal AdonisJS 7: framework Node.js ala Laravel — Ace CLI, routing, middleware, IoC container, VineJS, Lucid ORM dengan schema class ter-generate, Japa testing, dan contoh CRUD yang sudah dites.
AdonisJS adalah framework Node.js full-featured berbasis TypeScript yang sering disebut “Laravel-nya Node.js”. Buat yang terbiasa dengan Laravel, konsepnya akan terasa sangat familiar: CLI ace (mirip artisan), ORM Active Record (Lucid ≈ Eloquent), validator (VineJS), service provider, IoC container, dan test runner (Japa). Aku sedang mengeksplorasi AdonisJS, jadi catatan ini sekaligus rangkuman eksperimen.
Versi yang dibahas: AdonisJS 7 (@adonisjs/core 7.5.x, @adonisjs/lucid 22.x, @vinejs/vine 4.x, @japa/runner 5.x — per September 2026). AdonisJS 7 butuh Node.js ≥ 24 dan npm ≥ 11.
1. Filosofi
- Batteries included — auth, session, validasi, ORM, mail, rate limiter, CORS, shield (CSRF/security header), hashing, dan test runner tersedia sebagai paket resmi yang saling terintegrasi.
- Konvensi di atas konfigurasi — struktur folder, penamaan file (
posts_controller.ts), dan alias import (#models/post) sudah ditentukan. - Type-safety end-to-end — v7 banyak menghasilkan kode otomatis (folder
.adonisjs/): barrel controller, tipe route, schema class model dari database, sampai kontrak tipe untuk frontend. - Dibangun sendiri, bukan wrapper — HTTP server, router, dan container adalah paket AdonisJS sendiri (
@adonisjs/http-server, dst.), bukan lapisan di atas Express.
2. Membuat Project & Struktur Folder
npm create adonisjs@latest adonis-demo -- --kit=api
cd adonis-demo
npm run dev # http://localhost:3333
Starter kit resmi: hypermedia (Edge + Alpine.js), react dan vue (Inertia), api, dan api-monorepo. Kit API memakai SQLite + Lucid secara default dan sudah menyertakan auth berbasis access token.
Struktur hasil kit api (disederhanakan):
adonis-demo/
├── .adonisjs/ # KODE TER-GENERATE — jangan diedit
│ ├── client/ # tipe route/registry untuk client & test
│ └── server/controllers.ts # barrel → import { controllers } from '#generated/controllers'
├── app/
│ ├── controllers/ # posts_controller.ts
│ ├── exceptions/handler.ts # global exception handler
│ ├── middleware/ # auth_middleware.ts, dll.
│ ├── models/ # post.ts (extends PostSchema)
│ ├── transformers/ # bentuk output JSON (baru di v7)
│ └── validators/ # post.ts (VineJS)
├── bin/
│ ├── server.ts # entry HTTP server
│ ├── console.ts # entry Ace CLI
│ └── test.ts # entry test runner
├── config/ # app, auth, database, cors, hash, session, ...
├── database/
│ ├── migrations/
│ ├── schema.ts # schema class ter-generate dari DB
│ └── schema_rules.ts
├── providers/ # service provider
├── start/
│ ├── env.ts # validasi environment variable
│ ├── kernel.ts # registrasi middleware
│ └── routes.ts # definisi route
├── tests/
│ ├── bootstrap.ts
│ └── functional/
├── ace.js
├── adonisrc.ts # manifest project: providers, preloads, suites test
└── package.json
Generator Ace:
node ace make:model Post -m # model + migration
node ace make:controller posts --resource
node ace make:validator post
node ace make:middleware log_request
node ace make:test posts --suite=functional
node ace list # lihat semua perintah
3. Routing
Route didefinisikan di start/routes.ts. Controller dirujuk lewat barrel #generated/controllers (di-generate otomatis ke .adonisjs/server/controllers.ts) — controller tetap di-lazy load.
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'
import { controllers } from '#generated/controllers'
router.get('/', () => ({ hello: 'world' }))
// route dengan controller + nama route
router.get('/posts/:id', [controllers.Posts, 'show']).as('posts.show')
// resource: index, show, store, update, destroy (tanpa create/edit)
router.resource('posts', controllers.Posts).apiOnly()
// group dengan prefix + middleware
router
.group(() => {
router.get('profile', [controllers.Profile, 'show'])
})
.prefix('/api/v1/account')
.use(middleware.auth())
Named route dimanfaatkan untuk URL builder yang ter-typing, juga di test (client.visit('posts.show', ...)).
4. Middleware
Middleware adalah class dengan method handle(ctx, next):
// app/middleware/log_request_middleware.ts
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class LogRequestMiddleware {
async handle(ctx: HttpContext, next: NextFn) {
const start = performance.now()
await next() // jalankan handler
ctx.logger.info(`${ctx.request.method()} ${ctx.request.url()} ${(performance.now() - start).toFixed(1)}ms`)
}
}
Registrasi di start/kernel.ts ada tiga level:
| Level | API | Kapan jalan |
|---|---|---|
| Server | server.use([...]) | Setiap request, bahkan jika route tidak ada |
| Router | router.use([...]) | Setiap request yang cocok dengan route |
| Named | router.named({ auth: ... }) | Hanya di route yang memanggil .use(middleware.auth()) |
// start/kernel.ts (potongan dari kit api)
router.use([
() => import('@adonisjs/core/bodyparser_middleware'),
() => import('@adonisjs/auth/initialize_auth_middleware'),
])
export const middleware = router.named({
auth: () => import('#middleware/auth_middleware'),
})
5. IoC Container & Dependency Injection
AdonisJS punya IoC container bawaan. Class bisa diinjeksi lewat decorator @inject() — di constructor maupun di method.
import { inject } from '@adonisjs/core'
import type { HttpContext } from '@adonisjs/core/http'
import PostService from '#services/post_service'
@inject()
export default class PostsController {
constructor(protected postService: PostService) {}
async index(ctx: HttpContext) {
return this.postService.latest()
}
}
Service provider (providers/*.ts) dipakai untuk mendaftarkan binding, singleton, atau menjalankan kode saat boot — mirip AppServiceProvider di Laravel. Tidak ada konsep “module” seperti NestJS; organisasi kode mengikuti folder app/.
6. Validasi (VineJS)
VineJS adalah validator buatan tim AdonisJS. Di v7, validator dibuat dengan vine.create():
// app/validators/post.ts
import vine from '@vinejs/vine'
export const createPostValidator = vine.create({
title: vine.string().trim().minLength(3),
body: vine.string().trim(),
})
Di controller cukup request.validateUsing(). Tidak perlu try/catch — global exception handler mengubah error validasi menjadi 422 berisi JSON untuk API, atau redirect + flash message untuk aplikasi server-rendered.
Aturan yang butuh database pun tersedia, misalnya vine.string().email().unique({ table: 'users', column: 'email' }).
7. Data Layer: Lucid ORM
Lucid adalah ORM Active Record di atas Knex, mendukung PostgreSQL, MySQL/MariaDB, SQLite, dan MSSQL.
Perubahan besar di v7: migrations-first. Kamu menulis migration, menjalankan node ace migration:run, lalu Lucid menggenerate schema class di database/schema.ts. Model cukup meng-extend class itu.
// database/migrations/xxxx_create_posts_table.ts
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)
}
}
Setelah migration:run, database/schema.ts berisi (jangan diedit manual):
export class PostSchema extends BaseModel {
@column() declare body: string
@column.dateTime({ autoCreate: true }) declare createdAt: DateTime | null
@column({ isPrimary: true }) declare id: number
@column() declare title: string
@column.dateTime({ autoCreate: true, autoUpdate: true }) declare updatedAt: DateTime | null
}
// app/models/post.ts
import { PostSchema } from '#database/schema'
export default class Post extends PostSchema {
// relasi, hooks, computed, scope ditulis di sini
}
Query sehari-hari:
await Post.all()
await Post.findOrFail(1) // 404 otomatis via exception handler
await Post.query().where('title', 'like', '%adonis%').orderBy('id', 'desc').paginate(1, 10)
await Post.create({ title: 'Halo', body: '...' })
| Perintah Ace | Fungsi |
|---|---|
node ace make:migration posts | Buat file migration |
node ace migration:run | Jalankan migration + generate schema class |
node ace migration:rollback | Rollback batch terakhir |
node ace make:seeder User / db:seed | Seeder |
node ace make:factory Post | Factory untuk test |
Untuk output JSON, v7 memperkenalkan transformers (app/transformers/) — memilih field mana yang dikirim, mirip API Resource di Laravel.
8. Testing (Japa)
AdonisJS memakai Japa dengan plugin API client, assertion database, dan helper auth sudah terpasang di tests/bootstrap.ts. Suite unit dan functional didefinisikan di adonisrc.ts.
// tests/functional/posts.spec.ts
import { test } from '@japa/runner'
import testUtils from '@adonisjs/core/services/test_utils'
test.group('Posts', (group) => {
// setiap test dibungkus transaksi lalu di-rollback
group.each.setup(() => testUtils.db().withGlobalTransaction())
test('membuat post baru', async ({ client }) => {
const response = await client.post('/posts').json({ title: 'Halo Adonis', body: 'Isi post' })
response.assertStatus(201)
response.assertBodyContains({ title: 'Halo Adonis' })
})
test('menolak title yang terlalu pendek', async ({ client }) => {
const response = await client.post('/posts').json({ title: 'Hi', body: 'x' })
response.assertStatus(422)
})
})
node ace test # semua suite
node ace test functional # suite tertentu
node ace test --watch
9. Performa
Karena HTTP server AdonisJS ditulis sendiri (bukan di atas Express), overhead-nya cukup rendah. Di benchmark fastify/benchmarks
(run 2 September 2026, Node v24.20.0, 4 vCPU, autocannon -c 100 -d 40 -p 10, endpoint “hello world” JSON), baris adonisjs mencatat sekitar 89.837 req/s — di bawah Fastify 5.12.1 (≈97.595) tapi jauh di atas Express 5.2.1 (≈59.651).
Catatan penting soal angka itu:
- Versi yang tercantum
9.3.0adalah versi@adonisjs/http-server, bukan framework penuh v7. Benchmark-nya hanya menyalakan HTTP server + router AdonisJS, tanpa session, shield, auth, bodyparser, dan middleware lain yang ada di aplikasi nyata. - Aplikasi dari starter kit menjalankan beberapa middleware per request (lihat
start/kernel.ts), jadi throughput riilnya lebih rendah. Hapus middleware yang tidak dipakai jika performa jadi perhatian. - Seperti biasa, query database biasanya jauh lebih dominan dibanding overhead framework.
10. Ekosistem & Kematangan
| Aspek | Penilaian |
|---|---|
| Umur | Sejak 2015; v5 beralih ke TypeScript, v6 ke ESM, v7 rilis Februari 2026 |
| Paket resmi | auth, session, shield, cors, limiter, mail, drive, bouncer (otorisasi), ally (OAuth), lock, cache, transmit (SSE), inertia, vite |
| Komunitas | Lebih kecil dari Express/Nest/Next, tapi dokumentasi dan video (Adocasts) sangat baik |
| Kontrol kualitas | Tim inti kecil, API konsisten antar paket |
| Risiko | Library pihak ketiga lebih sedikit; kadang harus bikin integrasi sendiri |
| Dokumentasi | docs.adonisjs.com |
11. Contoh CRUD Minimal (Sudah Dites)
Contoh ini aku jalankan di kit api (Node 25, AdonisJS 7.5.2) dan kedua test di atas lolos.
npm create adonisjs@latest adonis-demo -- --kit=api
cd adonis-demo
node ace make:model Post -m
node ace make:controller posts --resource
node ace make:validator post
node ace make:test posts --suite=functional
- Isi migration dan validator seperti di bagian 6 & 7, lalu
node ace migration:run. - Controller:
// app/controllers/posts_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import Post from '#models/post'
import { createPostValidator } from '#validators/post'
export default class PostsController {
async index() {
return Post.query().orderBy('id', 'desc')
}
async show({ params }: HttpContext) {
return Post.findOrFail(params.id)
}
async store({ request, response }: HttpContext) {
const payload = await request.validateUsing(createPostValidator)
const post = await Post.create(payload)
return response.created(post)
}
}
- Tambahkan di
start/routes.ts:
router.resource('posts', controllers.Posts).only(['index', 'show', 'store'])
- Jalankan:
npm run dev
curl -X POST localhost:3333/posts -H 'Content-Type: application/json' \
-d '{"title":"Halo Adonis","body":"Isi post"}'
curl localhost:3333/posts
node ace test
12. Kapan Memilih AdonisJS?
- Datang dari Laravel dan ingin pengalaman serupa di TypeScript.
- Aplikasi CRUD/SaaS/monolith yang butuh auth, session, ORM, mail, dan validasi siap pakai.
- Full-stack dengan Inertia (React/Vue) tanpa harus memisah repo frontend-backend.
Kalau lebih butuh API super ringan, lihat Fastify ; kalau butuh arsitektur modular skala enterprise, lihat NestJS . Ringkasannya ada di Perbandingan Framework Node.js .
Referensi

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.