Cara Membuat Server Web di Node.js dengan Modul HTTP


Pendahuluan

Saat Anda melihat halaman web di peramban Anda, Anda sedang membuat permintaan ke komputer lain di internet, yang kemudian memberikan halaman web tersebut sebagai tanggapan. Komputer yang Anda ajak berkomunikasi melalui internet disebut server web. Server web menerima permintaan HTTP dari klien, seperti peramban Anda, dan memberikan tanggapan HTTP, seperti halaman HTML atau JSON dari API.

Modul Node.js http menyediakan segala yang Anda butuhkan untuk membangun server web tanpa dependensi eksternal. Modul ini menangani permintaan yang masuk, menguraikan header HTTP, mengelola koneksi, dan mengirimkan respons—semuanya melalui API JavaScript yang sederhana.

Modul http adalah solusi bawaan Node.js untuk membuat server dan klien HTTP. Berbeda dengan framework seperti Express.js, modul ini merupakan bagian dari inti Node.js, artinya tidak diperlukan instalasi atau dependensi. Hal ini membuatnya ideal untuk aplikasi ringan, mempelajari dasar-dasar HTTP, atau ketika Anda ingin kontrol penuh atas penanganan permintaan.

Banyak perangkat lunak yang terlibat agar sebuah server dapat mengembalikan halaman web. Perangkat lunak ini umumnya terbagi menjadi dua kategori: frontend dan backend. Kode front-end berkaitan dengan bagaimana konten disajikan, seperti warna bilah navigasi dan gaya teks. Kode back-end berkaitan dengan bagaimana data dipertukarkan, diproses, dan disimpan. Kode yang menangani permintaan jaringan dari browser Anda atau berkomunikasi dengan basis data terutama dikelola oleh kode back-end.

Node.js memungkinkan pengembang menggunakan JavaScript untuk menulis kode back-end, meskipun secara tradisional JavaScript digunakan di browser untuk menulis kode front-end. Memiliki front-end dan back-end bersama-sama seperti ini mengurangi usaha yang dibutuhkan untuk membuat server web, yang merupakan salah satu alasan utama mengapa Node.js menjadi pilihan populer untuk menulis kode back-end.

Tutorial ini menunjukkan cara membangun server web siap produksi menggunakan modul http yang termasuk dalam Node.js. Anda akan membangun server yang menangani routing, mengurai body request, menyajikan file statis, menerapkan penanganan error yang tepat, dan bekerja dengan sintaks CommonJS maupun ES Module.

Mempermudah penyebaran aplikasi Node.js dengan Platform Aplikasi Linux-Console.net. Sebarkan Node langsung dari GitHub dalam hitungan menit.

Poin Penting

Sebelum memulai, berikut adalah hal-hal yang akan Anda capai:

  • Membuat server HTTP menggunakan modul inti http Node.js tanpa framework:
    Anda akan belajar cara membuat server web HTTP yang sepenuhnya fungsional hanya dengan modul inti http Node.js. Ini berarti menghindari ketergantungan eksternal atau framework, memberikan pemahaman yang lebih dalam tentang bagaimana server sebenarnya bekerja pada tingkat rendah, serta memungkinkan implementasi yang lebih ringan dan fleksibel untuk kebutuhan Anda.

  • Menangani berbagai jenis konten (JSON, HTML, CSV, file statis) dengan header yang tepat:
    Anda akan mempelajari cara bekerja dengan berbagai jenis konten, merespons dengan HTML untuk halaman web, JSON untuk API, file CSV, atau menyajikan aset statis seperti gambar dan skrip. Anda akan memahami cara mengatur header HTTP seperti Content-Type dengan benar, memastikan bahwa browser dan klien menafsirkan respons Anda dengan benar untuk pengalaman pengguna yang lancar.

  • Implementasikan routing menggunakan parsing URL permintaan dan pernyataan switch:
    Routing adalah hal mendasar bagi server web, memungkinkan Anda melayani logika berbeda untuk URL dan endpoint yang berbeda. Anda akan melihat teknik untuk parsing URL permintaan yang masuk dan menyusun logika server Anda (misalnya, dengan pernyataan switch) untuk memberikan respons kustom berdasarkan jalur dan metode HTTP, meniru dasar-dasar apa yang dilakukan kerangka kerja besar di balik layar.

  • Proses permintaan POST dengan memparsing isi permintaan dan menangani berbagai jenis konten:
    Menangani data yang dikirim pengguna adalah kunci untuk aplikasi interaktif apa pun. Anda akan belajar cara menangani permintaan POST, termasuk membaca dan memparsing isi permintaan sebagai data mentah, JSON, atau format lainnya. Bagian ini mencakup buffering data yang masuk, memparsingnya dengan aman, dan merespons sesuai dengan payload yang diterima, memberikan dasar untuk interaksi pengguna atau pengembangan API.

  • Sajikan file statis secara efisien dari sistem berkas:
    Banyak server web perlu menyajikan aset statis—gambar, stylesheet, file JavaScript—langsung kepada pengguna. Anda akan mempelajari cara membaca file dari disk dan mengirimnya secara efisien melalui HTTP, mengelola jalur file dengan bijak untuk menghindari masalah keamanan, serta menjelajahi optimisasi seperti streaming file untuk meningkatkan kinerja pada file yang lebih besar.

  • Implementasikan penanganan kesalahan dengan kode status HTTP yang sesuai dan pencatatan:
    Server yang tangguh harus menangani kesalahan dengan baik. Anda akan melihat cara mendeteksi, mencatat, dan menanggapi kesalahan—seperti permintaan yang salah atau file yang hilang—menggunakan kode status HTTP standar seperti 400, 404, dan 500. Anda juga akan mempelajari praktik pencatatan dasar agar masalah dapat dilacak dan pengguna menerima pesan kesalahan yang informatif tanpa membocorkan informasi sensitif.

  • Gunakan JavaScript modern dengan sintaks CommonJS (require) dan ES Modules (import):
    Node.js mendukung dua sistem modul: CommonJS dan ES Modules. Anda akan berlatih menggunakan kedua pernyataan require() dan import, memahami kapan—dan bagaimana—menggunakan masing-masing gaya. Ini tidak hanya memastikan kompatibilitas di berbagai proyek Node.js, tetapi juga membantu Anda menulis kode JavaScript modern yang bersih dan mudah dipelihara.

  • Terapkan praktik terbaik produksi termasuk caching file, pemulihan kesalahan, dan pertimbangan keamanan:
    Terakhir, Anda akan membahas praktik terbaik yang penting untuk menerapkan server web Node.js di lingkungan nyata. Ini termasuk memanfaatkan caching file untuk kinerja, menerapkan strategi pemulihan kesalahan agar server Anda tetap tangguh, serta menerapkan langkah-langkah keamanan dasar (seperti sanitasi input dan header HTTP yang tepat) untuk melindungi aplikasi dan pengguna Anda.

Memahami Modul HTTP

Modul http adalah antarmuka tingkat rendah Node.js untuk komunikasi HTTP. Modul ini menyediakan:

  • Pembuatan server: http.createServer() membuat sebuah instance server HTTP
  • Penanganan permintaan: Permintaan yang masuk direpresentasikan oleh objek IncomingMessage
  • Manajemen respons: Respons keluar menggunakan objek ServerResponse
  • Dukungan streaming: Baik permintaan maupun tanggapan menggunakan aliran data, memungkinkan transfer data yang efisien

Mengapa Menggunakan Modul HTTP Daripada Express? Sementara Express.js menyediakan abstraksi yang praktis, modul http bawaan memberi Anda kontrol penuh, ukuran bundel yang lebih kecil, dan pemahaman mendalam tentang dasar-dasar HTTP. Ini adalah fondasi tempat framework dibangun. Untuk API sederhana, microservices, atau tujuan pembelajaran, ini seringkali sudah cukup.

Modul ini berbasis peristiwa: ketika sebuah permintaan tiba, server memancarkan sebuah peristiwa 'request' dan memanggil fungsi pendengar permintaan Anda. Desain ini memungkinkan Node.js untuk menangani banyak koneksi secara bersamaan dengan efisien pada satu thread melalui loop event-nya.

Konsep inti yang perlu dipahami:

  • I/O non-blocking: Modul http menggunakan event loop di Node.js untuk menangani beberapa koneksi tanpa membuat thread terpisah untuk setiap permintaan. Ini membuatnya sangat efisien untuk operasi yang membutuhkan I/O.
  • Streams: Objek permintaan dan respons mewarisi dari stream Node.js. Ini berarti payload besar dapat diproses secara bertahap tanpa memuat semuanya ke dalam memori—penting untuk menangani unggahan file atau menyajikan file berukuran besar.
  • Header dan kode status: Modul ini memberi Anda kontrol langsung atas header HTTP (res.setHeader()) dan kode status (res.writeHead()), memungkinkan penggunaan semantik HTTP secara tepat.
  • Tidak ada routing otomatis: Berbeda dengan framework, tidak ada routing bawaan. Anda harus secara manual mem-parsing req.url dan req.method untuk menentukan cara merespons—memberikan transparansi penuh tentang bagaimana routing sebenarnya bekerja.

Ketika Anda memanggil http.createServer(), Node.js membuat server TCP di belakang layar yang mendengarkan koneksi masuk, mengurai pesan protokol HTTP, dan memicu event yang bisa ditangani oleh kode Anda. Memahami operasi tingkat rendah ini membantu Anda men-debug masalah dan membuat keputusan arsitektur yang tepat.

