2.1
Alur Request dan Middleware `caching`
Satu request masuk lewat api/server.js, melewati cors(), express.json(), lalu Router dari api/routes.js. Router menempelkan header Cache-Control ke setiap respons, kemudian empat route GET memanggil middleware caching yang menyimpan body respons di sebuah objek in-memory. Halaman ini hanya membahas urutan lapis itu dan efek sampingnya; bentuk body serta kode status tiap endpoint dibahas di Kontrak Response dan Penanganan Error, sedangkan daftar lengkap route ada di Referensi Endpoint.
Urutan lapis di api/server.js
Berkas api/server.js hanya 18 baris: memuat .env lewat dotenv, membuat aplikasi express, memasang empat lapis, lalu memanggil listen. Tidak ada lapis lain di antara express() dan routes (api/server.js:8-181).
graph LR
req["request masuk"] --> tp["trust proxy: 1"]
tp --> cors["cors()"]
cors --> json["express.json()"]
json --> router["Router dari api/routes.js"]
router --> cc["header Cache-Control"]
cc --> match{"route cocok?"}
match -->|"GET /"| idx["index handler tanpa caching"]
match -->|"GET /surah, /juz/:juz"| caching["middleware caching"]
match -->|"path lain"| fb["router.all('*') 404"]
style tp fill:#f59e0b,color:#000
style cc fill:#f59e0b,color:#000
style caching fill:#8b5cf6,color:#fff
style idx fill:#2d7d46,color:#fff
style fb fill:#dc2626,color:#fff
%% fig: Lapis request dari express() sampai pencocokan route
%% desc: Request melewati trust proxy, cors, express.json, lalu Router; header Cache-Control dipasang sebelum route dicocokkan, dan hanya empat route GET yang masuk ke middleware caching.| Baris | Kode | Peran |
|---|---|---|
| 8 | const port = process.env.PORT dengan fallback 3000 | port server |
| 11 | server.set('trust proxy', 1) | mempercayai satu lapis proxy di depan aplikasi |
| 12 | server.use(cors()) | memasang header CORS untuk semua respons |
| 13 | server.use(express.json()) | memasang parser body JSON |
| 14 | server.use(routes) | menyerahkan seluruh path ke Router api/routes.js |
| 16-18 | server.listen(port, ...) | menyalakan server dan mencetak Server running at port: |
Seluruh baris di atas berada dalam satu berkas (api/server.js:8-181). Dependensi runtime yang dipakai hanya cors ^2.8.5, dotenv ^16.0.1, dan express ^4.17.1 (package.json:15-192). Pada deployment Vercel, api/server.js dibangun sebagai fungsi @vercel/node yang menerima semua path (vercel.json:1-113), sehingga urutan lapis yang sama berlaku di lokal maupun di deployment.
Header Cache-Control di level Router
router.use pertama di api/routes.js berjalan untuk setiap request yang mencapai Router, sebelum route mana pun dicocokkan (api/routes.js:9-124). Nilainya ditulis literal sebagai satu string (api/routes.js:105):
public, max-age=0, s-maxage=86400, stale-while-revalidate
| Directive | Nilai | Arti |
|---|---|---|
public | tanpa nilai | respons boleh disimpan cache bersama |
max-age | 0 | cache klien wajib revalidasi setiap kali |
s-maxage | 86400 | cache bersama boleh menyimpan hingga 24 jam |
stale-while-revalidate | tanpa nilai | kode tidak menyebut durasinya, jadi jendela revalidasi bergantung pada cache penerima |
Karena dipasang lewat router.use, header ini juga menempel pada respons index / dan pada respons fallback 404, padahal keduanya tidak memakai caching (api/routes.js:14-326, api/routes.js:40-457).
Middleware caching: key, hit, dan miss
api/middlewares.js hanya berisi satu objek const cache = {} di scope modul dan satu fungsi caching yang diekspor sebagai { caching } (api/middlewares.js:18, api/middlewares.js:39, api/middlewares.js:19-2110). Objek itu hidup selama proses Node berjalan dan dipakai bersama oleh semua request.
sequenceDiagram
autonumber
participant R as Router routes.js
participant M as caching
participant K as cache in-memory
participant H as Handler
R->>M: GET /surah/:surah dengan caching
M->>M: key = req.url
alt cache[key] terisi
M->>K: baca cache[key]
K-->>M: body respons pertama
M-->>R: res.status(200).send(cache[key])
else cache[key] kosong
M->>M: res.sendResponse = res.send
M->>M: res.send diarahkan ke cache[key] = body
M->>H: next()
H-->>M: res.status(200).send(body)
M->>K: cache[key] = body
end%% fig: Siklus hit dan miss di middleware caching %% desc: Pada hit, body yang tersimpan dikirim ulang dengan status 200 tanpa memanggil handler; pada miss, res.send diganti lebih dulu sehingga body handler ikut masuk ke cache sebelum next().
Perhatikan dua hal pada diagram di atas. Pertama, jalur hit mengembalikan res.status(200).send(cache[key]) dan berhenti di situ, sehingga handler tidak pernah dipanggil dan status asli respons pertama tidak dipakai lagi (api/middlewares.js:6-811). Kedua, jalur miss menyimpan referensi res.send yang asli ke res.sendResponse, mengganti res.send dengan fungsi yang menulis cache[key] = body lalu memanggil res.sendResponse(body), baru kemudian memanggil next() (api/middlewares.js:10-1412).
| Tahap | Ekspresi | Catatan |
|---|---|---|
| key | const key = req.url | memuat path sekaligus query string (api/middlewares.js:413) |
| hit | res.status(200).send(cache[key]) | status ditulis tetap 200 (api/middlewares.js:6-811) |
| miss | res.sendResponse = res.send lalu res.send = (body) => ... | patch berlaku pada objek response request itu saja (api/middlewares.js:10-1412) |
| simpan | cache[key] = body | body disimpan sebagai objek apa adanya, bukan string JSON (api/middlewares.js:11-1314) |
| lanjut | next() | handler baru dijalankan setelah patch terpasang (api/middlewares.js:1615) |
| invalidasi | tidak ada | tidak ada TTL, penghapusan, atau pembatasan ukuran (api/middlewares.js:18) |
Batas dan jebakan
⚠️ Respons 404 dari handler ikut tersimpan, lalu dikirim ulang dengan status
200. Handler mengirim 404 lewatres.sendyang sudah dipatch (api/handlers/surah.js:30-3516, api/handlers/juz.js:9-1517), sehingga bodySurah "..." is not found.masuk kecache[key]sebagai body biasa (api/middlewares.js:11-1314). Request kedua ke path yang sama menerima body tersebut dengan status200.
⚠️
cachetidak pernah dibersihkan. Tidak ada TTL, tidak ada penghapusan entri, dan tidak ada batas jumlah entri di berkas middleware (api/middlewares.js:18), jadi objek itu tumbuh selama proses hidup dan hilang seluruhnya saat proses berhenti.
⚠️ Key memakai
req.urlmentah (api/middlewares.js:413). Query string ikut menjadi bagian key, padahal tidak satu pun handler membaca query, sehingga/surah/18dan/surah/18?x=1menempati dua entri berbeda dengan body yang identik.
Dua masa hidup yang berbeda perlu dibedakan: cache in-memory berakhir bersama prosesnya, sedangkan header s-maxage=86400 memberi izin cache bersama menyimpan respons hingga 24 jam (api/routes.js:105). Tidak ada kode di repository yang membersihkan salah satunya, sehingga perubahan isi data/quran.json tidak langsung terlihat oleh klien.
Route yang tidak melewati caching
| Route | Middleware yang dipasang | Masuk cache? |
|---|---|---|
GET / | handler inline yang mengirim daftar endpoint | tidak (api/routes.js:14-326) |
GET /surah | caching, SurahHandler.getAllSurah | ya (api/routes.js:34-3818) |
GET /surah/:surah | caching, SurahHandler.getSurah | ya (api/routes.js:34-3818) |
GET /surah/:surah/:ayah | caching, SurahHandler.getAyahFromSurah | ya (api/routes.js:34-3818) |
GET /juz/:juz | caching, JuzHandler.getJuz | ya (api/routes.js:34-3818) |
ALL * | handler fallback Resource "..." is not found. | tidak (api/routes.js:40-457) |
Karena keempat route yang di-cache didaftarkan dengan router.get, semua metode lain — misalnya POST /surah — jatuh ke router.all('*') dan menerima 404 tanpa pernah menyentuh cache (api/routes.js:34-3818, api/routes.js:40-457).