September 27, 2026
Fastify.
Mengenal Fastify 5: plugin & encapsulation, hooks, validasi berbasis JSON Schema, type provider, testing dengan inject, dan kenapa Fastify cepat.
Fastify adalah framework web Node.js yang fokus pada performa rendah-overhead dan developer experience lewat sistem plugin dan skema. Aku memakai Fastify sebagai backend di Proxmox Management Server (Fastify + TypeScript + Drizzle + BullMQ), jadi catatan ini juga merangkum pola yang terasa berguna di project nyata.
Versi yang dibahas: Fastify 5 (terbaru di npm per September 2026: 5.12.x). Fastify v5 hanya mendukung Node.js 20+.
1. Filosofi
- Schema-first — route idealnya punya JSON Schema untuk
body,querystring,params, danresponse. Skema ini dipakai untuk validasi dan serialisasi. - Semua adalah plugin — route, database, auth, bahkan konfigurasi didaftarkan sebagai plugin lewat
register(). - Encapsulation — plugin punya “konteks” sendiri. Decorator dan hook yang didaftarkan di dalam plugin tidak bocor ke luar kecuali sengaja dibuka.
- Overhead sekecil mungkin — logger (Pino), router, dan serializer dipilih/dibuat supaya cepat.
- Tetap tidak terlalu opinionated soal struktur folder, ORM, atau arsitektur.
2. Struktur Project
Struktur umum dengan @fastify/autoload (atau yang dihasilkan fastify-cli):
fastify-api/
├── src/
│ ├── app.ts # build app: register plugins & routes
│ ├── server.ts # listen()
│ ├── plugins/ # plugin global (dibungkus fastify-plugin)
│ │ ├── db.ts # decorate fastify.db
│ │ ├── auth.ts # decorate request.user + hook
│ │ └── sensible.ts
│ ├── routes/ # tiap folder = prefix URL (autoload)
│ │ ├── root.ts
│ │ └── users/
│ │ ├── index.ts # /users
│ │ └── schema.ts
│ ├── services/
│ │ └── users.service.ts
│ └── db/
│ ├── schema.ts # Drizzle schema
│ └── migrations/
├── test/
│ └── users.test.ts
├── tsconfig.json
└── package.json
Pola app.ts terpisah dari server.ts penting karena testing Fastify memakai app.inject() tanpa membuka port.
3. Routing
Fastify memakai router find-my-way yang berbasis radix tree, jadi pencocokan route tidak bergantung pada jumlah route secara linear.
app.get('/users', async () => users)
app.get<{ Params: { id: string } }>('/users/:id', async (request, reply) => {
const user = users.find((u) => u.id === Number(request.params.id))
if (!user) return reply.code(404).send({ message: 'Not found' })
return user // return value = response body
})
// bentuk lengkap
app.route({
method: 'POST',
url: '/users',
schema: { body: createUserSchema },
handler: async (request, reply) => { /* ... */ },
})
Ciri khas: handler async cukup me-return nilai; Fastify yang menyerialisasi dan mengirim.
Mengelompokkan route dengan prefix dilakukan lewat plugin:
app.register(usersRoutes, { prefix: '/api/users' })
4. Plugin, Decorator, dan Encapsulation
Plugin adalah fungsi async (fastify, opts) => {}. Di dalamnya kamu bisa mendaftarkan route, hook, decorator, atau plugin lain.
// src/plugins/db.ts
import fp from 'fastify-plugin'
import { drizzle } from 'drizzle-orm/mysql2'
export default fp(async (fastify) => {
const db = drizzle(process.env.DATABASE_URL!)
fastify.decorate('db', db)
fastify.addHook('onClose', async () => {
// tutup koneksi pool di sini
})
})
declare module 'fastify' {
interface FastifyInstance {
db: ReturnType<typeof drizzle>
}
}
| Konsep | Penjelasan |
|---|---|
register(plugin) | Membuat konteks anak baru |
fastify-plugin (fp) | “Membuka” encapsulation — decorator/hook terlihat oleh parent |
decorate() | Menambah properti ke instance (fastify.db) |
decorateRequest() / decorateReply() | Menambah properti ke request/reply |
Encapsulation inilah yang menggantikan DI container di Fastify: dependency (db, config, service) ditempel ke instance lewat decorator, dan cakupannya diatur oleh pohon plugin.
5. Hooks (Lifecycle)
Fastify tidak memakai middleware gaya (req, res, next) sebagai konsep utama; penggantinya adalah hooks di setiap tahap lifecycle.
| Hook | Kapan dipanggil | Contoh penggunaan |
|---|---|---|
onRequest | Request masuk, body belum di-parse | Auth token, rate limit |
preParsing | Sebelum body di-parse | Dekompresi stream |
preValidation | Sebelum validasi skema | Normalisasi input |
preHandler | Setelah validasi, sebelum handler | Cek otorisasi/role |
preSerialization | Sebelum payload diserialisasi | Bungkus response |
onSend | Sebelum dikirim | Ubah header |
onResponse | Response sudah terkirim | Metrics, logging |
onError | Terjadi error | Laporan error |
Hook aplikasi (bukan per request): onReady, onListen, onClose, onRoute, onRegister.
app.addHook('onRequest', async (request, reply) => {
if (!request.headers.authorization) {
return reply.code(401).send({ message: 'Unauthorized' })
}
})
Kalau benar-benar perlu middleware Express, ada @fastify/express / @fastify/middie, tapi sebaiknya pakai plugin native (@fastify/cors, @fastify/helmet, @fastify/rate-limit, dll.).
6. Validasi & Serialisasi
Validasi bawaan Fastify memakai JSON Schema yang dikompilasi oleh Ajv saat startup. Skema response dikompilasi oleh fast-json-stringify.
const userSchema = {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string', format: 'email' },
},
} as const
app.get('/users/:id', {
schema: {
params: { type: 'object', properties: { id: { type: 'integer' } }, required: ['id'] },
response: { 200: userSchema },
},
}, async (request) => { /* ... */ })
Dua keuntungan skema response:
- Lebih cepat — fast-json-stringify membuat fungsi serialisasi khusus dari skema, sehingga tidak perlu menebak tipe setiap properti seperti
JSON.stringify. - Lebih aman — properti yang tidak ada di skema (misalnya
password) tidak ikut terkirim.
Type Provider (TypeBox)
Supaya skema sekaligus menjadi tipe TypeScript, pakai type provider. Untuk @fastify/type-provider-typebox v6, TypeBox di-install sebagai paket typebox:
npm i typebox @fastify/type-provider-typebox
import Fastify from 'fastify'
import { Type, TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
const app = Fastify().withTypeProvider<TypeBoxTypeProvider>()
app.post('/users', {
schema: {
body: Type.Object({
name: Type.String({ minLength: 1 }),
email: Type.String({ format: 'email' }),
}),
},
}, async (request) => {
request.body.email // bertipe string, tanpa deklarasi manual
})
Alternatif: fastify-type-provider-zod jika tim lebih suka Zod.
7. Data Layer / ORM
Fastify tidak mengharuskan ORM tertentu. Polanya: buat plugin yang men-decorate instance dengan client database.
| Pilihan | Catatan |
|---|---|
| Drizzle | Skema TypeScript, migrasi ter-typing — dipakai di Proxmox Management Server |
| Prisma | Buat plugin yang decorate fastify.prisma |
@fastify/mysql, @fastify/postgres | Plugin resmi berbasis driver langsung |
| Kysely / Knex | Query builder |
Tutup koneksi di hook onClose supaya app.close() (termasuk di test) bersih.
8. Testing
Fastify punya app.inject() (berbasis light-my-request) yang menyuntikkan request palsu langsung ke router, tanpa socket HTTP. Cepat dan tidak butuh port.
// test/users.test.ts
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { buildApp } from '../src/app.js'
test('GET /users', async (t) => {
const app = buildApp()
t.after(() => app.close())
const res = await app.inject({ method: 'GET', url: '/users' })
assert.equal(res.statusCode, 200)
assert.ok(Array.isArray(res.json()))
})
test('POST /users menolak email invalid', async (t) => {
const app = buildApp()
t.after(() => app.close())
const res = await app.inject({
method: 'POST',
url: '/users',
payload: { name: 'A', email: 'bukan-email' },
})
assert.equal(res.statusCode, 400)
})
Catatan: error validasi bawaan Fastify mengembalikan 400, bukan 422.
9. Performa — Kenapa Fastify Cepat?
Angka dari benchmark resmi fastify/benchmarks
(juga ditampilkan di fastify.dev/benchmarks
), run 2 September 2026, Node v24.20.0, mesin Linux x64 4 vCPU, autocannon -c 100 -d 40 -p 10, endpoint “hello world” JSON:
| Framework | Versi | Req/s | Latency (ms) |
|---|---|---|---|
| node-http (tanpa framework) | v24.20.0 | 98.864 | 9,61 |
| fastify | 5.12.1 | 97.595 | 9,73 |
| adonisjs (http-server) | 9.3.0 | 89.837 | 10,67 |
| hono | 4.13.5 | 88.525 | 10,80 |
| koa | 3.2.1 | 78.662 | 12,21 |
| express | 5.2.1 | 59.651 | 16,25 |
Artinya overhead Fastify di atas http bawaan Node sangat tipis. Sumber kecepatannya:
- find-my-way — router radix tree, lookup cepat walau route banyak.
- fast-json-stringify — serializer hasil kompilasi dari skema
response. - Validasi terkompilasi — skema Ajv dikompilasi sekali saat startup, bukan diinterpretasi per request.
- Pino — logger JSON dengan overhead rendah.
- Hook tanpa rantai middleware panjang dan objek request/reply yang ringan.
Tetap ingat: ini benchmark endpoint kosong. Tim Fastify sendiri menyebut angka ini ilustratif dan menyarankan mengukur dengan beban kerja sendiri. Kalau handler-mu menunggu database 20 ms, selisih framework jadi kecil. Ada juga baris fastify-big-json (~19.929 req/s) yang menunjukkan ukuran payload JSON sangat memengaruhi hasil.
10. Ekosistem & Kematangan
| Aspek | Penilaian |
|---|---|
| Umur | Sejak 2016, v5 dirilis 2024 |
| Governance | Proyek OpenJS Foundation |
| Plugin resmi | @fastify/*: cors, helmet, jwt, cookie, multipart, rate-limit, swagger, websocket, static, autoload |
| TypeScript | Tipe bawaan + type provider |
| Dokumentasi | fastify.dev — lengkap, termasuk LTS policy |
| Dipakai sebagai adapter | NestJS (@nestjs/platform-fastify) |
11. Contoh Minimal yang Bisa Dijalankan
mkdir fastify-demo && cd fastify-demo
npm init -y
npm pkg set type=module
npm install fastify
server.js:
import Fastify from 'fastify'
const app = Fastify({ logger: true })
const users = [{ id: 1, name: 'Fanny', email: '[email protected]', password: 'rahasia' }]
const userSchema = {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' },
},
}
app.get('/users', {
schema: { response: { 200: { type: 'array', items: userSchema } } },
}, async () => users) // password otomatis tidak ikut terkirim
app.post('/users', {
schema: {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string', minLength: 1 },
email: { type: 'string', format: 'email' },
},
},
response: { 201: userSchema },
},
}, async (request, reply) => {
const user = { id: users.length + 1, ...request.body }
users.push(user)
return reply.code(201).send(user)
})
await app.listen({ port: 3000 })
node server.js
curl localhost:3000/users
curl -X POST localhost:3000/users \
-H 'Content-Type: application/json' \
-d '{"name":"Budi","email":"[email protected]"}'
# body invalid → 400 dengan pesan dari Ajv
12. Kapan Memilih Fastify?
- API/backend yang throughput dan latency-nya penting.
- Suka pendekatan schema-first (validasi + dokumentasi OpenAPI via
@fastify/swagger). - Ingin struktur modular (plugin) tanpa “berat”-nya framework penuh seperti NestJS.
Lanjut ke NestJS yang bisa berjalan di atas Fastify, atau lihat 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.