Prasyarat

  • Pastikan Node.js terpasang di mesin pengembangan Anda. Tutorial ini bekerja dengan Node.js 20.x atau 22.x LTS (direkomendasikan). Untuk menginstal Node.js di macOS atau Ubuntu, ikuti langkah-langkah dalam Cara Menginstal Node.js dan Membuat Lingkungan Pengembangan Lokal di macOS atau Cara Menginstal Node.js di Ubuntu.

  • Platform Node.js mendukung pembuatan server web secara langsung. Familiaritas dengan dasar-dasar Node.js sangat membantu. Tinjau panduan kami tentang Cara Menulis dan Menjalankan Program Pertama Anda di Node.js.

  • Pemrograman asinkron digunakan di beberapa bagian. Jika Anda baru mengenal kode async atau modul fs, lihat Cara Menulis Kode Asinkron di Node.js.

  • Untuk bagian yang berfokus pada produksi, pemahaman tentang manajemen proses Linux (systemd/PM2) dan reverse proxy (seperti Nginx) akan membantu saat menerapkan panduan deployment.

Langkah 1 — Membuat Server HTTP Dasar

Buat server HTTP minimal yang merespons setiap permintaan dengan teks biasa. Dasar ini berlaku baik jika Anda menggunakan Express atau membangunnya dari awal. Ketika Node.js memanggil http.createServer(), itu membuat server TCP yang mendengarkan pada sebuah port. Setiap koneksi yang masuk memicu fungsi pendengar permintaan Anda.

Siapkan folder proyek Anda:

mkdir first-servers

cd first-servers

Sekarang, buat file yang akan menampung kode tersebut:

touch hello.js

Buka file di editor teks. Kita akan menggunakan nano karena tersedia di terminal:

nano hello.js

Kita mulai dengan memuat modul http yang standar pada semua instalasi Node.js. Tambahkan baris berikut ke hello.js:

const http = require("http");

Modul http berisi fungsi untuk membuat server, yang akan kita bahas nanti. Jika Anda ingin mempelajari lebih lanjut tentang modul di Node.js, lihat artikel Kami Cara Membuat Modul Node.js.

Langkah berikutnya adalah mendefinisikan dua konstanta, yaitu host dan port yang akan digunakan server kita:

...
const host = 'localhost';
const port = 8000;

Seperti yang telah disebutkan sebelumnya, server web menerima permintaan dari browser dan klien lainnya. Kita dapat berinteraksi dengan server web dengan memasukkan nama domain, yang diterjemahkan menjadi alamat IP oleh server DNS. Alamat IP adalah urutan angka unik yang mengidentifikasi sebuah mesin di jaringan, seperti internet. Untuk informasi lebih lanjut tentang konsep nama domain, lihat artikel kami Pengantar Terminologi, Komponen, dan Konsep DNS.

Nilai localhost adalah alamat pribadi khusus yang digunakan komputer untuk merujuk pada dirinya sendiri. Ini biasanya setara dengan alamat IP internal 127.0.0.1 dan hanya tersedia untuk komputer lokal, bukan untuk jaringan lokal yang kita gabung atau untuk internet.

Port adalah angka yang digunakan server sebagai titik akhir atau “pintu” ke alamat IP kita. Dalam contoh kita, kita akan menggunakan port 8000 untuk server web kita. Port 8080 dan 8000 biasanya digunakan sebagai port default dalam pengembangan, dan dalam kebanyakan kasus pengembang akan menggunakannya daripada port lain untuk server HTTP.

Ketika kita mengikat server kita ke host dan port ini, kita akan dapat mengakses server kita dengan mengunjungi http://localhost:8000 di browser lokal.

Mari kita tambahkan sebuah fungsi khusus, yang dalam Node.js kita sebut request listener. Fungsi ini dimaksudkan untuk menangani permintaan HTTP yang masuk dan mengembalikan respons HTTP. Fungsi ini harus memiliki dua argumen, sebuah objek permintaan dan sebuah objek respons. Objek permintaan menangkap semua data dari permintaan HTTP yang masuk. Objek respons digunakan untuk mengembalikan respons HTTP bagi server.

Kami ingin server pertama kami menampilkan pesan ini setiap kali seseorang mengaksesnya: "Server pertamaku!".

Mari kita tambahkan fungsi itu berikutnya:

...

const requestListener = function (req, res) {
  res.writeHead(200);
  res.end("My first server!");
};

Fungsi biasanya diberi nama berdasarkan apa yang dilakukannya. Misalnya, jika kita membuat fungsi pendengar permintaan untuk mengembalikan daftar buku, kemungkinan kita akan menamakannya listBooks(). Karena ini adalah kasus contoh, kita akan menggunakan nama umum requestListener.

Semua fungsi pendengar permintaan di Node.js menerima dua argumen: req dan res (kita bisa menamainya berbeda jika mau). Permintaan HTTP yang dikirim pengguna ditangkap dalam objek Request, yang sesuai dengan argumen pertama, req. Respons HTTP yang kita kembalikan ke pengguna dibentuk dengan berinteraksi dengan objek Response pada argumen kedua, res.

Baris pertama res.writeHead(200); menetapkan kode status HTTP dari respons. Kode status HTTP menunjukkan seberapa baik permintaan HTTP ditangani oleh server. Dalam kasus ini, kode status 200 berarti "OK". Jika Anda tertarik mempelajari berbagai kode HTTP yang dapat dikembalikan oleh server web Anda beserta artinya, panduan kami tentang Cara Menangani Kode Kesalahan HTTP Umum adalah tempat yang bagus untuk memulai.

Baris berikutnya dari fungsi, res.end("Server pertamaku!");, menulis respons HTTP kembali ke klien yang memintanya. Fungsi ini mengembalikan data apa pun yang harus dikembalikan oleh server. Dalam kasus ini, fungsinya mengembalikan data teks.

Akhirnya, sekarang kita bisa membuat server kita dan memanfaatkan request listener kita:

...

const server = http.createServer(requestListener);
server.listen(port, host, () => {
  console.log(`Server is running on http://${host}:${port}`);
});

Simpan dan keluar dari nano dengan menekan CTRL+X.

Di baris pertama, kita membuat objek server baru melalui fungsi createServer() dari modul http. Server ini menerima permintaan HTTP dan meneruskannya ke fungsi requestListener() kita.

Setelah kita membuat server kita, kita harus mengikatnya ke alamat jaringan. Kita melakukan itu dengan metode server.listen(). Metode ini menerima tiga argumen: port, host, dan fungsi callback yang dijalankan saat server mulai mendengarkan.

Semua argumen ini bersifat opsional, tetapi ada baiknya untuk secara eksplisit menyebutkan port dan host yang ingin kita gunakan oleh server web. Saat men-deploy server web ke lingkungan yang berbeda, mengetahui port dan host tempat server berjalan diperlukan untuk mengatur load balancing atau alias DNS.

Fungsi callback mencatat pesan ke konsol kami sehingga kami dapat mengetahui kapan server mulai mendengarkan koneksi.

Berikut adalah file hello.js lengkap dengan komentar di dalamnya yang menjelaskan setiap bagiannya:

// Import the built-in http module - no installation needed
const http = require("http");

// Define where the server will listen
const host = 'localhost';  // Only accessible from this machine
const port = 8000;          // Common development port

// Request listener: handles every incoming HTTP request
const requestListener = function (req, res) {
    // Set HTTP status code to 200 (OK)
    res.writeHead(200);
    
    // Send the response body and close the connection
    res.end("My first server!");
};

// Create the server with our request listener
const server = http.createServer(requestListener);

// Start listening for connections
server.listen(port, host, () => {
    console.log(`Server is running on http://${host}:${port}`);
});

Kode ini membuat server HTTP yang fungsional dalam kurang dari 20 baris. Setiap kali seorang klien terhubung—terlepas dari URL atau metode HTTP—server akan merespons dengan "Server pertamaku!" dan kode status 200.

Catatan: Meskipun requestListener() tidak menggunakan objek req, itu tetap harus menjadi argumen pertama dari fungsi.

Dengan kurang dari lima belas baris kode, kita sekarang memiliki server web. Mari kita lihat jalannya dan mengujinya secara menyeluruh dengan menjalankan program:

node hello.js

Di konsol, kita akan melihat keluaran ini:

Server is running on http://localhost:8000

Perhatikan bahwa prompt menghilang. Ini karena server Node.js adalah proses yang berjalan lama. Server hanya akan berhenti jika mengalami kesalahan yang menyebabkan crash dan keluar, atau jika kita menghentikan proses Node.js yang menjalankan server tersebut.

Di jendela terminal terpisah, kita akan berkomunikasi dengan server menggunakan cURL, sebuah alat CLI untuk mentransfer data ke dan dari jaringan. Masukkan perintah untuk membuat permintaan HTTP GET ke server kita yang sedang berjalan:

curl http://localhost:8000

Ketika kita menekan ENTER, terminal kita akan menampilkan output berikut:

My first server!

Kami sekarang telah menyiapkan server dan mendapatkan respons server pertama kami.

Mari kita uraikan apa yang terjadi saat kami menguji server kami. Dengan menggunakan cURL, kami mengirim permintaan GET ke server di http://localhost:8000. Server Node.js kami mendengarkan koneksi dari alamat tersebut. Server meneruskan permintaan itu ke fungsi requestListener(). Fungsi tersebut mengembalikan data teks dengan kode status 200. Server kemudian mengirimkan respons itu kembali ke cURL, yang menampilkan pesan tersebut di terminal kami.

Dalam produksi, biasanya Anda tidak mengikat ke localhost—Anda mengikat ke 0.0.0.0 untuk menerima koneksi dari antarmuka jaringan mana pun. Namun, untuk pengembangan lokal, localhost lebih aman dan mencegah akses eksternal ke server pengembangan Anda. Selain itu, jika port 8000 sudah digunakan, Anda akan mendapatkan kesalahan EADDRINUSE—cukup pilih port yang berbeda atau hentikan proses yang menggunakan port tersebut dengan lsof -i :8000 (macOS/Linux).

Sebelum kita melanjutkan, mari hentikan server yang sedang berjalan dengan menekan CTRL+C. Ini akan menghentikan eksekusi server kita, membawa kita kembali ke prompt baris perintah.

