Belajar ExpressJS - Static Files & Templating
Episode 11 of 28

Belajar ExpressJS - Static Files & Templating

Menyajikan aset statis dengan express.static dan merender halaman server-side dengan EJS dan Pug, termasuk keamanan template dan struktur views yang rapi.

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

Pendahuluan

Sejauh ini kalian membangun API yang mengembalikan JSON. Episode 11 menjawab sisi lain aplikasi web: menyajikan halaman dan aset. Banyak sistem produksi — dashboard admin, halaman marketing, aplikasi monolitik — butuh Express untuk merender HTML langsung dari server.

Mengapa ini penting di 2026, di tengah popularitas SPA? Tiga alasan: SEO (HTML server-side langsung ter-index), performansi awal (tanpa menunggu bundle JavaScript), dan aplikasi internal yang tidak butuh kompleksitas frontend terpisah. Express menangani keduanya dengan express.static untuk aset dan template engine untuk halaman.

Static Files dengan express.static()

Cara Kerja Dasar

express.static() (dikenalkan di episode 5) menyajikan file apa adanya:

JSMenyajikan aset statis
import express from "express"
import path from "node:path"
 
const app = express()
 
app.use(express.static(path.join(process.cwd(), "public")))

Struktur direktori public/:

Struktur direktori public
public/
  css/style.css
  js/app.js
  images/logo.svg

File public/css/style.css lalu bisa diakses di http://localhost:3000/css/style.css — tanpa prefix tambahan.

Virtual Path Prefix

Untuk memisahkan aset publik dari file lain, gunakan prefix:

JSStatic dengan virtual prefix
app.use("/static", express.static("public"))

Sekarang aset diakses lewat http://localhost:3000/static/css/style.css. Prefix ini tidak perlu ada di filesystem — ia murni "label" URL, sehingga kalian bisa merestrukturisasi folder tanpa memutus link client.

Note

express.static juga otomatis mengirim ETag dan header cache dasar. Di episode 21 kita akan mengoptimalkan header Cache-Control untuk aset yang tidak berubah.

Templating dengan EJS

Setup

EJS menggunakan sintaks HTML dengan tag <% %> untuk logika:

Install EJS
npm install ejs
JSMengaktifkan EJS di app.js
const app = express()
 
app.set("view engine", "ejs")
app.set("views", path.join(process.cwd(), "views"))

app.set("view engine", "ejs") memberi tahu Express template mana yang dipakai; app.set("views", ...) menentukan lokasi file template.

HTMLviews/index.ejs
<!doctype html>
<html lang="id">
  <head>
    <meta charset="utf-8" />
    <title><%= title %></title>
    <link rel="stylesheet" href="/static/css/style.css" />
  </head>
  <body>
    <h1><%= title %></h1>
    <ul>
      <% users.forEach((user) => { %>
        <li><%= user.name %> - <%= user.email %></li>
      <% }) %>
    </ul>
  </body>
</html>

Tag <%= %> meng-escape output HTML otomatis — inilah pertahanan pertama terhadap XSS. Data dikirim dari handler lewat res.render.

Merender dari Controller

JSMerender halaman dengan data
export const renderUsers = async (req, res) => {
  const users = await User.find({}).lean()
  res.render("index", {
    title: "Daftar User",
    users,
  })
}

res.render("index", data) menggabungkan template index.ejs dengan data, lalu mengirim HTML jadi. Data yang dipassarkan tersedia sebagai variabel di template.

Templating dengan Pug

Pug (sebelumnya Jade) memakai sintaks indentasi tanpa tag penutup — lebih ringkas tapi lebih kaku:

Install Pug
npm install pug
JSMengaktifkan Pug
app.set("view engine", "pug")
app.set("views", path.join(process.cwd(), "views"))
views/index.pug
doctype html
html(lang="id")
  head
    meta(charset="utf-8")
    title= title
    link(rel="stylesheet", href="/static/css/style.css")
  body
    h1= title
    ul
      each user in users
        li #{user.name} - #{user.email}

Pug mengeksekusi JavaScript inline untuk logika (each ... in), dan interpolasi #{...} juga di-escape. Pilih EJS jika ingin template yang mirip HTML biasa; Pug jika menyukai sintaks minimal.

Keamanan Template

Escape Output

Kesalahan paling serius di templating adalah menampilkan input pengguna tanpa escape — celah XSS:

HTMLBenar vs salah dalam EJS
<%# BENAR: di-escape, aman terhadap XSS %>
<p><%= user.name %></p>
 
<%# SALAH: raw, XSS jika name berisi script %>
<p><%- user.name %></p>

Tag <%- %> (tanpa = yang double dengan -) tidak meng-escape — hanya boleh dipakai untuk konten yang kalian percaya sepenuhnya, seperti HTML yang dihasilkan sendiri.

Warning

Perbedaan <%= %> (escape) dan <%- %> (raw) adalah garis tipis antara aman dan XSS. Aturan: input pengguna selalu lewat <%= %>. Pengecualian raw hanya untuk konten internal yang sudah disanitasi. Detail lengkap XSS dibahas di episode 18.

Never Trust req.query

Jangan pernah merender data dari req.query langsung ke template tanpa escape — nilai query adalah input pengguna.

Common Pitfalls

views Path Relatif

app.set("views", "views") bergantung pada working directory saat node dijalankan. Gunakan path.join(process.cwd(), "views") agar bekerja di mana pun server dipanggil — penting saat deployment (episode 24).

Lupa Install Engine

Express melempar error "Cannot find module 'ejs'" jika engine tidak terinstall. npm install ejs harus jalan sebelum app.set("view engine", "ejs") berguna.

Static dan Route Bertabrakan

Jika ada file public/users.html, request GET /users.html dilayani static — bukan route. Simpan aset dengan ekstensi jelas dan gunakan prefix /static bila perlu menghindari tabrakan nama.

Tip

Untuk aplikasi yang juga menyajikan API, pisahkan dengan jelas: /api/* untuk JSON (tanpa session template), sisanya untuk halaman. Pola ini menjaga endpoint tetap murni dan mudah diuji.

Penutup

Episode 11 menambahkan wajah kedua aplikasi web: express.static untuk aset dengan virtual path, EJS dan Pug untuk rendering server-side, dan aturan keamanan template yang mencegah XSS.

Inti yang harus dibawa pulang:

  • express.static("public") menyajikan aset; virtual prefix (/static) memisahkan namespace.
  • EJS (<%= %>) dan Pug (#{...}) sama-sama meng-escape output secara default.
  • res.render("index", data) menggabungkan template dan data menjadi HTML.
  • <%- %> (raw) hanya untuk konten tepercaya — input pengguna selalu <%= %>.
  • Path views memakai path.join(process.cwd(), ...) agar portabel.

Di episode 12 selanjutnya kita akan mengamankan aplikasi dengan authentication & authorization — bcrypt, JWT dan session cookie, middleware auth, serta RBAC dengan peran. Sampai jumpa di episode 12!

Belajar ExpressJS - Static Files & Templating | Belajar ExpressJS