September 27, 2026

Express.js.

Mengenal Express 5: filosofi minimalis, struktur project, routing, middleware, validasi, data layer, testing, performa, dan contoh API yang bisa langsung dijalankan.

Express adalah framework web paling tua dan paling banyak dipakai di ekosistem Node.js. Hampir semua tutorial backend Node dimulai dari sini, dan banyak framework lain (termasuk NestJS secara default) berjalan di atasnya. Catatan ini memakai Express 5 (per September 2026 versi terbaru di npm adalah 5.2.1, butuh Node.js 18+).


1. Filosofi

Express menyebut dirinya “fast, unopinionated, minimalist web framework”. Artinya:

  • Minimalis — inti Express hanya berisi routing, sistem middleware, dan helper req/res. Tidak ada ORM, validasi, atau struktur folder bawaan.
  • Unopinionated — kamu bebas menyusun project sesuka hati. Enak untuk project kecil, tapi di tim besar perlu konvensi sendiri supaya tidak berantakan.
  • Semua adalah middleware — body parser, CORS, auth, logging, bahkan router, semuanya fungsi (req, res, next) yang dirangkai berurutan.

Kelebihan terbesar Express bukan fitur, melainkan ekosistem: hampir setiap library Node yang berhubungan dengan HTTP punya contoh integrasi dengan Express.


2. Apa yang Baru di Express 5

Express 5 akhirnya stabil setelah bertahun-tahun di v4. Perubahan penting yang sering kena saat migrasi:

PerubahanExpress 4Express 5
Error di handler asyncHarus try/catch + next(err)Promise yang reject otomatis diteruskan ke error handler
Wildcard routeapp.get('/*')Wildcard harus bernama: app.get('/*splat')
Parameter opsional/:file.:ext?Pakai kurung kurawal: /:file{.:ext}
req.body tanpa parser{}undefined
express.urlencoded()extended: trueextended: false
Status + bodyres.send(obj, 200)res.status(200).send(obj)
app.del()AdaDihapus, pakai app.delete()

Ada codemod resmi untuk membantu migrasi:

npx codemod@latest @expressjs/v5-migration-recipe

3. Struktur Project

Express tidak punya struktur bawaan. Struktur di bawah ini adalah pola “layered” yang umum dipakai:

express-api/
├── src/
│   ├── app.js              # buat instance express + pasang middleware
│   ├── server.js           # app.listen() — dipisah agar app bisa dites
│   ├── routes/
│   │   └── users.routes.js
│   ├── controllers/
│   │   └── users.controller.js
│   ├── services/
│   │   └── users.service.js
│   ├── middlewares/
│   │   ├── error-handler.js
│   │   └── validate.js
│   ├── schemas/
│   │   └── user.schema.js  # skema Zod
│   └── db/
│       └── client.js       # Prisma / Drizzle / Knex
├── test/
│   └── users.test.js
├── .env
└── package.json

Kunci pentingnya: pisahkan app dan server. app.js hanya meng-export instance Express, sedangkan server.js yang memanggil listen(). Dengan begitu test bisa memakai app tanpa membuka port.


4. Routing

Routing di Express memetakan method + path ke handler.

app.get('/users', listUsers)
app.get('/users/:id', getUser)          // req.params.id
app.post('/users', createUser)
app.delete('/users/:id', deleteUser)

// Wildcard di Express 5 wajib bernama
app.get('/files/*filepath', (req, res) => {
  // /files/a/b.txt → req.params.filepath = ['a', 'b.txt']
  res.send(req.params.filepath.join('/'))
})

Untuk mengelompokkan route, gunakan express.Router():

// src/routes/users.routes.js
import { Router } from 'express'
import * as users from '../controllers/users.controller.js'

const router = Router()
router.get('/', users.list)
router.get('/:id', users.show)
router.post('/', users.create)

export default router

// src/app.js
app.use('/api/users', usersRouter)

Router Express mencocokkan route secara linear (dicek satu per satu sesuai urutan pendaftaran). Untuk ratusan route ini jarang jadi bottleneck utama, tapi berbeda dengan router berbasis radix tree seperti milik Fastify.


5. Middleware

Middleware adalah fungsi (req, res, next). Urutan app.use() = urutan eksekusi.