Di sebagian besar situs web yang kita kunjungi atau API yang kita gunakan, respons server jarang dalam bentuk teks biasa. Kita mendapatkan halaman HTML dan data JSON sebagai format respons yang umum. Pada langkah berikutnya, kita akan belajar bagaimana mengembalikan respons HTTP dalam format data umum yang kita temui di web.

Jika Anda ingin belajar tentang cara membuat permintaan HTTP dari Node.js (sisi klien), lihat panduan kami tentang Cara Membuat Klien HTTP dengan HTTP Core di Node.js.

Langkah 2 — Mengembalikan Berbagai Jenis Konten

Respon yang kita kembalikan dari server web dapat memiliki berbagai format. JSON dan HTML telah disebutkan sebelumnya, dan kita juga dapat mengembalikan format teks lain seperti XML dan CSV. Akhirnya, server web juga dapat mengembalikan data non-teks seperti PDF, file zip, audio, dan video.

Dalam artikel ini, selain teks biasa yang baru saja kami kembalikan, Anda akan belajar cara mengembalikan jenis data berikut:

  • JSON
  • CSV
  • HTML

Ketiga tipe data tersebut semuanya berbasis teks, dan merupakan format yang populer untuk menyampaikan konten di web. Banyak bahasa pemrograman dan alat pengembangan sisi server yang mendukung pengembalian berbagai tipe data ini. Dalam konteks Node.js, kita perlu melakukan dua hal:

  1. Atur header Content-Type dalam respons HTTP kita dengan nilai yang sesuai.
  2. Pastikan bahwa res.end() menerima data dalam format yang benar.

Mari kita lihat ini dalam aksi dengan beberapa contoh. Kode yang akan kita tulis di bagian ini dan bagian berikutnya memiliki banyak kesamaan dengan kode yang kita tulis sebelumnya. Sebagian besar perubahan terdapat dalam fungsi requestListener(). Mari buat file dengan “kode template” ini agar bagian-bagian berikutnya lebih mudah diikuti.

Buat file baru bernama html.js. File ini akan digunakan nanti untuk mengembalikan teks HTML dalam respons HTTP. Kita akan menaruh kode template di sini dan menyalinnya ke server lain yang mengembalikan berbagai jenis.

Di terminal, masukkan berikut ini:

touch html.js

Sekarang buka file ini di editor teks:

nano html.js

Mari kita salin “kode template.” Masukkan ini di nano:

const http = require("http");

const host = 'localhost';
const port = 8000;

const requestListener = function (req, res) {};

const server = http.createServer(requestListener);
server.listen(port, host, () => {
    console.log(`Server is running on http://${host}:${port}`);
});

Simpan dan keluar dari html.js dengan CTRL+X, lalu kembali ke terminal.

Sekarang mari kita salin file ini ke dalam dua file baru. File pertama akan digunakan untuk mengembalikan data CSV dalam respons HTTP:

cp html.js csv.js

File kedua akan mengembalikan respons JSON di server:

cp html.js json.js

File yang tersisa akan digunakan untuk latihan berikutnya:

cp html.js htmlFile.js

cp html.js routes.js

Kami sekarang siap untuk melanjutkan latihan kami. Mari kita mulai dengan mengembalikan JSON.

Menyajikan JSON

JavaScript Object Notation, yang biasa disebut JSON, adalah format pertukaran data berbasis teks. Seperti namanya, format ini berasal dari objek JavaScript, tetapi independen dari bahasa pemrograman, artinya dapat digunakan oleh bahasa pemrograman apapun yang dapat mem-parsing sintaksnya.

JSON biasanya digunakan oleh API untuk menerima dan mengembalikan data. Popularitasnya disebabkan oleh ukuran transfer data yang lebih kecil dibandingkan dengan standar pertukaran data sebelumnya seperti XML, serta alat-alat yang ada yang memungkinkan program untuk memprosesnya tanpa usaha berlebihan. Jika Anda ingin mempelajari lebih lanjut tentang JSON, Anda dapat membaca panduan kami tentang Cara Bekerja dengan JSON di JavaScript.

Buka file json.js dengan nano:

nano json.js

Saat menyajikan JSON, Anda harus mengatur header Content-Type menjadi application/json. Tanpa itu, browser dan klien API akan menganggap respons sebagai teks biasa, sehingga fetch().json() gagal. Atur header sebelum memanggil writeHead()—setelah header dikirim, header tidak bisa diubah.

Kesalahan umum: Memanggil res.writeHead(200) sebelum res.setHeader() akan mengunci header. Jika Anda mencoba mengatur header setelah writeHead(), header tersebut akan diabaikan. Selalu panggil setHeader() terlebih dahulu, kemudian writeHead().

Ubah fungsi requestListener():

...
const requestListener = function (req, res) {
    res.setHeader("Content-Type", "application/json");
    res.writeHead(200);
    res.end(`{"message": "This is a JSON response"}`);
};
...

Kode status 200 menunjukkan keberhasilan. Isi respons berisi JSON yang valid.

Mengapa menggunakan JSON.stringify() daripada menulis string secara manual? Menulis JSON secara manual rawan kesalahan. Jika pesan Anda mengandung tanda kutip, baris baru, atau karakter khusus, Anda harus melarikan diri secara manual. JSON.stringify() menangani ini secara otomatis:

const data = { 
    message: "This is a JSON response",
    timestamp: Date.now(),
    note: 'He said "hello"' // Quotes automatically escaped
};
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
res.end(JSON.stringify(data));

Hati-hati dengan referensi melingkar: Jika objek Anda merujuk pada dirinya sendiri, JSON.stringify() akan menghasilkan error: TypeError: Converting circular structure to JSON. Ini sering terjadi pada hasil query database. Selalu validasi struktur data Anda.

Apa yang terjadi jika Anda mencoba mengirim objek secara langsung? Modul http hanya menerima string atau Buffer. Jika Anda mencoba res.end({ message: "test" }), Node.js akan melemparkan: TypeError [ERR_INVALID_ARG_TYPE]: Argumen pertama harus berupa string atau Buffer. Anda harus mengubah objek menjadi string JSON terlebih dahulu.

Periksa apakah header telah diatur dengan benar: Gunakan flag -i dengan curl untuk melihat header respons:

curl -i http://localhost:8000

Anda seharusnya melihat Content-Type: application/json di header. Jika Anda melihat text/plain atau tidak ada Content-Type, itu berarti Anda memanggil setHeader() setelah writeHead() atau sama sekali lupa melakukannya.

Skema debugging umum: Jika JavaScript frontend Anda mendapatkan undefined saat memanggil fetch('/api').then(r => r.json()), periksa header dengan curl -i. Content-Type harus persis application/json (sensitif terhadap huruf besar/kecil untuk beberapa parser yang ketat).

Simpan dan keluar dari json.js dengan menekan CTRL+X. Sekarang, mari jalankan server dengan perintah node:

node json.js

Di terminal lain, mari kita mengakses server dengan menggunakan cURL:

curl http://localhost:8000

Saat kita menekan ENTER, kita akan melihat hasil berikut:

{"message": "This is a JSON response"}

Sekarang kita telah berhasil mengembalikan respons JSON, sama seperti banyak API populer yang kita gunakan untuk membuat aplikasi. Pastikan untuk keluar dari server yang sedang berjalan dengan CTRL+C agar kita bisa kembali ke prompt terminal standar. Selanjutnya, mari kita lihat format lain yang populer untuk mengembalikan data: CSV.

Melayani CSV

Format file Comma Separated Values (CSV) adalah standar teks yang umum digunakan untuk menyediakan data dalam bentuk tabel. Dalam sebagian besar kasus, setiap baris dipisahkan oleh baris baru, dan setiap item dalam baris dipisahkan oleh koma.

Di ruang kerja kami, buka file csv.js dengan editor teks:

nano csv.js

Mari tambahkan baris-baris berikut ke fungsi requestListener() kita:

...
const requestListener = function (req, res) {
    res.setHeader("Content-Type", "text/csv");
    res.setHeader("Content-Disposition", "attachment;filename=oceanpals.csv");
};
...

Kali ini, Content-Type kami menunjukkan bahwa yang dikembalikan adalah file CSV karena nilainya adalah text/csv. Header kedua yang kami tambahkan adalah Content-Disposition. Header ini memberi tahu browser bagaimana menampilkan data, khususnya di browser atau sebagai file terpisah.

Ketika kami mengembalikan respons CSV, sebagian besar browser modern secara otomatis mengunduh file tersebut meskipun header Content-Disposition tidak disetel. Namun, saat mengembalikan file CSV, kita tetap harus menambahkan header ini karena memungkinkan kita untuk menetapkan nama file CSV. Dalam hal ini, kita memberi sinyal ke browser bahwa file CSV ini adalah lampiran dan harus diunduh. Kemudian, kita memberi tahu browser bahwa nama file tersebut adalah oceanpals.csv.

Mari kita tulis data CSV dalam respons HTTP:

...
const requestListener = function (req, res) {
    res.setHeader("Content-Type", "text/csv");
    res.setHeader("Content-Disposition", "attachment;filename=oceanpals.csv");
    res.writeHead(200);
    res.end(`id,name,email\n1,Sammy Shark,shark@ocean.com`);
};
...

Kembalikan status 200/OK dengan data CSV. Koma memisahkan nilai di setiap kolom dan karakter baris baru (\n) memisahkan baris. Kami memiliki dua baris: baris header dan satu baris data.

Perhatian dalam pemformatan CSV: Contoh sederhana di atas berhasil, tetapi pembuatan CSV untuk produksi membutuhkan penghindaran karakter khusus. Jika data Anda mengandung koma (seperti "Smith, John") atau tanda kutip, itu akan merusak format CSV. Penghindaran CSV yang tepat adalah dengan membungkus nilai dengan tanda kutip dan menggandakan tanda kutip di dalamnya: "Smith, John" menjadi "Smith, ""John""" dalam CSV. Untuk produksi, gunakan library seperti csv-stringify atau hindari karakter khusus dengan benar untuk mencegah kesalahan parsing.

