Membuat HTTP Server dengan JavaScript Tanpa Framework
Halo, Gabuters!
Pada artikel sebelumnya, kita sudah belajar HTTP, JSON, dan REST API.
Kita mengenal request, response, HTTP method, status code, dan endpoint.
Namun, semua contoh sebelumnya masih berupa gambaran bagaimana komunikasi HTTP bekerja.
Sekarang kita akan membuat server yang benar-benar bisa menerima request dari browser.
Menariknya, kita tidak membutuhkan Express atau framework backend lainnya.
Kita cukup menggunakan JavaScript, Node.js, dan modul bawaan node:http.
Pada akhir artikel ini, kita akan mempunyai HTTP server sederhana yang bisa:
- Menampilkan pesan sambutan.
- Mengembalikan data JSON.
- Membaca path dan HTTP method.
- Menangani endpoint yang tidak ditemukan.
- Menampilkan waktu server.
- Menangani error sederhana tanpa langsung menghentikan proses.
Mari kita mulai!
1. Mengapa JavaScript Bisa Digunakan untuk Backend?
Pada seri JavaScript sebelumnya, kita menjalankan kode melalui browser.
Contohnya:
document.querySelector("button");Kode tersebut menggunakan DOM API yang disediakan browser.
Namun, JavaScript tidak hanya bisa dijalankan di browser.
Dengan runtime seperti Node.js, kita bisa menjalankan JavaScript langsung pada komputer atau server.
Node.js menyediakan berbagai modul bawaan, termasuk kemampuan untuk:
- Membaca dan menulis file.
- Menjalankan proses.
- Berkomunikasi melalui jaringan.
- Membuat HTTP server.
Artinya, kita dapat membuat backend menggunakan bahasa JavaScript yang sudah dipelajari sebelumnya.
Perbedaannya terletak pada lingkungan tempat kode dijalankan.
2. Apa Itu Node.js?
Node.js adalah runtime JavaScript yang memungkinkan kode JavaScript dijalankan di luar browser.
Salah satu penggunaannya adalah membangun aplikasi server.
Node.js bukan framework dan bukan bahasa pemrograman baru.
Kita tetap menulis JavaScript.
Sebagai contoh, kode berikut dapat dijalankan menggunakan Node.js:
const nama = "BapakGabut";
console.log(`Halo, ${nama}!`);Simpan sebagai:
halo.jsLalu jalankan melalui terminal:
node halo.jsHasilnya:
Halo, BapakGabut!Tidak diperlukan browser untuk menjalankan kode tersebut.
3. Menginstal Node.js
Sebelum membuat server, kita perlu memastikan Node.js sudah tersedia di komputer.
Kunjungi website resmi:
Unduh versi LTS (Long-Term Support) yang tersedia.
Versi LTS umumnya menjadi pilihan yang cocok untuk pembelajaran dan penggunaan yang mengutamakan stabilitas.
Ikuti proses instalasi sesuai sistem operasi yang digunakan.
Setelah selesai, buka terminal.
Pada Windows, kita bisa menggunakan PowerShell atau Command Prompt.
Pada macOS atau Linux, gunakan aplikasi Terminal.
Jalankan:
node --versionJika Node.js terpasang dengan benar, terminal akan menampilkan nomor versi.
Contohnya:
vXX.YY.ZZNomor versi yang muncul dapat berbeda tergantung versi yang diinstal.
Kita juga bisa memeriksa npm:
npm --versionnpm merupakan package manager yang biasanya disertakan dalam instalasi Node.js.
Namun, untuk artikel ini, kita tidak perlu memasang package tambahan.
Jika perintah node tidak dikenali, periksa apakah instalasi berhasil dan apakah lokasi Node.js sudah tersedia pada PATH sistem.
Setelah melakukan instalasi, terkadang kita perlu menutup dan membuka kembali terminal.
4. Menyiapkan Folder Proyek
Buat folder baru:
belajar-http-server/Di dalamnya, buat file:
server.jsStruktur proyek kita sangat sederhana:
belajar-http-server/└── server.jsKita belum membutuhkan package.json, folder node_modules, atau konfigurasi framework.
Semua contoh menggunakan modul bawaan Node.js.
Buka folder tersebut menggunakan editor kode pilihanmu.
5. Mengenal Modul node
Node.js menyediakan modul bawaan bernama node:http.
Modul ini memungkinkan kita membuat HTTP server.
Untuk menggunakannya, tulis:
const http = require("node:http");Kita menggunakan require() untuk memuat modul dengan sistem CommonJS.
Awalan node: menunjukkan bahwa modul tersebut merupakan modul bawaan Node.js.
Karena sudah tersedia, kita tidak perlu menjalankan:
npm install httpKita langsung bisa menggunakannya.
6. Membuat HTTP Server Pertama
Sekarang buka file server.js.
Masukkan kode berikut:
const http = require("node:http");
const server = http.createServer((req, res) => { res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.end("Halo, Gabuters! Ini server JavaScript pertama kita.");});
server.listen(3000, () => { console.log("Server berjalan di http://localhost:3000");});Simpan file tersebut.
Kemudian buka terminal pada folder proyek.
Jalankan:
node server.jsJika berhasil, terminal akan menampilkan:
Server berjalan di http://localhost:3000Sekarang buka browser dan kunjungi:
http://localhost:3000Kita akan melihat:
Halo, Gabuters! Ini server JavaScript pertama kita.Selamat!
Kita baru saja membuat HTTP server menggunakan JavaScript tanpa framework.
7. Memahami Kode HTTP Server
Mari kita bedah bagian-bagiannya.
Mengimpor modul http
const http = require("node:http");Baris ini memuat modul HTTP bawaan Node.js.
Membuat server
const server = http.createServer((req, res) => { // Menangani request});Method createServer() membuat objek HTTP server.
Callback yang diberikan akan dijalankan ketika server menerima request.
Callback tersebut menerima dua parameter utama:
req: objek request.res: objek response.
Mengirim status dan header
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8"});Baris tersebut menetapkan status code 200 dan header Content-Type.
Nilai text/plain menunjukkan bahwa respons berupa teks biasa.
Bagian charset=utf-8 menunjukkan encoding karakter yang digunakan.
Mengirim body dan mengakhiri response
res.end("Halo, Gabuters!");Method res.end() mengakhiri response.
Kita juga bisa memberikan data yang ingin dikirim sebagai body.
Untuk server sederhana, res.end() sering cukup untuk mengirim respons.
Menjalankan server pada port tertentu
server.listen(3000, () => { console.log("Server berjalan di http://localhost:3000");});Baris tersebut meminta server mulai mendengarkan koneksi pada port 3000.
Callback dijalankan ketika server berhasil mulai mendengarkan.
8. Apa Itu localhost dan Port?
Ketika membuka:
http://localhost:3000Ada dua bagian yang perlu dipahami.
localhost
localhost merupakan nama host yang biasanya mengarah ke alamat loopback komputer sendiri.
Dengan demikian, kita dapat mengakses layanan yang berjalan pada komputer yang sama.
Port
Port membantu sistem membedakan layanan jaringan pada sebuah host.
Dalam contoh ini, server menggunakan port:
3000Kita dapat memilih port lain, misalnya:
server.listen(4000);Kemudian mengakses:
http://localhost:4000Namun, port yang dipilih harus tersedia.
Jika sudah digunakan proses lain, server dapat gagal berjalan dengan error seperti EADDRINUSE.
Perlu diketahui juga bahwa pada contoh server.listen(3000), alamat antarmuka jaringan yang digunakan bergantung pada perilaku Node.js dan sistem operasi.
Untuk latihan yang secara eksplisit hanya ingin menerima koneksi loopback, kita bisa menggunakan:
server.listen(3000, "127.0.0.1");Pada contoh-contoh berikutnya, kita akan memakai alamat loopback tersebut agar server latihan tidak sengaja terbuka ke jaringan lain.
9. Mengenal req dan res
Dua objek yang paling penting dalam HTTP server Node.js adalah req dan res.
req: Request dari client
Objek req menyediakan informasi mengenai request yang diterima.
Contohnya:
console.log(req.method);console.log(req.url);req.method menunjukkan HTTP method.
req.url berisi target request sebagaimana diterima server, biasanya berupa path beserta query string.
res: Response untuk client
Objek res digunakan untuk mengirim response.
Contohnya:
res.statusCode = 200;res.setHeader("Content-Type", "text/plain; charset=utf-8");res.end("Request berhasil diproses.");Jadi, secara sederhana:
req = informasi yang masukres = jawaban yang dikirimKita akan menggunakan keduanya hampir di setiap contoh backend berikutnya.
10. Membaca URL yang Diminta
Sekarang kita buat server yang menampilkan path request.
Ganti isi server.js dengan:
const http = require("node:http");
const server = http.createServer((req, res) => { console.log("Method:", req.method); console.log("URL:", req.url);
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.end(`Kamu mengakses: ${req.url}`);});
server.listen(3000, "127.0.0.1", () => { console.log("Server berjalan di http://localhost:3000");});Jalankan kembali server:
node server.jsBuka:
http://localhost:3000/profilBrowser akan menampilkan:
Kamu mengakses: /profilTerminal juga akan mencatat method dan URL request.
Contohnya:
Method: GETURL: /profilCoba akses:
http://localhost:3000/artikelHasilnya akan berubah menjadi:
Kamu mengakses: /artikelDari sini, kita mulai melihat bagaimana server membedakan alamat yang diminta client.
11. Membuat Routing Sederhana
Routing adalah proses menentukan tindakan berdasarkan request yang diterima.
Untuk latihan pertama, kita dapat menggunakan percabangan if.
Ganti isi server.js dengan:
const http = require("node:http");
const server = http.createServer((req, res) => { res.setHeader("Content-Type", "text/plain; charset=utf-8");
if (req.method === "GET" && req.url === "/") { res.statusCode = 200; res.end("Selamat datang di BapakGabut!"); return; }
if (req.method === "GET" && req.url === "/profil") { res.statusCode = 200; res.end("Ini adalah halaman profil BapakGabut."); return; }
if (req.method === "GET" && req.url === "/tentang") { res.statusCode = 200; res.end("Kita sedang belajar backend JavaScript."); return; }
res.statusCode = 404; res.end("Halaman tidak ditemukan.");});
server.listen(3000, "127.0.0.1", () => { console.log("Server berjalan di http://localhost:3000");});Sekarang kita mempunyai tiga endpoint:
| Method | Endpoint | Response |
|---|---|---|
| GET | / | Pesan sambutan |
| GET | /profil | Informasi profil |
| GET | /tentang | Informasi pembelajaran |
Jika kita membuka:
http://localhost:3000/profilServer mengirim:
Ini adalah halaman profil BapakGabut.Jika kita membuka:
http://localhost:3000/tidak-adaServer mengirim:
Halaman tidak ditemukan.Dengan status:
404 Not FoundMengapa menggunakan return?
Perhatikan:
res.end("Selamat datang di BapakGabut!");return;Setelah response dikirim, return menghentikan eksekusi callback untuk request tersebut.
Ini membantu memastikan server tidak mencoba mengirim response kedua.
Tanpa alur kontrol yang benar, kita dapat mengalami error ketika mencoba mengubah header atau mengirim response setelah response sebelumnya selesai.
12. Mengembalikan Data JSON
Sejauh ini, server kita hanya mengirim teks biasa.
Padahal, API sering mengembalikan JSON.
Mari kita coba.
Ganti isi server.js dengan:
const http = require("node:http");
const server = http.createServer((req, res) => { if (req.method === "GET" && req.url === "/api/profil") { const profil = { nama: "BapakGabut", tagline: "Ngoding, main game, dan ngulik apa saja.", minat: ["JavaScript", "Web Development", "Gaming"] };
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
res.end(JSON.stringify(profil)); return; }
res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
res.end(JSON.stringify({ pesan: "Endpoint tidak ditemukan." }));});
server.listen(3000, "127.0.0.1", () => { console.log("Server berjalan di http://localhost:3000");});Jalankan server, kemudian buka:
http://localhost:3000/api/profilKita akan menerima data JSON:
{ "nama": "BapakGabut", "tagline": "Ngoding, main game, dan ngulik apa saja.", "minat": [ "JavaScript", "Web Development", "Gaming" ]}Tampilan JSON di browser dapat berbeda.
Ada browser yang menampilkannya sebagai teks, ada pula yang menyediakan tampilan JSON terformat.
Namun, data yang dikirim server tetap berupa JSON.
Mengapa menggunakan JSON.stringify()?
Karena profil merupakan object JavaScript.
Sedangkan body HTTP response perlu dikirim dalam bentuk data yang dapat ditransmisikan.
Kita menggunakan:
JSON.stringify(profil)Untuk menghasilkan string JSON.
Kemudian string tersebut dikirim menggunakan:
res.end();Dengan isi JSON sebagai argumennya.
13. Membuat Endpoint Daftar Artikel
Sekarang kita buat contoh yang lebih dekat dengan website BapakGabut.
Kita ingin menyediakan endpoint:
GET /api/artikelEndpoint tersebut akan mengembalikan daftar artikel.
Contohnya:
const artikel = [ { id: 1, judul: "Belajar HTML", kategori: "HTML" }, { id: 2, judul: "Belajar CSS", kategori: "CSS" }, { id: 3, judul: "Belajar JavaScript", kategori: "JavaScript" }];Data tersebut masih ditulis langsung di dalam program.
Kita belum menggunakan database.
Untuk membuat response JSON, kita dapat menulis:
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8"});
res.end(JSON.stringify(artikel));Pada bagian berikutnya, kita akan menggabungkan endpoint artikel dengan beberapa endpoint lain.
14. Membuat Endpoint Waktu Server
Selain data artikel, kita juga dapat membuat endpoint yang mengembalikan waktu saat request diproses.
Contohnya:
const waktu = new Date().toISOString();Method toISOString() menghasilkan representasi tanggal dan waktu dalam format ISO 8601 menggunakan UTC.
Misalnya:
2026-10-09T12:30:00.000ZNilai tersebut hanya contoh.
Waktu sebenarnya bergantung pada saat request diterima.
Kita dapat mengirim data:
{ waktuServer: new Date().toISOString()}Dengan begitu, setiap request dapat menghasilkan nilai waktu yang baru.
15. Menggabungkan Semua Endpoint
Sekarang kita buat versi akhir server pada artikel ini.
Ganti seluruh isi server.js dengan kode berikut:
const http = require("node:http");
const HOST = "127.0.0.1";const PORT = 3000;
const artikel = [ { id: 1, judul: "Belajar HTML", kategori: "HTML" }, { id: 2, judul: "Belajar CSS", kategori: "CSS" }, { id: 3, judul: "Belajar JavaScript", kategori: "JavaScript" }];
function kirimJSON(res, statusCode, data) { res.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8" });
res.end(JSON.stringify(data));}
const server = http.createServer((req, res) => { try { const url = new URL(req.url, `http://${req.headers.host || "localhost"}`); const pathname = url.pathname;
console.log(`${req.method} ${pathname}`);
if (req.method === "GET" && pathname === "/") { kirimJSON(res, 200, { pesan: "Selamat datang di API BapakGabut!" }); return; }
if (req.method === "GET" && pathname === "/api/profil") { kirimJSON(res, 200, { nama: "BapakGabut", tagline: "Ngoding, main game, dan ngulik apa saja.", minat: ["JavaScript", "Web Development", "Gaming"] }); return; }
if (req.method === "GET" && pathname === "/api/artikel") { kirimJSON(res, 200, artikel); return; }
if (req.method === "GET" && pathname === "/api/waktu") { kirimJSON(res, 200, { waktuServer: new Date().toISOString() }); return; }
kirimJSON(res, 404, { pesan: "Endpoint tidak ditemukan." }); } catch (error) { console.error("Terjadi error:", error);
if (!res.headersSent) { kirimJSON(res, 500, { pesan: "Terjadi kesalahan pada server." }); } else { res.destroy(); } }});
server.on("error", (error) => { console.error("Server gagal berjalan:", error.message);});
server.listen(PORT, HOST, () => { console.log(`Server berjalan di http://localhost:${PORT}`);});Kode ini merupakan HTTP server sederhana yang mempunyai beberapa endpoint.
Kita juga membuat function kirimJSON() agar tidak perlu menulis header dan JSON.stringify() berulang kali.
Mengapa menggunakan new URL()?
Pada contoh sebelumnya, kita membandingkan req.url secara langsung.
Namun, req.url dapat berisi path beserta query string.
Contohnya:
/api/artikel?kategori=HTMLJika dibandingkan langsung dengan:
/api/artikelKeduanya tidak sama.
Dengan:
const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);const pathname = url.pathname;Kita dapat mengambil bagian pathname saja.
Untuk server latihan lokal, pendekatan ini cukup membantu memahami parsing URL.
Pada aplikasi produksi, nilai yang berasal dari header request tetap harus diperlakukan sebagai input yang tidak tepercaya.
Karena itu, jangan menggunakan nilai Host untuk keputusan keamanan tanpa validasi.
Mengapa menggunakan try…catch?
Blok try...catch membantu menangani exception sinkron yang terjadi ketika callback memproses request.
Namun, try...catch tersebut tidak otomatis menangkap seluruh error asynchronous.
Jika nanti kita menggunakan Promise atau operasi asynchronous, kita perlu menerapkan penanganan error yang sesuai.
Kode ini merupakan latihan dasar, bukan template server produksi yang sudah mencakup seluruh kebutuhan keamanan dan keandalan.
16. Mencoba Semua Endpoint
Jalankan:
node server.jsKemudian coba alamat berikut.
| URL | Hasil |
|---|---|
http://localhost:3000/ | Pesan sambutan |
http://localhost:3000/api/profil | Data profil |
http://localhost:3000/api/artikel | Daftar artikel |
http://localhost:3000/api/waktu | Waktu server |
http://localhost:3000/tidak-ada | Response 404 |
Karena seluruh endpoint menggunakan method GET, kita bisa mencobanya langsung melalui address bar browser.
Untuk memeriksa status code dan header, gunakan tab Network pada Developer Tools.
17. Menguji Server dengan curl
Selain browser, kita juga bisa menggunakan curl untuk mengirim HTTP request.
Jika curl tersedia, jalankan:
curl http://localhost:3000/api/artikelKita akan menerima JSON berisi daftar artikel.
Untuk melihat header response, gunakan:
curl -i http://localhost:3000/api/artikelOpsi -i menampilkan header response bersama body.
Kita juga bisa mencoba endpoint yang tidak ditemukan:
curl -i http://localhost:3000/tidak-adaRespons akan mempunyai status 404.
Pada Windows PowerShell, jika perintah curl mempunyai perilaku berbeda karena alias atau konfigurasi shell, kita dapat mencoba curl.exe.
Contohnya:
curl.exe -i http://localhost:3000/api/artikelUntuk latihan ini, kita belum perlu menggunakan tool API khusus.
Browser dan terminal sudah cukup.
18. Menghentikan dan Menjalankan Ulang Server
Ketika menjalankan:
node server.jsProses Node.js akan tetap aktif untuk menerima request.
Jika kita mengubah kode pada file, proses yang sedang berjalan biasanya tidak otomatis memuat perubahan tersebut.
Untuk menghentikannya, tekan:
Ctrl + CKemudian jalankan kembali:
node server.jsPada versi Node.js modern, terdapat juga fitur watch mode.
Contohnya:
node --watch server.jsMode ini dapat memulai ulang proses ketika file yang dipantau berubah.
Namun, untuk memahami prosesnya, kita bisa menggunakan cara manual terlebih dahulu.
19. Kesalahan yang Sering Terjadi
Error: node is not recognized
Jika terminal tidak mengenali perintah node, periksa instalasi Node.js.
Pastikan Node.js tersedia pada PATH dan buka ulang terminal setelah instalasi.
Error: Cannot find module
Jika muncul error bahwa file tidak ditemukan, periksa lokasi terminal dan nama file.
Pastikan kita menjalankan:
node server.jsDari folder yang berisi file tersebut.
Error: EADDRINUSE
Error ini biasanya menunjukkan alamat atau port yang ingin digunakan sudah dipakai proses lain.
Misalnya, ada server sebelumnya yang masih berjalan pada port 3000.
Hentikan proses tersebut atau gunakan port lain.
Browser terus memuat halaman
Periksa apakah seluruh jalur penanganan request akhirnya memanggil:
res.end();Jika response tidak diakhiri, client dapat terus menunggu.
Error: Cannot set headers after they are sent
Error ini dapat terjadi jika program mencoba mengatur header setelah response mulai dikirim.
Pastikan alur program tidak mengirim lebih dari satu response untuk request yang sama.
Penggunaan return setelah mengirim response membantu menghindari masalah tersebut.
Perubahan kode tidak terlihat
Jika kita menjalankan Node.js tanpa watch mode, hentikan dan jalankan kembali server setelah mengubah file.
Endpoint selalu menghasilkan 404
Periksa HTTP method dan pathname.
Contohnya, endpoint:
/api/artikelBerbeda dari:
/api/artikel/Jika routing kita membandingkan string secara langsung, perbedaan garis miring di akhir dapat memengaruhi hasil.
20. Latihan Mandiri
Sekarang giliran Gabuters mencoba sendiri.
Latihan 1: Endpoint kategori
Tambahkan endpoint:
GET /api/kategoriYang mengembalikan:
[ "HTML", "CSS", "JavaScript", "Backend"]Latihan 2: Endpoint informasi website
Buat endpoint:
GET /api/websiteYang mengembalikan:
{ "nama": "BapakGabut", "tagline": "Ngoding, main game, dan ngulik apa saja.", "aktif": true}Latihan 3: Mengubah port
Ubah server agar berjalan pada port:
4000Kemudian akses endpoint menggunakan alamat yang sesuai.
Latihan 4: Membaca query parameter
Coba akses:
http://localhost:3000/api/artikel?kategori=HTMLTambahkan kode untuk membaca query parameter:
const kategori = url.searchParams.get("kategori");Untuk latihan ini, cukup tampilkan nilai kategori pada terminal.
Belum perlu membuat filter data.
Latihan 5: Membuat endpoint health check
Tambahkan endpoint:
GET /healthYang mengembalikan:
{ "status": "ok"}Endpoint semacam ini dapat digunakan sebagai pemeriksaan dasar apakah proses server merespons request.
Namun, respons sederhana tersebut belum membuktikan bahwa seluruh dependency atau fitur aplikasi bekerja dengan baik.
21. Apa yang Sudah Kita Pelajari?
Pada artikel ini, kita berhasil membuat HTTP server menggunakan JavaScript tanpa framework.
Kita sudah mempraktikkan:
- Menjalankan JavaScript menggunakan Node.js.
- Menggunakan modul bawaan
node:http. - Membuat server dengan
http.createServer(). - Mengenal objek
reqdanres. - Menjalankan server menggunakan
listen(). - Membuat routing sederhana.
- Mengirim response teks dan JSON.
- Menggunakan status code
200,404, dan500. - Membaca pathname menggunakan
URL. - Menguji endpoint melalui browser dan
curl.
Ini merupakan fondasi penting untuk mempelajari backend.
Kita sekarang sudah memahami bahwa API bukan sekadar alamat URL.
Di balik sebuah endpoint, terdapat program yang menerima request, menentukan tindakan, lalu mengirim response.
22. Penutup
Selamat, Gabuters!
Kita sudah membuat HTTP server pertama menggunakan JavaScript dan Node.js native.
Tanpa Express.
Tanpa framework.
Tanpa dependency tambahan.
Walaupun masih sederhana, server kita sudah dapat menerima request dan mengembalikan JSON melalui beberapa endpoint.
Namun, ada satu hal yang belum kita lakukan.
Sejauh ini, semua endpoint hanya mengambil data menggunakan method GET.
Bagaimana jika kita ingin menambahkan artikel baru?
Bagaimana cara membaca body JSON dari request POST?
Bagaimana cara memperbarui atau menghapus data?
Pada artikel berikutnya, kita akan membuat REST API sederhana menggunakan JavaScript murni.
Kita akan mempraktikkan operasi CRUD menggunakan GET, POST, PUT, dan DELETE dengan penyimpanan sementara di memori.
Dengan begitu, kita mulai membangun backend yang dapat mengelola data, bukan hanya menampilkannya.
Sampai jumpa di artikel berikutnya, Gabuters!
Bagikan Artikel
Buat poster berbagi atau salin tautan artikel ini.
Lanjut membaca
Coba jalur lain
Pilihan konsisten dari artikel lain
Terakhir diperbarui pada , 21 hari yang lalu
Sebagian konten mungkin sudah tidak berlaku