JenisContohKeterangan
Built-inexpress.json(), express.static()Ikut paket express
Third-partycors, helmet, morgan, express-rate-limitInstall terpisah
Buatan sendiriauth, logging, validasiFungsi biasa
Error handler(err, req, res, next)4 parameter, didaftarkan paling akhir
// middleware logging sederhana
app.use((req, res, next) => {
  const start = performance.now()
  res.on('finish', () => {
    console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${(performance.now() - start).toFixed(1)}ms`)
  })
  next()
})

// error handler — harus 4 argumen
app.use((err, req, res, next) => {
  const status = err.status ?? 500
  res.status(status).json({ message: err.message ?? 'Internal Server Error' })
})

Di Express 5, handler async yang melempar error langsung sampai ke error handler di atas — tidak perlu lagi paket express-async-errors.


6. Validasi

Express tidak punya validasi bawaan. Pilihan populer:

LibraryGaya
ZodSkema TypeScript-first, type inference
express-validatorChain middleware (body('email').isEmail())
JoiSkema object, populer sejak lama
AjvJSON Schema, paling cepat

Contoh middleware validasi generik dengan Zod:

// src/middlewares/validate.js
export const validate = (schema) => (req, res, next) => {
  const result = schema.safeParse(req.body)
  if (!result.success) {
    return res.status(422).json({ errors: result.error.issues })
  }
  req.body = result.data // data sudah bersih & ter-cast
  next()
}

7. Dependency Injection & Modul

Express tidak punya DI container. Pola yang umum:

  • Import langsung (import db from '../db/client.js') — paling sederhana.
  • Factory function: createUsersRouter({ usersService }) — dependency dioper manual, memudahkan mocking saat test.
  • Library DI terpisah seperti awilix atau tsyringe jika project membesar.

Kalau kamu mulai merasa butuh modul, DI, dan decorator, itu tanda untuk melirik NestJS atau AdonisJS .


8. Data Layer / ORM

Tidak ada konvensi resmi. Kombinasi yang sering ditemui:

ORM / Query builderCatatan
PrismaSchema file sendiri, client ter-generate, DX bagus
DrizzleSkema di TypeScript, dekat dengan SQL, ringan
KnexQuery builder klasik
Sequelize / TypeORMORM gaya Active Record / Data Mapper
MongooseKhusus MongoDB

Karena tidak ada lifecycle hook framework, koneksi database biasanya dibuat sekali di modul db/client.js lalu di-import di mana saja.


9. Testing

Kombinasi klasik: Supertest + test runner (bawaan node:test, Vitest, atau Jest). Supertest menerima instance app langsung, tanpa perlu listen().

// test/users.test.js
import { test } from 'node:test'
import assert from 'node:assert/strict'
import request from 'supertest'
import app from '../src/app.js'

test('GET /api/users mengembalikan array', async () => {
  const res = await request(app).get('/api/users')
  assert.equal(res.status, 200)
  assert.ok(Array.isArray(res.body))
})

test('POST /api/users menolak email tidak valid', async () => {
  const res = await request(app).post('/api/users').send({ name: 'A', email: 'bukan-email' })
  assert.equal(res.status, 422)
})

Jalankan dengan node --test.


10. Performa

Express bukan framework tercepat, dan itu wajar karena desainnya:

  • Router linear — setiap request dicocokkan ke daftar layer satu per satu.
  • Rantai middleware — setiap app.use() menambah pemanggilan fungsi per request.
  • Serialisasi JSON memakai JSON.stringify biasa (tidak ada serializer berbasis skema).
  • Objek req/res di-extend dengan banyak helper.

Sebagai gambaran, di benchmark resmi fastify/benchmarks (run 2 September 2026, Node v24.20.0, 4 vCPU, autocannon -c 100 -d 40 -p 10, endpoint “hello world” JSON), Express 5.2.1 tercatat sekitar 59.651 req/s, sementara Fastify 5.12.1 sekitar 97.595 req/s. Versi Express dengan beberapa middleware tambahan turun ke sekitar 51.264 req/s.

Penting dipahami: ini overhead framework pada endpoint kosong. Di aplikasi nyata, waktu query database, panggilan API eksternal, dan logika bisnis biasanya jauh lebih dominan. Untuk kebanyakan aplikasi, Express cukup cepat.


11. Ekosistem & Kematangan

AspekPenilaian
UmurSejak 2010, salah satu paket npm paling banyak diunduh
GovernanceProyek di bawah OpenJS Foundation
Dokumentasiexpressjs.com — ringkas
MiddlewareSangat banyak (ribuan)
TypeScriptLewat @types/express, bukan bawaan
TrenStabil, rilis v5 menghidupkan kembali pengembangan

12. Contoh Minimal yang Bisa Dijalankan

mkdir express-demo && cd express-demo
npm init -y
npm pkg set type=module
npm install express zod

Buat server.js:

import express from 'express'
import { z } from 'zod'

const app = express()
app.use(express.json())

const users = [{ id: 1, name: 'Fanny', email: '[email protected]' }]

const createUserSchema = z.object({
  name: z.string().min(1),
  email: z.email(),
})

app.get('/users', (req, res) => {
  res.json(users)
})

app.get('/users/:id', (req, res) => {
  const user = users.find((u) => u.id === Number(req.params.id))
  if (!user) return res.status(404).json({ message: 'User not found' })
  res.json(user)
})

app.post('/users', async (req, res) => {
  const result = createUserSchema.safeParse(req.body)
  if (!result.success) {
    return res.status(422).json({ errors: result.error.issues })
  }
  const user = { id: users.length + 1, ...result.data }
  users.push(user)
  res.status(201).json(user)
})

// Express 5: error dari handler async otomatis sampai ke sini
app.use((err, req, res, next) => {
  console.error(err)
  res.status(500).json({ message: 'Internal Server Error' })
})

app.listen(3000, () => console.log('http://localhost:3000'))

Jalankan dan coba:

node --watch server.js

curl localhost:3000/users
curl -X POST localhost:3000/users \
  -H 'Content-Type: application/json' \
  -d '{"name":"Budi","email":"[email protected]"}'

13. Kapan Memilih Express?

  • Prototipe cepat, API kecil, atau belajar konsep HTTP di Node.
  • Butuh middleware tertentu yang hanya tersedia untuk Express.
  • Tim sudah familiar dan tidak butuh struktur/DI yang ketat.

Kalau performa dan validasi berbasis skema jadi prioritas, lihat Fastify . Perbandingan lengkapnya 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.

Comments