Apa yang terjadi tanpa Content-Disposition? Sebagian besar browser mengunduh file jika Content-Type: text/csv disetel, tetapi nama file mungkin menjadi path URL atau sesuatu yang generik. Header attachment;filename=oceanpals.csv memastikan browser menyimpannya dengan nama tertentu. Tanpa attachment, beberapa browser mencoba menampilkan CSV secara langsung, yang sering terlihat berantakan.

Kita akan menguji server ini di browser. Simpan csv.js dan keluar dari editor dengan CTRL+X.

Jalankan server dengan perintah Node.js:

node csv.js

Di Terminal lain, mari kita mengakses server dengan menggunakan cURL:

curl http://localhost:8000

Konsol akan menampilkan ini:

id,name,email
1,Sammy Shark,shark@ocean.com

Jika kita mengunjungi http://localhost:8000 di browser kita, sebuah file CSV akan diunduh. Nama file tersebut akan menjadi oceanpals.csv.

Keluar dari server yang sedang berjalan dengan CTRL+C untuk kembali ke prompt terminal standar.

Setelah mengembalikan JSON dan CSV, kita telah membahas dua kasus yang populer untuk API. Mari kita lanjutkan ke bagaimana kita mengembalikan data untuk situs web yang dilihat orang di browser.

Menyajikan HTML

HTML (HyperText Markup Language), adalah format yang paling umum digunakan saat kita ingin pengguna berinteraksi dengan server kita melalui browser web. HTML dibuat untuk menyusun konten web. Browser web dirancang untuk menampilkan konten HTML, serta gaya apa pun yang kita tambahkan dengan CSS, teknologi web front-end lain yang memungkinkan kita mengubah tampilan estetika situs web kita.

Mari buka kembali html.js dengan editor teks kita:

nano html.js

Ubah fungsi requestListener() untuk mengembalikan header Content-Type yang sesuai untuk respons HTML:

...
const requestListener = function (req, res) {
    res.setHeader("Content-Type", "text/html");
};
...

Sekarang, mari kita kembalikan konten HTML ke pengguna. Tambahkan baris yang disorot ke html.js sehingga terlihat seperti ini:

...
const requestListener = function (req, res) {
    res.setHeader("Content-Type", "text/html");
    res.writeHead(200);
    res.end(`<html><body><h1>This is HTML</h1></body></html>`);
};
...

Pertama-tama kita menambahkan kode status HTTP. Kemudian kita memanggil response.end() dengan argumen string yang berisi HTML yang valid. Ketika kita mengakses server kita di browser, kita akan melihat halaman HTML dengan satu tag header yang berisi This is HTML.

Mari simpan dan keluar dengan menekan CTRL+X. Sekarang, mari jalankan server dengan perintah node:

node html.js

Kita akan melihat Server is running on http://localhost:8000 saat program kita telah dimulai.

Sekarang buka browser dan kunjungi http://localhost:8000. Halaman kami akan terlihat seperti ini:

Mari hentikan server yang sedang berjalan dengan CTRL+C dan kembali ke prompt terminal standar.

Umum bagi HTML untuk ditulis dalam sebuah file, terpisah dari kode sisi server seperti program Node.js kita. Selanjutnya, mari kita lihat bagaimana kita dapat mengembalikan respons HTML dari file.

Langkah 3 — Menyajikan Halaman HTML dari File

Kita bisa menyajikan HTML sebagai string di Node.js kepada pengguna, tetapi sebaiknya kita memuat file HTML dan menyajikan isinya. Dengan cara ini, seiring pertumbuhan file HTML, kita tidak perlu mempertahankan string panjang dalam kode Node.js kita, sehingga kode tetap lebih ringkas dan memungkinkan kita untuk bekerja pada setiap aspek website secara independen. "Pemecahan masalah berdasarkan kepentingan" ini umum dalam banyak pengaturan pengembangan web, jadi ada baiknya mengetahui cara memuat file HTML untuk mendukungnya di Node.js.

Untuk menyajikan file HTML, kita memuat file HTML dengan modul fs dan menggunakan datanya saat menulis respons HTTP kita.

Pertama, kita akan membuat file HTML yang akan dikembalikan oleh server web. Buat file HTML baru:

touch index.html

Sekarang buka index.html di editor teks:

nano index.html

Halaman web kami akan minimalis. Halaman ini akan memiliki latar belakang oranye dan menampilkan beberapa teks salam di tengah. Tambahkan kode ini ke file:

<!DOCTYPE html>

<head>
    <title>My Website</title>
    <style>
        *,
        html {
            margin: 0;
            padding: 0;
            border: 0;
        }

        html {
            width: 100%;
            height: 100%;
        }

        body {
            width: 100%;
            height: 100%;
            position: relative;
            background-color: rgb(236, 152, 42);
        }

        .center {
            width: 100%;
            height: 50%;
            margin: 0;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: white;
            font-family: "Trebuchet MS", Helvetica, sans-serif;
            text-align: center;
        }

        h1 {
            font-size: 144px;
        }

        p {
            font-size: 64px;
        }
    </style>
</head>

<body>
    <div class="center">
        <h1>Hello Again!</h1>
        <p>This is served from a file</p>
    </div>
</body>

</html>

Halaman web tunggal ini menampilkan dua baris teks: Hello Again! dan This is served from a file. Baris-baris tersebut muncul di tengah halaman, satu di atas yang lain. Baris teks pertama ditampilkan dalam bentuk heading, artinya akan berukuran besar. Baris teks kedua akan muncul sedikit lebih kecil. Semua teks akan berwarna putih dan halaman web memiliki latar belakang berwarna oranye.

Meskipun ini bukanlah cakupan artikel atau seri ini, jika Anda tertarik untuk mempelajari lebih lanjut tentang HTML, CSS, dan teknologi web front-end lainnya, Anda dapat melihat panduan Mozilla tentang Memulai dengan Web.

Itu saja yang kita butuhkan untuk HTML, jadi simpan dan keluar dari file dengan CTRL+X. Sekarang kita bisa melanjutkan ke kode server.

Untuk latihan ini, kita akan bekerja pada htmlFile.js. Buka dengan editor teks:

nano htmlFile.js

Karena kita harus membaca sebuah file, mari kita mulai dengan mengimpor modul fs:

const http = require("http");
const fs = require('fs').promises;
...

Modul ini berisi fungsi readFile() yang akan kita gunakan untuk memuat file HTML di tempat. Kami mengimpor varian promise sesuai dengan praktik terbaik JavaScript modern. Kami menggunakan promise karena secara sintaksis lebih ringkas daripada callback, yang harus kami gunakan jika kami menetapkan fs hanya ke require('fs'). Untuk mempelajari lebih lanjut tentang praktik terbaik pemrograman asynchronous, Anda dapat membaca panduan kami Cara Menulis Kode Asynchronous di Node.js.

Kita ingin file HTML kita dibaca saat seorang pengguna meminta sistem kita. Mari kita mulai dengan memodifikasi requestListener() untuk membaca file tersebut:

...
const requestListener = function (req, res) {
    fs.readFile(__dirname + "/index.html")
};
...

Kami menggunakan metode fs.readFile() untuk memuat file. Argumennya adalah __dirname + "/index.html". Variabel khusus __dirname memiliki jalur absolut dari lokasi kode Node.js dijalankan. Kemudian kami menambahkan /index.html agar kami dapat memuat file HTML yang sebelumnya telah kami buat.

Sekarang mari kita kembalikan halaman HTML setelah dimuat:

...
const requestListener = function (req, res) {
    fs.readFile(__dirname + "/index.html")
        .then(contents => {
            res.setHeader("Content-Type", "text/html");
            res.writeHead(200);
            res.end(contents);
        })
};
...

Jika janji fs.readFile() berhasil terpenuhi, itu akan mengembalikan datanya. Kita menggunakan metode then() untuk menangani kasus ini. Parameter contents berisi data dari file HTML.

Pertama, kami menetapkan header Content-Type ke text/html untuk memberi tahu klien bahwa kami mengembalikan data HTML. Kemudian kami menulis kode status untuk menunjukkan bahwa permintaan berhasil. Akhirnya, kami mengirimkan halaman HTML yang kami muat ke klien, dengan data di variabel contents.

Metode fs.readFile() kadang bisa gagal, jadi kita harus menangani kasus ini ketika kita mendapatkan error. Tambahkan ini ke fungsi requestListener():

...
const requestListener = function (req, res) {
    fs.readFile(__dirname + "/index.html")
        .then(contents => {
            res.setHeader("Content-Type", "text/html");
            res.writeHead(200);
            res.end(contents);
        })
        .catch(err => {
            res.writeHead(500);
            res.end('Internal Server Error');
            return;
        });
};
...

Simpan file dan keluar dari nano dengan CTRL+X.

Saat sebuah promise mengalami kesalahan, promise tersebut ditolak. Kami menangani kasus itu dengan metode catch(). Metode ini mengatur kode status menjadi 500 yang menandakan bahwa terjadi kesalahan internal, dan mengirimkan pesan kesalahan yang ramah pengguna. Dalam produksi, Anda sebaiknya tidak pernah menampilkan detail kesalahan mentah kepada pengguna, karena ini bisa membocorkan informasi sensitif.

Jalankan server kami dengan perintah node:

node htmlFile.js

Di peramban web, kunjungi http://localhost:8000. Anda akan melihat halaman ini:

Anda sekarang telah mengembalikan halaman HTML dari server ke pengguna. Anda dapat menghentikan server yang sedang berjalan dengan CTRL+C. Anda akan melihat prompt terminal kembali saat Anda melakukannya.

