Belajar ExpressJS - Router & Modular App
Episode 8 of 28

Belajar ExpressJS - Router & Modular App

Membedah express.Router, cara memecah aplikasi menjadi route modular per resource, mounting di app, dan refactor aplikasi Hello World menjadi struktur routes controllers dan middleware yang bersih.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 7 error handling terpusat berdiri, aplikasi kalian butuh organisasi. Episode 8 memperkenalkan express.Router() — alat untuk memecah satu app.js yang makin panjang menjadi modul-modul route yang mandiri.

Mengapa ini penting? Bayangkan aplikasi dengan route users, products, orders, dan auth — jika semua ditulis di app.js, file itu akan membengkak hingga ribuan baris dan konflik git menjadi mimpi buruk. Router memungkinkan tiap resource dirawat di file sendiri, diuji sendiri, dan bahkan dipakai ulang di mount point berbeda. Ini juga refactor pertama yang mengaktifkan struktur folder yang kita siapkan di episode 3.

express.Router()

Router adalah "mini-app": ia punya get, post, use, dan seluruh API route seperti app, tapi bukan server yang berdiri sendiri:

JSsrc/routes/users.js - router pertama
import { Router } from "express"
 
const router = Router()
 
router.get("/", (req, res) => {
  res.json({ data: [], path: "/users" })
})
 
router.get("/:id", (req, res) => {
  res.json({ id: req.params.id })
})
 
router.post("/", (req, res) => {
  res.status(201).json(req.body)
})
 
export default router

Perhatikan path di dalam router relatif terhadap mount point. router.get("/") di sini berarti GET /users setelah router di-mount di /users. Ini kunci menghindari duplikasi prefix.

Mounting di App

Router dihubungkan ke app dengan app.use():

JSsrc/app.js - mounting router
import express from "express"
import usersRouter from "./routes/users.js"
 
const app = express()
 
app.use(express.json())
 
app.use("/users", usersRouter)
 
app.use((req, res) => {
  res.status(404).json({ error: "NOT_FOUND", message: "Route tidak ditemukan" })
})
 
export default app

Sekarang seluruh route di usersRouter hidup di bawah prefix /users. Mengubah prefix hanya perlu satu baris — router itu sendiri tidak tahu (dan tidak perlu tahu) di mana ia dipasang.

Struktur Modular Penuh

Struktur yang kita siapkan di episode 3 kini terisi. Pola routes + controllers memisahkan definisi URL dari logika:

Controllers

JSsrc/controllers/userController.js
const users = []
 
export const listUsers = (req, res) => {
  res.json({ data: users })
}
 
export const getUser = (req, res) => {
  const user = users.find((u) => u.id === Number(req.params.id))
  if (!user) {
    const err = new Error("User tidak ditemukan")
    err.status = 404
    throw err
  }
  res.json({ data: user })
}
 
export const createUser = (req, res) => {
  const user = { id: users.length + 1, ...req.body }
  users.push(user)
  res.status(201).json({ data: user })
}

Controller adalah fungsi murni (req, res) yang menangani satu aksi — tidak tahu menahu soal URL. Inilah unit yang bisa diuji langsung tanpa server (ujian automated dibahas di episode 27).

Routes memakai Controller

JSsrc/routes/users.js - versi ter-refactor
import { Router } from "express"
import { listUsers, getUser, createUser } from "../controllers/userController.js"
 
const router = Router()
 
router.get("/", listUsers)
router.get("/:id", getUser)
router.post("/", createUser)
 
export default router

Route kini hanya peta: path dan metode dipasangkan ke controller. Logika berada di controller, mudah dibaca dan diuji.

Menambahkan Resource Baru

Menambah resource tinggal mengulang pola yang sama:

JSsrc/app.js - mount resource kedua
import usersRouter from "./routes/users.js"
import productsRouter from "./routes/products.js"
 
app.use("/users", usersRouter)
app.use("/products", productsRouter)

Setiap resource mandiri. Ini contoh nyata dari filosofi unopinionated Express — tidak ada framework yang memaksa struktur ini, tapi pola routes/controllers ini adalah standar industri yang paling banyak dipakai.

Middleware per Router

Router juga bisa punya middleware khusus, misalnya validasi hanya untuk route users:

JSMiddleware khusus router
const router = Router()
 
router.use((req, res, next) => {
  console.log(`users router: ${req.method} ${req.path}`)
  next()
})

Middleware yang dipasang router.use() hanya berjalan untuk request yang masuk ke router tersebut — tidak untuk route lain di app. Ini berguna untuk logging spesifik resource atau autentikasi grup route (episode 12).

Note

Perbedaan req.path di dalam router: karena router dipasang di /users, req.path di dalam handler router mengembalikan bagian setelah mount point (misal /42), sedangkan req.originalUrl selalu URL lengkap. Gunakan sesuai kebutuhan — keduanya sering tertukar.

Common Pitfalls

Prefix Ganda di Router

Menulis router.get("/users") di router yang di-mount di /users menghasilkan /users/users. Di dalam router, path selalu relatif — jangan ulangi prefix.

Lupa Export Default

Router yang tidak di-export membuat app.use("/users", usersRouter) gagal saat import. Selalu export default router di akhir file router.

404 Di Dalam Router

router.use((req, res) => ...) untuk 404 hanya menangkap request di dalam prefix router tersebut. 404 global harus tetap didefinisikan di app.js setelah semua mount.

Tip

Jadikan pola "routes = peta, controllers = logika" sebagai kebiasaan tetap. Saat episode 9 dan 10 menambahkan database, kalian cukup menambah lapisan model — routes dan controllers nyaris tidak berubah.

Penutup

Episode 8 memecah aplikasi monolitik menjadi modul: express.Router() membungkus route per resource, app.use(prefix, router) menempelkannya ke app, dan pola routes/controllers memisahkan peta URL dari logika bisnis.

Inti yang harus dibawa pulang:

  • express.Router() adalah mini-app: route di dalamnya relatif terhadap mount point.
  • Mount dengan app.use("/users", usersRouter); ganti prefix cukup satu baris.
  • Pemisahan routes (peta) vs controllers (logika) memudahkan pengujian dan perawatan.
  • Router bisa punya middleware sendiri yang hanya berlaku di dalamnya.
  • 404 global tetap didefinisikan di app.js, bukan di router.

Di episode 9 selanjutnya kita akan menambah lapisan database pertama: CRUD dengan MongoDB dan Mongoose — schema, model, koneksi, dan REST CRUD lengkap. Sampai jumpa di episode 9!

Belajar ExpressJS - Router & Modular App | Belajar ExpressJS