Ketika menulis kode seperti ini di lingkungan produksi, Anda mungkin tidak ingin memuat halaman HTML setiap kali menerima permintaan HTTP. Meskipun halaman HTML ini ukurannya sekitar 800 byte, situs web yang lebih kompleks bisa mencapai ukuran megabyte. File besar bisa memerlukan waktu cukup lama untuk dimuat. Jika situs Anda mengharapkan banyak lalu lintas, sebaiknya muat file HTML saat startup dan simpan isinya. Setelah dimuat, Anda dapat menyiapkan server dan membuatnya mendengarkan permintaan pada suatu alamat.

Untuk menunjukkan metode ini, mari kita lihat bagaimana kita bisa mengubah server kita agar lebih efisien dan dapat diskalakan.

Menyajikan HTML secara Efisien

Alih-alih memuat HTML untuk setiap permintaan, pada langkah ini kita akan memuatnya sekali di awal. Permintaan akan mengembalikan data yang kita muat saat startup.

Di terminal, buka kembali skrip Node.js dengan editor teks:

nano htmlFile.js

Mari kita mulai dengan menambahkan variabel baru sebelum kita membuat fungsi requestListener():

...
let indexFile;

const requestListener = function (req, res) {
...

Saat kita menjalankan program ini, variabel ini akan menampung isi file HTML.

Sekarang, mari kita sesuaikan kembali fungsi requestListener(). Alih-alih memuat file, sekarang fungsi ini akan mengembalikan isi dari indexFile:

...
const requestListener = function (req, res) {
    res.setHeader("Content-Type", "text/html");
    res.writeHead(200);
    res.end(indexFile);
};
...

Selanjutnya, kita memindahkan logika pembacaan file dari fungsi requestListener() ke saat server kita dijalankan. Lakukan perubahan berikut saat kita membuat server:

...

const server = http.createServer(requestListener);

fs.readFile(__dirname + "/index.html")
    .then(contents => {
        indexFile = contents;
        server.listen(port, host, () => {
            console.log(`Server is running on http://${host}:${port}`);
        });
    })
    .catch(err => {
        console.error(`Could not read index.html file: ${err}`);
        process.exit(1);
    });

Simpan file dan keluar dari nano dengan CTRL+X.

Kode yang membaca file mirip dengan apa yang kita tulis dalam percobaan pertama kita. Namun, ketika kita berhasil membaca file, kita sekarang menyimpan isi file ke variabel global indexFile kita. Kemudian kita memulai server dengan metode listen(). Hal pentingnya adalah file dimuat sebelum server dijalankan. Dengan cara ini, fungsi requestListener() akan yakin mengembalikan halaman HTML, karena indexFile tidak lagi menjadi variabel kosong.

Penangan kesalahan kami juga telah berubah. Jika file tidak dapat dimuat, kami menangkap kesalahan tersebut dan mencetaknya ke konsol kami. Kemudian kami keluar dari program Node.js dengan fungsi exit() tanpa memulai server. Dengan cara ini, kami dapat melihat mengapa pembacaan file gagal, menangani masalahnya, dan kemudian memulai server lagi.

Sekarang kita telah membuat berbagai server web yang mengembalikan berbagai jenis data kepada pengguna. Sejauh ini, kita belum menggunakan data permintaan untuk menentukan apa yang harus dikembalikan. Kita akan perlu menggunakan data permintaan saat menyiapkan rute atau jalur yang berbeda di server Node.js, jadi selanjutnya mari kita lihat bagaimana mereka bekerja bersama.

Langkah 4 — Mengelola Rute Menggunakan Objek Permintaan HTTP

Sebagian besar situs web yang kita kunjungi atau API yang kita gunakan biasanya memiliki lebih dari satu endpoint sehingga kita bisa mengakses berbagai sumber daya. Contoh yang baik adalah sistem manajemen buku, seperti yang mungkin digunakan di perpustakaan. Sistem ini tidak hanya perlu mengelola data buku, tetapi juga mengelola data penulis untuk kemudahan katalogisasi dan pencarian.

Meskipun data untuk buku dan penulis saling terkait, keduanya adalah objek yang berbeda. Dalam kasus seperti ini, pengembang perangkat lunak biasanya membuat kode setiap objek di endpoint yang berbeda sebagai cara untuk memberi tahu pengguna API jenis data apa yang sedang mereka gunakan.

Mari kita buat server baru untuk sebuah perpustakaan kecil, yang akan mengembalikan dua jenis data yang berbeda. Jika pengguna mengunjungi alamat server kita di /books, mereka akan menerima daftar buku dalam format JSON. Jika mereka pergi ke /authors, mereka akan menerima daftar informasi penulis dalam format JSON.

Sejauh ini, server mengembalikan respons yang sama untuk setiap permintaan. Untuk mendukung beberapa sumber daya, simpan data Anda dalam variabel dan gunakan pengaturan rute bersyarat. Misalnya:

const books = JSON.stringify([
  { title: "The Alchemist", author: "Paulo Coelho", year: 1988 },
  { title: "The Prophet", author: "Kahlil Gibran", year: 1923 }
]);
const authors = JSON.stringify([
  { name: "Paulo Coelho", countryOfBirth: "Brazil", yearOfBirth: 1947 },
  { name: "Kahlil Gibran", countryOfBirth: "Lebanon", yearOfBirth: 1883 }
]);

const requestListener = function (req, res) {
  res.setHeader("Content-Type", "application/json");
  switch (req.url) {
    case "/books":
      res.writeHead(200);
      res.end(books);
      break;
    case "/authors":
      res.writeHead(200);
      res.end(authors);
      break;
    default:
      res.writeHead(404);
      res.end(JSON.stringify({error:"Resource not found"}));
  }
}

Uji dengan curl http://localhost:8000/books atau /authors untuk melihat data masing-masing. URL lain akan mengembalikan error 404.

Masalah dengan pencocokan string sederhana: req.url adalah string mentah yang mencakup semuanya setelah domain—jalur, string kueri, dan fragmen. Jika seseorang mengunjungi /books?id=123, case "/books" Anda tidak akan cocok karena req.url adalah /books?id=123, bukan /books. Parameter kueri dapat merusak routing: /books?sort=name tidak akan cocok dengan /books. Karakter yang di-url-encode dapat merusak routing: /books%20reviews (spasi dikodekan sebagai %20) tidak akan cocok dengan /books reviews.

Gunakan API URL sebagai gantinya: API URL secara otomatis memisahkan pathname dari parameter kueri, mendekode karakter yang di-URL-kan, dan menangani kasus tepi dengan benar.

const host = req.headers.host || 'localhost:8000';
const url = new URL(req.url, `http://${host}`);

switch (url.pathname) {
  case '/books':
    // Validate HTTP method - only GET allowed for this endpoint
    if (req.method !== 'GET') {
      res.writeHead(405, { 'Allow': 'GET' });
      return res.end(JSON.stringify({ error: 'Method not allowed' }));
    }
    res.writeHead(200);
    return res.end(books);

  case '/authors':
    if (req.method !== 'GET') {
      res.writeHead(405, { 'Allow': 'GET' });
      return res.end(JSON.stringify({ error: 'Method not allowed' }));
    }
    res.writeHead(200);
    return res.end(authors);

  default:
    res.writeHead(404);
    return res.end(JSON.stringify({ error: 'Resource not found' }));
}

Mengapa memeriksa metode HTTP? Jalur yang sama mungkin mendukung metode HTTP yang berbeda (GET untuk mengambil, POST untuk membuat). Tanpa pemeriksaan ini, permintaan POST ke /books akan mengembalikan daftar buku alih-alih membuat buku baru, yang melanggar konvensi REST API. Kode status 405 Method Not Allowed dengan header Allow memberi tahu klien metode mana yang valid untuk endpoint ini.

Penanganan string kueri: API URL secara otomatis memisahkan pathname (/books) dari parameter kueri (?id=123), sehingga /books?id=123 dapat diarahkan ke endpoint books dengan benar, sementara Anda masih dapat mengakses url.searchParams.get('id') jika diperlukan.

Uji berbagai skenario:

curl http://localhost:8000/books

curl http://localhost:8000/authors  

curl http://localhost:8000/books?id=123  # Query string doesn't break routing

curl -X POST http://localhost:8000/books  # Returns 405 Method Not Allowed

curl http://localhost:8000/nonexistent  # Returns 404

Langkah 5 — Menangani Permintaan POST dan Mengurai Isi Permintaan

Sejauh ini, kita hanya menangani permintaan GET. Sebagian besar API perlu menerima data dari klien, yang biasanya dilakukan melalui permintaan POST. Modul http mengharuskan pemrosesan manual terhadap isi permintaan, yang diterima sebagai aliran.

Wawasan penting: Berbeda dengan permintaan GET di mana data hadir di URL, badan permintaan POST datang sebagai aliran. Ini berarti data tidak datang sekaligus—melainkan dalam beberapa bagian. Jika Anda mencoba menguraikan JSON sebelum semua bagian datang, Anda akan mendapatkan data yang tidak lengkap atau rusak. Anda harus mengumpulkan semua bagian terlebih dahulu, kemudian menguraikannya.

Ini yang terjadi jika Anda mencoba untuk mengurai terlalu dini:

// WRONG - Body isn't complete yet
req.on('data', chunk => {
  const data = JSON.parse(chunk); // Will fail - chunk is only part of the body
});

// CORRECT - Collect all chunks first
let body = '';
req.on('data', chunk => body += chunk.toString());
req.on('end', () => {
  const data = JSON.parse(body); // Now body is complete
});

Buat server yang menerima permintaan POST dengan payload JSON. Buat file baru:

touch post.js

cp routes.js post.js

Buka post.js:

nano post.js

Berikut adalah parser tubuh JSON siap produksi yang menangani masalah keamanan:

const MAX_BODY = 1 * 1024 * 1024; // 1MB limit prevents memory exhaustion

function parseJSONBody(req, res) {
  return new Promise((resolve, reject) => {
    let body = '';
    let received = 0;
    
    // Validate Content-Type before processing
    const contentType = req.headers['content-type'] || '';
    if (!contentType.startsWith('application/json')) {
      res.writeHead(415, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'Unsupported Content-Type' }));
      return reject(new Error('Unsupported Content-Type'));
    }
    
    // Collect data chunks
    req.on('data', chunk => {
      received += chunk.length;
      
      // Prevent memory exhaustion from huge payloads
      if (received > MAX_BODY) {
        res.writeHead(413, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: 'Payload too large' }));
        req.destroy(); // Stop receiving more data
        return reject(new Error('Payload too large'));
      }
      body += chunk.toString(); // Convert Buffer to string
    });
    
    // All chunks received, now parse
    req.on('end', () => {
      try {
        if (!body) {
          res.writeHead(400, { 'Content-Type': 'application/json' });
          res.end(JSON.stringify({ error: 'Empty request body' }));
          return reject(new Error('Empty request body'));
        }
        
        const data = JSON.parse(body);
        
        // Validate it's an object (not a string, number, etc.)
        if (typeof data !== 'object' || data === null || Array.isArray(data)) {
          res.writeHead(400, { 'Content-Type': 'application/json' });
          res.end(JSON.stringify({ error: 'Invalid JSON object' }));
          return reject(new Error('Invalid JSON object'));
        }
        
        resolve(data);
      } catch (parseError) {
        // JSON.parse() throws for malformed JSON
        res.writeHead(400, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: 'Malformed JSON' }));
        console.error('JSON parse error:', parseError.message);
      }
    });
    
    // Handle network errors
    req.on('error', (error) => {
      console.error('Request error:', error.message);
      res.writeHead(400, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'Request error' }));
    });
  });
}

Mengapa memeriksa Content-Type? Jika klien mengirim data formulir dengan Content-Type: application/x-www-form-urlencoded, mencoba menguraikannya sebagai JSON akan gagal. Kode status 415 Unsupported Media Type memberi tahu klien bahwa Content-Type mereka tidak diterima.

Mengapa membatasi ukuran tubuh? Tanpa batas, klien yang berniat jahat bisa mengirim gigabyte data, yang dapat menguras memori server Anda. Respon 413 Payload Too Large dan req.destroy() menghentikan koneksi segera saat batas terlampaui.

Mengapa memvalidasi hasil parsing? JSON.parse('"hello"') berhasil tetapi mengembalikan string, bukan objek. Kode Anda mungkin mengharapkan data.property berfungsi, tetapi string tidak memiliki properti. Pemeriksaan ini memastikan Anda mendapatkan objek yang sebenarnya.

Gunakan fungsi ini di handler POST Anda:

const requestListener = async function (req, res) {
  if (req.method === 'POST' && req.url === '/books') {
    const bookData = await parseJSONBody(req, res);
    // Now use bookData safely
    // ...
  }
};

Langkah 6 — Menyajikan Berkas Statis

Banyak aplikasi web perlu menyajikan aset statis seperti gambar, file CSS, file JavaScript, atau sumber daya lainnya. Meskipun modul http tidak memiliki penyajian file statis bawaan seperti express.static di Express, Anda dapat mengimplementasikannya menggunakan modul fs dan path.

Masalah keamanan kritis: Jika Anda menggunakan path.join(__dirname, 'public', req.url) secara sembarangan tanpa validasi, penyerang dapat mengakses file apa pun di server Anda menggunakan serangan traversal direktori. Permintaan seperti GET /../../../etc/passwd bisa mengekspos file sistem. Pemeriksaan path.resolve() memastikan path yang dihasilkan tidak pernah keluar dari direktori public.

Berikut adalah hal-hal yang bisa salah tanpa validasi jalur yang tepat:

// VULNERABLE - Allows directory traversal
const filePath = path.join(__dirname, 'public', req.url);
// Request: /../../../etc/passwd
// Resolves to: /etc/passwd (outside public directory!)

// SECURE - Validates path stays within public directory
const safePath = path.resolve(publicDir, '.' + reqPath);
if (!safePath.startsWith(publicDir)) {
  res.writeHead(403);
  res.end('Forbidden');
  return;
}
// Request: /../../../etc/passwd
// safePath: /home/user/app/public/../../../etc/passwd
// Resolved: /etc/passwd
// Check fails because /etc/passwd doesn't start with /home/user/app/public

Buat server file statis yang aman:

touch static.js

Buka static.js:

nano static.js

Melayani file statis secara efisien dan aman dengan streaming menggunakan fs.createReadStream() dan resolusi jalur yang aman:

const http = require("http");
const fs = require("fs");
const path = require("path");

const host = 'localhost';
const port = 8000;

const mimeTypes = {
  '.html': 'text/html',
  '.css': 'text/css',
  '.js': 'application/javascript',
  '.json': 'application/json',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.gif': 'image/gif',
  '.svg': 'image/svg+xml',
  '.ico': 'image/x-icon',
  '.pdf': 'application/pdf'
};

const publicDir = path.resolve(__dirname, 'public');

const requestListener = (req, res) => {
  // Normalize the request path
  let reqPath = req.url === '/' ? '/index.html' : req.url;
  
  // Strip query string - ?id=123 shouldn't affect file path
  reqPath = reqPath.split('?')[0];
  
  // Resolve path and check it stays within publicDir
  // The '.' prefix prevents absolute paths from being resolved incorrectly
  const safePath = path.resolve(publicDir, '.' + reqPath);
  
  // Critical security check: ensure resolved path is within publicDir
  if (!safePath.startsWith(publicDir)) {
    res.writeHead(403, { 'X-Content-Type-Options': 'nosniff' });
    res.end('Forbidden');
    return;
  }
  
  // Check if file exists and is actually a file (not a directory)
  fs.stat(safePath, (err, stat) => {
    if (err || !stat.isFile()) {
      res.writeHead(404, { 'X-Content-Type-Options': 'nosniff' });
      res.end('File not found');
      return;
    }
    
    // Determine MIME type from file extension
    const ext = path.extname(safePath).toLowerCase();
    const contentType = mimeTypes[ext] || 'application/octet-stream';
    
    res.setHeader("Content-Type", contentType);
    res.setHeader("X-Content-Type-Options", "nosniff"); // Prevents MIME sniffing attacks
    res.writeHead(200);
    
    // Stream file instead of loading into memory
    // Critical for large files - won't consume all memory
    const stream = fs.createReadStream(safePath);
    stream.pipe(res);
    
    stream.on('error', () => {
      // File might be deleted between stat() and createReadStream()
      res.writeHead(500, { 'X-Content-Type-Options': 'nosniff' });
      res.end('Server error');
    });
  });
};

const server = http.createServer(requestListener);
server.listen(port, host, () => {
  console.log(`Server is running on http://${host}:${port}`);
  console.log(`Serving static files from: ${publicDir}`);
});

Buat direktori public dan tambahkan file uji:

mkdir public

echo "<h1>Static File Served!</h1>" > public/index.html

Jalankan server:

node static.js

Kunjungi http://localhost:8000 di browser Anda untuk melihat file statis yang dilayani.

Mengapa menggunakan streaming dibandingkan fs.readFile()? Untuk file video 100MB, fs.readFile() memuat seluruh file ke memori sebelum mengirimnya. Dengan fs.createReadStream(), data mengalir langsung dari disk ke jaringan dalam potongan kecil, menggunakan memori minimal terlepas dari ukuran file.

Mengapa memeriksa stat.isFile()? Jika seseorang meminta path direktori seperti /images/, fs.stat() akan berhasil tetapi isFile() akan mengembalikan false. Tanpa pemeriksaan ini, Anda mungkin mencoba men-stream direktori, yang akan gagal. Pemeriksaan ini memastikan Anda hanya menyajikan file yang sesungguhnya.

Uji perlindungan lintasan jalur:

curl http://localhost:8000/../../../etc/passwd

# Should return 403 Forbidden, not the system file

Langkah 7 — Penanganan Kesalahan dan Pencatatan

Penanganan kesalahan yang kuat sangat penting untuk server produksi. Modul http tidak menyediakan penanganan kesalahan otomatis, jadi Anda harus mengimplementasikannya sendiri.

Penanganan kesalahan yang tepat melibatkan penangkapan kesalahan asinkron, pencatatan kesalahan tersebut dengan benar, mengirimkan respons kesalahan yang ramah pengguna, dan mencegah kerusakan server akibat kesalahan yang tidak tertangani. Mari kita tingkatkan server kita dengan penanganan kesalahan dan pencatatan yang komprehensif:

const http = require("http");
const fs = require('fs').promises;

const host = 'localhost';
const port = 8000;

// Simple logging utility
const log = {
    info: (msg) => console.log(`[INFO] ${new Date().toISOString()} - ${msg}`),
    error: (msg, err) => console.error(`[ERROR] ${new Date().toISOString()} - ${msg}`, err),
    warn: (msg) => console.warn(`[WARN] ${new Date().toISOString()} - ${msg}`)
};

const requestListener = async function (req, res) {
    try {
        log.info(`${req.method} ${req.url} - ${req.headers['user-agent'] || 'Unknown'}`);
        if (req.url === '/') {
            try {
                const data = await fs.readFile(__dirname + '/index.html');
                res.setHeader("Content-Type", "text/html");
                res.writeHead(200);
                res.end(data);
            } catch (error) {
                log.error('Failed to read index.html', error);
                res.writeHead(500);
                res.end('<h1>Internal Server Error</h1><p>Unable to load page.</p>');
            }
        } else if (req.url === '/error') {
            throw new Error('Simulated error for testing');
        } else {
            res.writeHead(404);
            res.end('<h1>Not Found</h1><p>The requested resource was not found.</p>');
        }
    } catch (error) {
        log.error('Unhandled error in request handler', error);
        const errorMessage = process.env.NODE_ENV !== 'production'
            ? error.message
            : 'An unexpected error occurred';
        res.writeHead(500);
        res.end(`<h1>Internal Server Error</h1><p>${errorMessage}</p>`);
    }
};

const server = http.createServer(requestListener);

// Handle server-level errors
server.on('error', (error) => {
    if (error.code === 'EADDRINUSE') {
        log.error(`Port ${port} is already in use`);
    } else {
        log.error('Server error', error);
    }
    process.exit(1);
});

// Handle client connection errors
server.on('clientError', (err, socket) => {
    log.warn('Client error: ' + err.message);
    socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(port, host, () => {
    log.info(`Server is running on http://${host}:${port}`);
});

Pemadaman Halus (SIGTERM/SIGINT)

Saat dijalankan di bawah pengelola proses seperti PM2 atau Docker, tangani sinyal terminasi untuk menutup server dengan baik:

const connections = new Set();

server.on('connection', socket => {
  connections.add(socket);
  socket.on('close', () => connections.delete(socket));
});

const shutdown = (signal) => {
  log.warn(`${signal} received: shutting down gracefully...`);
  server.close(() => {
    log.info('Server closed.');
    process.exit(0);
  });
  setTimeout(() => {
    log.warn('Forcing socket close...');
    connections.forEach(s => s.destroy());
    process.exit(1);
  }, 10000).unref();
};

process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));

Praktik penanganan kesalahan utama yang ditunjukkan:

  1. Blok try-catch di sekitar operasi async
  2. Penangan kesalahan tingkat server untuk masalah koneksi dan port
  3. Penanganan kesalahan klien untuk permintaan yang tidak valid
  4. Pencatatan dengan cap waktu dan tingkat keparahan
  5. Pesan kesalahan yang sadar lingkungan (jangan menampilkan jejak tumpukan di produksi)
  6. Respons kesalahan yang anggun dengan kode status HTTP yang sesuai

Praktik Terbaik: Dalam produksi, pertimbangkan untuk menggunakan pustaka logging terstruktur seperti Winston atau Pino, dan kirim kesalahan kritis ke layanan pelacakan kesalahan seperti Sentry atau Rollbar. Contoh ini menggunakan logging konsol sederhana untuk tujuan pendidikan.

Langkah 8 — Menggunakan Sintaks Modul ES (ESM)

Node.js modern mendukung ES Modules (ESM) menggunakan sintaks import/export alih-alih CommonJS require/module.exports. Untuk menggunakan ESM, beri nama file Anda dengan ekstensi .mjs atau tetapkan "type": "module" di package.json Anda.

Sintaks ESM menggunakan import alih-alih require dan export alih-alih module.exports. Berikut adalah server dasar yang sama menggunakan sintaks Modul ES:

Buat server.mjs:

touch server.mjs

nano server.mjs
import http from 'http';
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';

// ES Modules don't have __dirname, so we recreate it
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const host = 'localhost';
const port = 8000;

const requestListener = async (req, res) => {
    try {
        if (req.url === '/') {
            const data = await fs.readFile(path.join(__dirname, 'index.html'));
            res.setHeader("Content-Type", "text/html");
            res.writeHead(200);
            res.end(data);
        } else {
            res.writeHead(404);
            res.end('Not found');
        }
    } catch (error) {
        console.error('Error:', error);
        res.writeHead(500);
        res.end('Server error');
    }
};

const server = http.createServer(requestListener);

server.listen(port, host, () => {
    console.log(`Server is running on http://${host}:${port}`);
});

Jalankan itu:

node server.mjs

Perbedaan Utama: Dalam ESM, __dirname tidak tersedia—Anda harus mendapatkannya dari import.meta.url. Selain itu, jalur file dalam operasi fs mungkin perlu diselesaikan secara eksplisit. Selain itu, API modul HTTP tetap sama.

Sebagai alternatif, buat package.json dengan "type": "module":

{
  "type": "module"
}

Kemudian gunakan file .js dengan sintaks ESM:

import http from 'http';

const server = http.createServer((req, res) => {
    res.writeHead(200);
    res.end('ESM Server');
});

server.listen(8000);

Pertimbangan Produksi

Saat berpindah dari pengembangan ke produksi, beberapa pertimbangan penting perlu diperhatikan:

Dukungan HTTPS

Modul http hanya melayani HTTP. Untuk HTTPS, gunakan modul https dengan cara yang serupa:

const https = require('https');
const fs = require('fs');

const options = {
    key: fs.readFileSync('path/to/private-key.pem'),
    cert: fs.readFileSync('path/to/certificate.pem')
};

const server = https.createServer(options, requestListener);

Keamanan: Jangan pernah menyimpan kunci pribadi atau sertifikat ke dalam sistem kontrol versi. Gunakan variabel lingkungan atau layanan manajemen rahasia yang aman di produksi.

Konfigurasi Reverse Proxy

Dalam produksi, server Node.js biasanya dijalankan di belakang reverse proxy seperti Nginx atau Apache. Proxy menangani terminasi SSL, file statis, dan penyeimbangan beban. Server Node.js Anda sebaiknya:

  • Dengarkan hanya di localhost atau 127.0.0.1
  • Baca header X-Forwarded-For untuk IP klien
  • Percayai header proxy hanya dari server proxy Anda

Lihat panduan kami tentang Cara Mengonfigurasi Nginx sebagai Reverse Proxy di Ubuntu untuk detail pengaturannya.

Manajemen Proses

Untuk produksi, gunakan pengelola proses seperti PM2 untuk menangani:

  • Restart otomatis saat terjadi crash
  • Penerapan tanpa waktu henti
  • Manajemen log
  • Mode klaster untuk memanfaatkan beberapa inti CPU

Pelajari lebih lanjut di panduan kami Cara Mengatur Aplikasi Node.js untuk Produksi di Ubuntu.

Optimisasi Kinerja

  • Cache file: Muat file statis saat startup (seperti yang ditunjukkan pada Langkah 3) daripada membaca dari disk setiap kali permintaan. Ini secara signifikan mengurangi beban I/O.

  • Stream file besar: Untuk file lebih dari 1MB, gunakan fs.createReadStream() dan alirkan ke respon daripada memuat seluruh file ke dalam memori.

  • Connection pooling: Untuk koneksi database, gunakan connection pooling untuk menghindari beban dari pembuatan koneksi baru setiap permintaan.

  • Kompressi: Gunakan modul zlib untuk kompresi gzip pada respons teks guna mengurangi penggunaan bandwidth:

    const zlib = require('zlib');
    const gzip = zlib.createGzip();
    res.setHeader('Content-Encoding', 'gzip');
    gzip.pipe(res);
    gzip.write(data);
    gzip.end();
  • Mode cluster: Gunakan modul Node.js cluster untuk membuat proses pekerja sebanyak jumlah inti CPU, memaksimalkan throughput untuk operasi yang intensif CPU.

  • Batas waktu permintaan: Atur nilai batas waktu yang wajar untuk mencegah klien yang lambat mengonsumsi sumber daya:

    server.setTimeout(30000); // 30 seconds
  • Pemantauan memori: Lacak penggunaan memori untuk mendeteksi kebocoran lebih awal. Di lingkungan produksi, pantau ukuran heap dan lakukan restart secara teratur jika memori tumbuh tanpa batas.

Praktik Terbaik untuk Caching dan Kompresi

Untuk meningkatkan kinerja dan mengurangi waktu respons, terapkan caching dan kompresi di tempat yang sesuai.

  • Aktifkan caching untuk file statis:
    Tambahkan header Cache-Control dan ETag pada respons file statis Anda. Misalnya:

    const stat = fs.statSync(safePath);
    res.setHeader('Cache-Control', 'public, max-age=86400'); // cache for 1 day
    res.setHeader('ETag', `"${stat.size}-${stat.mtime.getTime()}"`);

    Peramban dan CDN akan menggunakan kembali salinan yang tersimpan di cache sampai ETag atau durasi cache berakhir.

  • Gunakan kompresi gzip atau Brotli:
    Respons berbasis teks seperti HTML, CSS, JS, dan JSON sangat mudah dikompresi. Anda dapat menerapkan kompresi menggunakan modul zlib Node:

    const zlib = require('zlib');
    const stream = fs.createReadStream(safePath);
    res.setHeader('Content-Encoding', 'gzip');
    stream.pipe(zlib.createGzip()).pipe(res);

    Ini mengurangi penggunaan bandwidth dan mempercepat pengiriman bagi klien dengan koneksi yang lebih lambat.

  • Hindari caching berlebihan pada endpoint dinamis:
    Untuk API atau data yang sering diperbarui, atur durasi cache yang lebih pendek (misalnya, max-age=60) atau gunakan no-store jika diperlukan.

Optimisasi ini melengkapi streaming file statis dan shutdown yang mulus, membantu server Node.js Anda memberikan kinerja tinggi sekaligus tetap aman dan dapat diprediksi.

Gunakan modul http Node.js untuk API minimal, routing kustom, atau mempelajari internals HTTP.
Gunakan Express/Fastify ketika Anda membutuhkan middleware, templating, atau prototyping cepat.

Pemecahan Masalah Umum

Port Sudah Digunakan (EADDRINUSE)

Error: Error: listen EADDRINUSE: alamat sudah digunakan :::8000

Solusi:

  • Temukan proses yang menggunakan port: lsof -i :8000 (macOS/Linux) atau netstat -ano | findstr :8000 (Windows)
  • Hentikan prosesnya atau gunakan port yang berbeda
  • Tambahkan penanganan kesalahan (seperti yang ditunjukkan pada Langkah 7) untuk menangkap ini dengan baik

Permintaan Body Tidak Diterima

Masalah: Badan permintaan POST kosong atau tidak terdefinisi

Solusi: Body permintaan adalah aliran—Anda harus mengumpulkan potongan-potongan. Selalu gunakan pola yang ditunjukkan pada Langkah 5:

let body = '';
req.on('data', chunk => body += chunk.toString());
req.on('end', () => {
    // Now parse body
});

Kesalahan CORS di Peramban

Masalah: Browser memblokir permintaan karena kebijakan CORS

Solusi: Tambahkan header CORS di respons Anda:

res.setHeader("Access-Control-Allow-Origin", "*"); // Or specific domain
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");

Keamanan: Menggunakan "*" untuk Access-Control-Allow-Origin memperbolehkan asal mana pun. Di lingkungan produksi, tentukan domain frontend Anda secara eksplisit.

Berkas Statis Mengembalikan 404

Masalah: Berkas statis tidak ditemukan meskipun ada

Solusi:

  • Periksa resolusi jalur berkas
  • Periksa apakah __dirname mengarah ke direktori yang benar
  • Pastikan izin file memungkinkan untuk dibaca
  • Validasikan bahwa sanitasi jalur tidak menghapus karakter yang valid

Pertanyaan yang Sering Diajukan

Apa itu modul HTTP dalam Node.js?

Modul http adalah modul bawaan Node.js untuk membuat server dan klien HTTP. Modul ini menyediakan API tingkat rendah untuk menangani permintaan dan respons HTTP tanpa memerlukan dependensi eksternal. Ini merupakan bagian dari inti Node.js, sehingga tersedia di setiap instalasi Node.js.

Bagaimana cara membuat server web sederhana di Node.js?

Buat server menggunakan http.createServer() dengan fungsi pendengar permintaan:

const http = require('http');

const server = http.createServer((req, res) => {
    res.writeHead(200);
    res.end('Hello World');
});

server.listen(8000);

Ini membuat server yang mendengarkan pada port 8000 dan merespons semua permintaan dengan "Hello World".

Apa perbedaan antara modul HTTP dan Express.js?

Modul http adalah fungsi inti Node.js yang menyediakan penanganan HTTP tingkat rendah. Express.js adalah kerangka kerja yang dibangun di atas http yang menambahkan routing, middleware, templating, dan kemudahan lainnya. Modul http memberi Anda kontrol penuh tetapi membutuhkan pengaturan manual lebih banyak, sementara Express.js menyediakan abstraksi dengan menambah ketergantungan tambahan.

Gunakan modul http ketika:

  • Membangun API atau mikroservis yang ringan
  • Belajar dasar-dasar HTTP
  • Anda menginginkan nol dependensi
  • Kontrol penuh atas penanganan permintaan/respons

Gunakan Express.js ketika:

  • Membangun aplikasi kompleks dengan banyak rute
  • Anda membutuhkan ekosistem middleware
  • Prototipe cepat adalah prioritas
  • Anda menginginkan pola yang sesuai standar komunitas

Untuk pengenalan lengkap tentang Express.js dan bagaimana ia menyederhanakan pengembangan web, lihat panduan kami tentang Dasar-dasar Node.js Express.

Bisakah saya membuat REST API menggunakan Node.js tanpa Express?

Ya, tentu saja. Modul http sudah cukup untuk membangun REST API. Anda perlu menangani secara manual:

  • Parsing rute (menggunakan req.url dan req.method)
  • Parsing tubuh permintaan (mengumpulkan potongan aliran)
  • Pemformatan respons
  • Penanganan kesalahan

Untuk API sederhana, ini seringkali lebih disukai daripada menambahkan ketergantungan framework. Lihat Langkah 4 dan 5 dalam tutorial ini untuk contoh routing dan penanganan permintaan POST.

Bagaimana cara menangani berbagai rute menggunakan modul HTTP Node.js?

Parse req.url dan gunakan logika kondisional atau pernyataan switch:

const requestListener = (req, res) => {
    switch (req.url) {
        case '/':
            res.writeHead(200);
            res.end('Home page');
            break;
        case '/about':
            res.writeHead(200);
            res.end('About page');
            break;
        default:
            res.writeHead(404);
            res.end('Not found');
    }
};

Untuk routing yang lebih kompleks, Anda dapat mem-parsing jalur URL, string kueri menggunakan modul url, dan menangani berbagai metode HTTP.

Bagaimana cara mengirim respons HTML atau JSON dengan modul HTTP?

Tetapkan header Content-Type dengan tepat dan kirim datanya:

HTML:

res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
res.end('<h1>Hello</h1>');

JSON:

res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(JSON.stringify({ message: 'Hello' }));

Header Content-Type memberitahu klien bagaimana cara menafsirkan data respons.

Port berapa yang sebaiknya saya gunakan untuk server Node.js lokal?

Port pengembangan yang umum adalah:

  • 8000 atau 8080: Pengembangan HTTP umum
  • 3000: Populer dengan framework seperti Express
  • 5000: Juga umum

Port apa pun di atas 1024 dapat digunakan oleh pengguna non-root. Port di bawah 1024 memerlukan hak administrator. Periksa ketersediaannya dengan lsof -i :PORT (macOS/Linux) sebelum digunakan.

Bagaimana saya bisa menangani error 404 di server HTTP Node.js?

Tambahkan kasus default dalam logika pengaturan rute Anda:

const requestListener = (req, res) => {
    if (req.url === '/') {
        res.writeHead(200);
        res.end('Home');
    } else {
        res.writeHead(404);
        res.end('Not Found');
    }
};

Selalu atur kode status ke 404 untuk rute yang tidak ada agar sesuai dengan standar HTTP.

Bagaimana saya menyajikan file statis tanpa Express?

Gunakan modul fs untuk membaca berkas dan modul path untuk resolusi jalur yang aman:

const fs = require('fs').promises;
const path = require('path');

const requestListener = async (req, res) => {
    const filePath = path.join(__dirname, 'public', req.url);
    try {
        const data = await fs.readFile(filePath);
        res.writeHead(200);
        res.end(data);
    } catch (error) {
        res.writeHead(404);
        res.end('File not found');
    }
};

Selalu validasi jalur untuk mencegah serangan traversal direktori (lihat Langkah 6 untuk implementasi yang aman).

Bisakah saya menggunakan ES Modules (import) dengan modul HTTP di Node.js?

Ya, Node.js mendukung ES Modules. Gunakan salah satu:

  1. File dengan ekstensi .mjs:
import http from 'http';
// ... rest of code
  1. Atur "type": "module" di package.json, lalu gunakan file .js:
{
  "type": "module"
}

Perlu dicatat bahwa ES Modules tidak memiliki __dirname—turunkan dari import.meta.url seperti yang ditunjukkan pada Langkah 8.

Bagaimana cara saya menguraikan isi permintaan POST dengan modul HTTP?

Body POST datang sebagai aliran. Kumpulkan bagian-bagiannya, lalu parsing:

const parseBody = (req) => {
    return new Promise((resolve, reject) => {
        let body = '';
        req.on('data', chunk => body += chunk.toString());
        req.on('end', () => {
            try {
                resolve(JSON.parse(body)); // For JSON
            } catch (error) {
                reject(error);
            }
        });
        req.on('error', reject);
    });
};

Polanya ini bekerja untuk JSON, data formulir, atau jenis konten berbasis teks apa pun.

Apakah modul HTTP cocok untuk digunakan dalam produksi?

Ya, modul http siap untuk produksi dan digunakan oleh banyak aplikasi Node.js. Namun, pertimbangkan:

  • Manajemen proses: Gunakan PM2 atau yang serupa untuk restart otomatis
  • Reverse proxy: Jalankan di belakang Nginx/Apache untuk SSL dan penyeimbangan beban
  • Penanganan kesalahan: Terapkan penanganan kesalahan dan pencatatan secara menyeluruh
  • Keamanan: Validasi input, bersihkan jalur, tetapkan batas ukuran pada permintaan
  • Kinerja: Cache file statis, gunakan clustering untuk operasi yang terbatas oleh CPU

Banyak aplikasi produksi menggunakan modul http secara langsung, terutama microservices dan API ringan. Untuk panduan lengkap tentang penyebaran produksi, lihat Cara Menyiapkan Aplikasi Node.js untuk Produksi di Ubuntu 22.04.

Kesimpulan

Dalam tutorial ini, Anda telah membangun server HTTP Node.js yang siap produksi dari awal. Anda memulai dengan respons teks dasar, kemudian berkembang melalui penyajian berbagai jenis konten (JSON, CSV, dan HTML). Anda menerapkan routing untuk menangani URL yang berbeda, memparsing isi permintaan POST, menyajikan file statis dengan aman, dan menambahkan penanganan kesalahan serta pencatatan yang komprehensif. Anda juga telah mengeksplorasi sintaks CommonJS dan ES Module, serta mempertimbangkan penerapan di lingkungan produksi.

Sekarang Anda siap untuk membuat server web yang menangani berbagai kebutuhan tanpa ketergantungan pada kerangka kerja. Dengan pengetahuan ini, Anda dapat:

  • Bangun API REST ringan untuk mikroservis
  • Melayani situs web multi-halaman dengan pengaturan rute yang tepat
  • Tangani unggahan dan pengunduhan file
  • Terapkan logika middleware kustom
  • Buat keputusan yang tepat tentang kapan kerangka kerja memberikan nilai versus menambah kompleksitas yang tidak perlu

Langkah selanjutnya:

  • Pelajari lebih dalam dasar-dasar Node.js: Kembali ke seri Cara Mengoding di Node.js untuk pembahasan yang lebih lengkap
  • Jelajahi dokumentasi resmi: Dokumentasi modul HTTP Node.js berisi referensi API yang detail dan kasus penggunaan lanjutan
  • Ketika Anda membutuhkan fitur framework: Pertimbangkan dasar-dasar Express.js untuk pengembangan cepat dengan dukungan middleware dan templating

Memahami dasar-dasar HTTP ini membuat Anda menjadi pengembang Node.js yang lebih cakap. Apakah Anda memilih untuk menggunakan framework atau bekerja langsung dengan modul http, sekarang Anda memahami mekanisme dasar yang mendukung semua aplikasi web Node.js. Pengetahuan ini membantu Anda memperbaiki masalah, mengoptimalkan kinerja, dan merancang sistem yang dapat berkembang dengan efektif.

Terima kasih telah belajar bersama Komunitas Linux-Console.net.