Cara Membuat Pelayan Web dalam Node.js dengan Modul HTTP


Pengenalan

Apabila anda melihat laman web dalam pelayar anda, anda sedang membuat permintaan kepada komputer lain di internet, yang kemudian memberikan anda laman web itu sebagai balasan. Komputer yang anda berhubung melalui internet itu adalah pelayan web. Pelayan web menerima permintaan HTTP daripada klien, seperti pelayar anda, dan memberikan respons HTTP, seperti halaman HTML atau JSON daripada API.

Modul Node.js http menyediakan segala yang anda perlukan untuk membina pelayan web tanpa kebergantungan luaran. Ia mengendalikan permintaan yang masuk, menganalisis tajuk HTTP, menguruskan sambungan, dan menghantar respons—semuanya melalui API JavaScript yang mudah.

Modul http adalah penyelesaian terbina dalam Node.js untuk membuat pelayan dan klien HTTP. Berbeza dengan rangka kerja seperti Express.js, ia adalah sebahagian daripada teras Node.js, bermakna tiada pemasangan atau kebergantungan diperlukan. Ini menjadikannya sesuai untuk aplikasi ringan, mempelajari asas-asas HTTP, atau apabila anda ingin kawalan penuh ke atas pengendalian permintaan.

Banyak perisian terlibat supaya pelayan dapat memulangkan halaman web. Perisian ini biasanya tergolong dalam dua kategori: frontend dan backend. Kod frontend berkaitan dengan bagaimana kandungan dipersembahkan, seperti warna bar navigasi dan gaya teks. Kod backend berkaitan dengan bagaimana data dipertukarkan, diproses, dan disimpan. Kod yang mengurus permintaan rangkaian dari pelayar anda atau berkomunikasi dengan pangkalan data kebanyakannya diuruskan oleh kod backend.

Node.js membolehkan pembangun menggunakan JavaScript untuk menulis kod belakang, walaupun secara tradisinya ia digunakan di pelayar untuk menulis kod hadapan. Menggabungkan kedua-dua bahagian hadapan dan belakang seperti ini mengurangkan usaha yang diperlukan untuk membuat pelayan web, yang merupakan salah satu sebab utama mengapa Node.js menjadi pilihan popular untuk menulis kod belakang.

Tutorial ini menunjukkan cara untuk membina pelayan web yang sedia untuk produksi menggunakan modul http yang disertakan dalam Node.js. Anda akan membina pelayan yang mengendalikan routing, menguraikan badan permintaan, menyajikan fail statik, melaksanakan pengendalian ralat yang betul, dan bekerja dengan kedua-dua sintaks CommonJS dan ES Module.

Permudahkan penyebaran aplikasi Node.js dengan Platform Aplikasi Linux-Console.net. Sebarkan Node terus dari GitHub dalam beberapa minit.

Kesimpulan Utama

Sebelum menyelam lebih dalam, ini yang akan anda capai:

  • Cipta pelayan HTTP menggunakan modul teras http Node.js tanpa rangka kerja:
    Anda akan mempelajari cara menubuhkan pelayan web HTTP yang berfungsi sepenuhnya menggunakan hanya modul teras http Node.js. Ini bermakna mengelakkan kebergantungan luaran atau rangka kerja, memberi anda pemahaman yang lebih mendalam mengenai bagaimana pelayan berfungsi pada tahap rendah, serta membolehkan pelaksanaan yang lebih ringan dan fleksibel untuk kes penggunaan anda.

  • Mengendalikan pelbagai jenis kandungan (JSON, HTML, CSV, fail statik) dengan pengepala yang betul:
    Anda akan mempelajari cara bekerja dengan pelbagai jenis kandungan, memberikan respons dengan HTML untuk laman web, JSON untuk API, fail CSV, atau menyediakan aset statik seperti imej dan skrip. Anda akan memahami cara menetapkan pengepala HTTP seperti Content-Type dengan betul, memastikan pelayar dan klien menafsirkan respons anda dengan tepat untuk pengalaman pengguna yang lancar.

  • Laksanakan routing menggunakan parsing URL permintaan dan pernyataan switch:
    Routing adalah asas bagi pelayan web, membolehkan anda menyediakan logik berbeza untuk URL dan titik akhir yang berbeza. Anda akan melihat teknik untuk memparsing URL permintaan masuk dan menyusun logik pelayan anda (contohnya, dengan pernyataan switch) untuk memberikan respons tersuai berdasarkan laluan dan kaedah HTTP, meniru asas-asas apa yang dilakukan oleh rangka kerja yang lebih besar di belakang tabir.

  • Proses permintaan POST dengan menganalisis badan permintaan dan mengendalikan pelbagai jenis kandungan:
    Mengendalikan data yang dihantar oleh pengguna adalah penting untuk mana-mana aplikasi interaktif. Anda akan belajar bagaimana untuk mengendalikan permintaan POST, termasuk membaca dan menganalisis badan permintaan sebagai data mentah, JSON, atau format lain. Bahagian ini merangkumi penampungan data yang masuk, menganalisisnya dengan selamat, dan memberi respons mengikut muatan yang diterima, menyediakan asas untuk interaksi pengguna atau pembangunan API.

  • Hidangkan fail statik dengan cekap dari sistem fail:
    Banyak pelayan web perlu menghidangkan aset statik—imej, helaian gaya, fail JavaScript—secara terus kepada pengguna. Anda akan mengetahui cara membaca fail dari cakera dan menghantarnya dengan cekap melalui HTTP, menguruskan laluan fail dengan bertanggungjawab untuk mengelakkan isu keselamatan, serta meneroka pengoptimuman seperti penstriman fail untuk meningkatkan prestasi bagi fail bersaiz besar.

  • Laksanakan pengendalian ralat dengan kod status HTTP dan log yang sesuai:
    Sebuah pelayan yang mantap mesti mengendalikan ralat dengan baik. Anda akan melihat bagaimana untuk mengesan, merekod, dan memberi respons terhadap ralat—seperti permintaan yang salah atau fail yang hilang—menggunakan kod status HTTP standard seperti 400, 404, dan 500. Anda juga akan mempelajari amalan log asas supaya masalah dapat dijejak dan pengguna menerima mesej ralat yang informatif tanpa mendedahkan maklumat sensitif.

  • Gunakan JavaScript moden dengan kedua-dua sintaks CommonJS (require) dan ES Modules (import):
    Node.js menyokong dua sistem modul: CommonJS dan ES Modules. Anda akan berlatih menggunakan kedua-dua require() dan pernyataan import, memahami bila—dan bagaimana—untuk menggunakan setiap gaya. Ini bukan sahaja memastikan keserasian merentas projek Node.js yang berbeza, tetapi juga membantu anda menulis kod JavaScript yang moden, bersih, dan mudah diselenggara.

  • Amalkan amalan terbaik pengeluaran termasuk caching fail, pemulihan ralat, dan pertimbangan keselamatan:
    Akhir sekali, anda akan menyentuh amalan terbaik penting untuk menyebarkan pelayan web Node.js dalam persekitaran dunia sebenar. Ini termasuk menggunakan caching fail untuk prestasi, melaksanakan strategi pemulihan ralat untuk memastikan pelayan anda tahan lasak, serta mengaplikasikan langkah-langkah keselamatan asas (seperti penyucian input dan tajuk HTTP yang betul) untuk melindungi aplikasi dan pengguna anda.

Memahami Modul HTTP

Modul http adalah antara muka peringkat rendah Node.js untuk komunikasi HTTP. Ia menyediakan:

  • Penciptaan pelayan: http.createServer() mencipta satu instans pelayan HTTP
  • Pengendalian permintaan: Permintaan yang diterima diwakili oleh objek IncomingMessage
  • Pengurusan respons: Respons keluar menggunakan objek ServerResponse
  • Sokongan penstriman: Kedua-dua permintaan dan respons adalah penstriman, membolehkan pemindahan data yang cekap

Mengapa Menggunakan Modul HTTP Daripada Express? Walaupun Express.js menyediakan abstraksi yang mudah, modul http asli memberi anda kawalan penuh, saiz pakej yang lebih kecil, dan pemahaman mendalam tentang asas-asas HTTP. Ia adalah asas yang dibina oleh kerangka kerja. Untuk API mudah, mikroperkhidmatan, atau tujuan pembelajaran, ia seringkali semua yang anda perlukan.

Modul ini digerakkan oleh acara: apabila permintaan tiba, pelayan akan memancarkan acara 'request' dan memanggil fungsi pendengar permintaan anda. Reka bentuk ini membolehkan Node.js mengendalikan banyak sambungan serentak dengan cekap pada satu benang melalui gelung acara.

Konsep asas untuk difahami:

  • I/O tanpa sekatan: Modul http menggunakan gelung acara Node.js untuk mengendalikan pelbagai sambungan tanpa mencipta benang berasingan untuk setiap permintaan. Ini menjadikannya sangat cekap untuk operasi yang bergantung pada I/O.
  • Aliran: Objek permintaan dan respons mewarisi daripada aliran Node.js. Ini bermakna beban data besar boleh diproses secara berperingkat tanpa memuatkan semuanya ke dalam memori—amat penting untuk mengendalikan muat naik fail atau menyajikan fail bersaiz besar.
  • Header dan kod status: Modul ini memberi anda kawalan terus ke atas header HTTP (res.setHeader()) dan kod status (res.writeHead()), membolehkan semantik HTTP yang tepat.
  • Tiada penghalaan magik: Tidak seperti rangka kerja, tiada penghalaan terbina dalam. Anda perlu menguraikan req.url dan req.method secara manual untuk menentukan bagaimana untuk memberi respons—memberi ketelusan sepenuhnya tentang bagaimana penghalaan sebenarnya berfungsi.

Apabila anda memanggil http.createServer(), Node.js mencipta sebuah pelayan TCP di belakang tabir yang mendengar sambungan masuk, menganalisis mesej protokol HTTP, dan mengeluarkan acara yang boleh dikendalikan oleh kod anda. Memahami operasi tahap rendah ini membantu anda menyahpepijat masalah dan membuat keputusan seni bina yang berinformasi.

Prasyarat

  • Pastikan Node.js dipasang pada mesin pembangunan anda. Tutorial ini berfungsi dengan Node.js 20.x atau 22.x LTS (disyorkan). Untuk memasang Node.js pada macOS atau Ubuntu, ikuti langkah-langkah dalam Cara Memasang Node.js dan Mewujudkan Persekitaran Pembangunan Tempatan di macOS atau Cara Memasang Node.js pada Ubuntu.

  • Platform Node.js menyokong penciptaan pelayan web secara langsung. Kefahaman mengenai asas-asas Node.js amat membantu. Semak panduan kami tentang Cara Menulis dan Menjalankan Program Pertama Anda dalam Node.js.

  • Pengaturcaraan tak segerak digunakan dalam beberapa bahagian. Jika anda baru dengan kod async atau modul fs, lihat Cara Menulis Kod Tak Segerak dalam Node.js.

  • Bagi bahagian yang berfokus kepada pengeluaran, kefahaman mengenai pengurusan proses Linux (systemd/PM2) dan proksi terbalik (seperti Nginx) akan membantu ketika menggunakan panduan penyebaran.

Langkah 1 — Mewujudkan Pelayan HTTP Asas

Bina pelayan HTTP yang minimum yang memberi respons kepada sebarang permintaan dengan teks biasa. Asas ini terpakai sama ada anda menggunakan Express atau membina dari awal. Apabila Node.js memanggil http.createServer(), ia mencipta pelayan TCP yang mendengar pada satu port. Setiap sambungan masuk akan mencetuskan fungsi pendengar permintaan anda.

Sediakan folder projek anda:

mkdir first-servers

cd first-servers

Sekarang, cipta fail yang akan menyimpan kod tersebut:

touch hello.js

Buka fail dalam penyunting teks. Kami akan menggunakan nano kerana ia tersedia dalam terminal:

nano hello.js

Kita mula dengan memuatkan modul http yang standard dengan semua pemasangan Node.js. Tambahkan baris berikut ke hello.js:

const http = require("http");

Modul http mengandungi fungsi untuk membuat pelayan, yang akan kita lihat kemudian. Jika anda ingin mengetahui lebih lanjut tentang modul dalam Node.js, lihat artikel Cara Membuat Modul Node.js kami.

Langkah seterusnya adalah untuk menentukan dua pemalar, iaitu hos dan port yang akan digunakan oleh pelayan kami:

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

Seperti yang disebutkan sebelum ini, pelayan web menerima permintaan daripada pelayar dan klien lain. Kita boleh berinteraksi dengan pelayan web dengan memasukkan nama domain, yang diterjemahkan kepada alamat IP oleh pelayan DNS. Alamat IP ialah urutan nombor unik yang mengenal pasti mesin dalam rangkaian, seperti internet. Untuk maklumat lanjut mengenai konsep nama domain, lihat artikel kami Pengenalan kepada Terminologi, Komponen, dan Konsep DNS.

Nilai localhost adalah alamat peribadi khas yang digunakan oleh komputer untuk merujuk kepada dirinya sendiri. Ia biasanya setara dengan alamat IP dalaman 127.0.0.1 dan hanya boleh diakses oleh komputer tempatan, bukan oleh mana-mana rangkaian tempatan yang telah kami sertai atau oleh internet.

Port adalah nombor yang digunakan oleh pelayan sebagai titik akhir atau ‘pintu’ ke alamat IP kita. Dalam contoh kita, kita akan menggunakan port 8000 untuk pelayan web kita. Port 8080 dan 8000 biasanya digunakan sebagai port lalai dalam pembangunan, dan dalam kebanyakan kes pembangun akan menggunakannya berbanding port lain untuk pelayan HTTP.

Apabila kita mengikat pelayan kita kepada hos dan port ini, kita akan dapat mengakses pelayan kita dengan melawat http://localhost:8000 dalam pelayar tempatan.

Mari kita tambah satu fungsi khas, yang dalam Node.js kita panggil pemerhati permintaan. Fungsi ini bertujuan untuk menguruskan permintaan HTTP yang masuk dan mengembalikan tindak balas HTTP. Fungsi ini mesti mempunyai dua argumen, iaitu objek permintaan dan objek tindak balas. Objek permintaan menangkap semua data permintaan HTTP yang masuk. Objek tindak balas digunakan untuk mengembalikan tindak balas HTTP bagi pelayan.

Kami mahu pelayan pertama kami memulangkan mesej ini setiap kali seseorang mengaksesnya: "Pelayan pertama saya!".

Mari kita tambah fungsi itu seterusnya:

...

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

Fungsi itu biasanya akan dinamakan berdasarkan apa yang dilakukannya. Sebagai contoh, jika kita mencipta fungsi pendengar permintaan untuk memulangkan senarai buku, kita kemungkinan besar akan menamakannya listBooks(). Oleh kerana ini adalah kes contoh, kita akan menggunakan nama generik requestListener.

Semua fungsi pendengar permintaan dalam Node.js menerima dua hujah: req dan res (kita boleh menamakannya dengan cara yang berbeza jika mahu). Permintaan HTTP yang dihantar oleh pengguna ditangkap dalam objek Request, yang sepadan dengan hujah pertama, req. Respons HTTP yang kita kembalikan kepada pengguna dibentuk dengan berinteraksi dengan objek Response pada hujah kedua, res.

Baris pertama res.writeHead(200); menetapkan kod status HTTP bagi respons tersebut. Kod status HTTP menunjukkan sejauh mana permintaan HTTP ditangani oleh pelayan. Dalam kes ini, kod status 200 bersamaan dengan "OK". Jika anda berminat untuk mempelajari tentang pelbagai kod HTTP yang boleh dikembalikan oleh pelayan web anda beserta maksudnya, panduan kami tentang Cara Menyelesaikan Masalah Kod Ralat HTTP Biasa adalah tempat yang baik untuk bermula.

Baris seterusnya fungsi, res.end("My first server!");, menulis respons HTTP kembali kepada klien yang memintanya. Fungsi ini mengembalikan apa-apa data yang perlu dikembalikan oleh pelayan. Dalam kes ini, ia mengembalikan data teks.

Akhirnya, kita kini boleh membuat pelayan kita dan menggunakan pendengar permintaan kita:

...

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

Simpan dan keluar nano dengan menekan CTRL+X.

Dalam baris pertama, kita mencipta objek server baru melalui fungsi createServer() modul http. Server ini menerima permintaan HTTP dan menyampaikannya kepada fungsi requestListener() kita.

Selepas kita membuat pelayan kita, kita mesti mengikatnya ke alamat rangkaian. Kita melakukan itu dengan kaedah server.listen(). Ia menerima tiga argumen: port, host, dan fungsi panggilan balik yang dijalankan apabila pelayan mula mendengar.

Semua hujah ini adalah pilihan, tetapi adalah idea yang baik untuk menyatakan dengan jelas port dan hos yang ingin kita gunakan oleh pelayan web. Apabila menyebarkan pelayan web ke persekitaran yang berbeza, mengetahui port dan hos yang sedang digunakan adalah diperlukan untuk menetapkan keseimbangan beban atau alias DNS.

Fungsi panggilan balik mencatat mesej ke konsol kami supaya kami boleh mengetahui bila pelayan mula mendengar sambungan.

Berikut ialah fail lengkap hello.js dengan komen dalaman yang menerangkan setiap bahagian:

// 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}`);
});

Kod ini mencipta pelayan HTTP yang berfungsi dalam kurang daripada 20 baris. Setiap kali klien menyambung—tidak kira URL atau kaedah HTTP—pelayan akan membalas dengan “Pelayan pertama saya!” dan kod status 200.

Nota: Walaupun requestListener() tidak menggunakan objek req, ia masih perlu menjadi argumen pertama bagi fungsi tersebut.

Dengan kurang daripada lima belas baris kod, kita kini mempunyai pelayan web. Mari kita lihat ia berfungsi dan uji secara menyeluruh dengan menjalankan program ini:

node hello.js

Di konsol, kita akan melihat output ini:

Server is running on http://localhost:8000

Perhatikan bahawa arahan hilang. Ini kerana pelayan Node.js adalah proses yang berjalan lama. Ia hanya berhenti jika menghadapi ralat yang menyebabkan ia terhenti secara tiba-tiba, atau jika kita menghentikan proses Node.js yang menjalankan pelayan tersebut.

Dalam tetingkap terminal yang berasingan, kita akan berkomunikasi dengan pelayan menggunakan cURL, alat CLI untuk memindahkan data ke dan dari rangkaian. Masukkan arahan untuk membuat permintaan HTTP GET ke pelayan yang sedang berjalan:

curl http://localhost:8000

Apabila kita menekan ENTER, terminal kita akan menunjukkan output berikut:

My first server!

Kami kini telah menubuhkan server dan menerima maklum balas server pertama kami.

Mari kita pecahkan apa yang berlaku apabila kami menguji pelayan kami. Menggunakan cURL, kami menghantar permintaan GET ke pelayan di http://localhost:8000. Pelayan Node.js kami mendengar sambungan dari alamat tersebut. Pelayan menghantar permintaan itu ke fungsi requestListener(). Fungsi itu memulangkan data teks dengan kod status 200. Pelayan kemudian menghantar balasan itu kembali ke cURL, yang memaparkan mesej tersebut di terminal kami.

Dalam pengeluaran, anda biasanya tidak mengikat ke localhost—anda mengikat ke 0.0.0.0 untuk menerima sambungan dari mana-mana antara muka rangkaian. Walau bagaimanapun, untuk pembangunan tempatan, localhost lebih selamat dan menghalang akses luaran ke pelayan pembangunan anda. Juga, jika port 8000 sudah digunakan, anda akan mendapat ralat EADDRINUSE—cuma pilih port lain atau hentikan proses yang menggunakan port itu dengan lsof -i :8000 (macOS/Linux).

Sebelum kita meneruskan, mari keluar dari server yang sedang berjalan dengan menekan CTRL+C. Ini akan menghentikan pelaksanaan server kita, membawa kita kembali ke prompt baris arahan.

Dalam kebanyakan laman web yang kita lawati atau API yang kita gunakan, respons pelayan jarang dalam teks biasa. Kita menerima halaman HTML dan data JSON sebagai format respons biasa. Dalam langkah berikutnya, kita akan belajar bagaimana untuk mengembalikan respons HTTP dalam format data biasa yang kita temui di web.

Jika anda ingin belajar tentang membuat permintaan HTTP dari Node.js (pihak klien), lihat panduan kami tentang Cara Membuat Klien HTTP dengan HTTP Teras di Node.js.

Langkah 2 — Mengembalikan Pelbagai Jenis Kandungan

Tindak balas yang kita kembalikan daripada pelayan web boleh mengambil pelbagai format. JSON dan HTML telah disebut sebelum ini, dan kita juga boleh mengembalikan format teks lain seperti XML dan CSV. Akhir sekali, pelayan web boleh mengembalikan data bukan teks seperti PDF, fail zip, audio, dan video.

Dalam artikel ini, selain daripada teks biasa yang baru sahaja kami kembalikan, anda akan belajar bagaimana untuk mengembalikan jenis data berikut:

  • JSON
  • CSV
  • HTML

Ketiga-tiga jenis data ini semuanya berasaskan teks, dan merupakan format yang popular untuk menyampaikan kandungan di web. Banyak bahasa dan alat pembangunan sisi-server menyokong pengembalian jenis data yang berbeza ini. Dalam konteks Node.js, kita perlu melakukan dua perkara:

  1. Tetapkan header Content-Type dalam respons HTTP kami dengan nilai yang sesuai.
  2. Pastikan res.end() menerima data dalam format yang betul.

Mari kita lihat ini dalam tindakan dengan beberapa contoh. Kod yang akan kita tulis dalam bahagian ini dan bahagian seterusnya mempunyai banyak persamaan dengan kod yang kita tulis sebelum ini. Kebanyakan perubahan wujud dalam fungsi requestListener(). Mari kita buat fail dengan "kod templat" ini untuk memudahkan bahagian-bahagian akan datang diikuti.

Buat fail baru yang dipanggil html.js. Fail ini akan digunakan kemudian untuk mengembalikan teks HTML dalam respons HTTP. Kami akan meletakkan kod templat di sini dan menyalinnya ke pelayan lain yang mengembalikan pelbagai jenis.

Di terminal, masukkan yang berikut:

touch html.js

Sekarang buka fail ini dalam penyunting teks:

nano html.js

Mari salin “kod templat.” Masukkan ini dalam 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 html.js dengan CTRL+X, kemudian kembali ke terminal.

Sekarang mari salin fail ini ke dalam dua fail baru. Fail pertama akan digunakan untuk memulangkan data CSV dalam respon HTTP:

cp html.js csv.js

Fail kedua akan memulangkan respons JSON di pelayan:

cp html.js json.js

Fail yang tinggal akan digunakan untuk latihan kemudian:

cp html.js htmlFile.js

cp html.js routes.js

Kita kini bersedia untuk meneruskan latihan kita. Mari kita mulakan dengan memulangkan JSON.

Menyajikan JSON

JavaScript Object Notation, yang biasa dirujuk sebagai JSON, adalah format pertukaran data berasaskan teks. Seperti namanya, ia berasal daripada objek JavaScript, tetapi ia bebas bahasa, bermakna ia boleh digunakan oleh mana-mana bahasa pengaturcaraan yang boleh menafsir sintaksnya.

JSON biasanya digunakan oleh API untuk menerima dan mengembalikan data. Popularitinya disebabkan oleh saiz pemindahan data yang lebih kecil berbanding piawaian pertukaran data sebelumnya seperti XML, serta alat yang wujud yang membolehkan program memprosesnya tanpa usaha yang berlebihan. Jika anda ingin mengetahui lebih lanjut mengenai JSON, anda boleh membaca panduan kami tentang Cara Bekerja dengan JSON dalam JavaScript.

Buka fail json.js dengan nano:

nano json.js

Apabila menghantar JSON, anda mesti menetapkan header Content-Type kepada application/json. Tanpanya, pelayar dan klien API akan menganggap respons sebagai teks biasa, menyebabkan fetch().json() gagal. Tetapkan header sebelum memanggil writeHead()—sekali header dihantar, ia tidak boleh diubah.

Kesilapan biasa: Memanggil res.writeHead(200) sebelum res.setHeader() akan mengunci header. Jika anda cuba menetapkan header selepas writeHead(), ia akan diabaikan. Sentiasa 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"}`);
};
...

Kod status 200 menunjukkan kejayaan. Badan respons mengandungi JSON yang sah.

Mengapa menggunakan JSON.stringify() dan bukannya menulis string secara manual? Menulis JSON secara manual mudah menyebabkan kesilapan. Jika mesej anda mengandungi petikan, garis baru, atau aksara khas, anda perlu melarikan mereka secara manual. JSON.stringify() menguruskan ini secara automatik:

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));

Berhati-hati dengan rujukan berulang: Jika objek anda merujuk kepada dirinya sendiri, JSON.stringify() akan menghasilkan ralat: TypeError: Converting circular structure to JSON. Ini sering berlaku dengan keputusan pertanyaan pangkalan data. Sentiasa sahkan struktur data anda.

Apa yang berlaku jika anda cuba menghantar objek secara terus? Modul http hanya menerima string atau Buffer. Jika anda cuba res.end({ message: "test" }), Node.js akan membuang ralat: TypeError [ERR_INVALID_ARG_TYPE]: Argumen pertama mesti salah satu daripada jenis string atau Buffer. Anda mesti menukar objek kepada string JSON terlebih dahulu.

Sahkan bahawa pengepala telah ditetapkan dengan betul: Gunakan flag -i dengan curl untuk melihat pengepala respons:

curl -i http://localhost:8000

Anda sepatutnya melihat Content-Type: application/json dalam header. Jika anda melihat text/plain atau tiada Content-Type, anda sama ada memanggil setHeader() selepas writeHead() atau terlupa sama sekali.

Senario debugging biasa: Jika JavaScript frontend anda mendapat undefined semasa memanggil fetch('/api').then(r => r.json()), semak header menggunakan curl -i. Content-Type mesti tepat application/json (peka huruf untuk beberapa parser yang tegas).

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

node json.js

Di terminal lain, mari capai pelayan dengan menggunakan cURL:

curl http://localhost:8000

Apabila kita menekan ENTER, kita akan melihat hasil berikut:

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

Kini kami telah berjaya mengembalikan respons JSON, sama seperti banyak API popular yang kami gunakan untuk membuat aplikasi. Pastikan untuk keluar dari server yang sedang berjalan dengan CTRL+C supaya kami dapat kembali ke prompt terminal standard. Seterusnya, mari kita lihat format lain yang popular untuk mengembalikan data: CSV.

Menyediakan CSV

Format fail Comma Separated Values (CSV) adalah piawaian teks yang biasanya digunakan untuk menyediakan data berbentuk jadual. Dalam kebanyakan kes, setiap baris dipisahkan oleh baris baharu, dan setiap item dalam baris dipisahkan oleh koma.

Di ruang kerja kami, buka fail csv.js dengan penyunting teks:

nano csv.js

Mari kita tambah baris-baris berikut ke dalam 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 bahawa fail CSV sedang dikembalikan kerana nilainya adalah text/csv. Header kedua yang kami tambah ialah Content-Disposition. Header ini memberitahu pelayar bagaimana untuk memaparkan data, terutamanya di dalam pelayar atau sebagai fail berasingan.

Apabila kami mengembalikan respons CSV, kebanyakan pelayar moden secara automatik memuat turun fail tersebut walaupun header Content-Disposition tidak ditetapkan. Walau bagaimanapun, apabila mengembalikan fail CSV, kita masih perlu menambah header ini kerana ia membolehkan kita menetapkan nama fail CSV tersebut. Dalam kes ini, kita memberi isyarat kepada pelayar bahawa fail CSV ini adalah lampiran dan perlu dimuat turun. Kemudian kita memberitahu pelayar bahawa nama fail itu ialah oceanpals.csv.

Mari 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. Tanda koma memisahkan nilai dalam setiap lajur dan aksara baris baru (\n) memisahkan baris. Kami mempunyai dua baris: baris tajuk dan satu baris data.

Peringatan format CSV: Contoh mudah di atas berfungsi, tetapi penjanaan CSV untuk produksi memerlukan pelarian (escaping). Jika data anda mengandungi koma (seperti "Smith, John") atau tanda petik, ia akan merosakkan format CSV. Pelarian CSV yang betul membalut nilai dengan tanda petik dan menggandakan tanda petik di dalamnya: "Smith, John" menjadi "Smith, ""John""" dalam CSV. Untuk penggunaan produksi, gunakan pustaka seperti csv-stringify atau lakukan pelarian nilai dengan betul untuk mengelakkan ralat parse.

Apa yang berlaku tanpa Content-Disposition? Kebanyakan pelayar memuat turun fail jika Content-Type: text/csv ditetapkan, tetapi nama fail mungkin menjadi laluan URL atau sesuatu yang generik. Header attachment;filename=oceanpals.csv memastikan pelayar menyimpannya dengan nama tertentu. Tanpa attachment, sesetengah pelayar cuba memaparkan CSV secara terus, yang selalunya kelihatan berselerak.

Kita akan menguji pelayan ini di pelayar. Simpan csv.js dan keluar dari penyunting dengan CTRL+X.

Jalankan pelayan dengan arahan Node.js:

node csv.js

Di Terminal lain, mari capai pelayan dengan menggunakan cURL:

curl http://localhost:8000

Konsol akan menunjukkan ini:

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

Jika kita pergi ke http://localhost:8000 di pelayar kami, satu fail CSV akan dimuat turun. Nama failnya ialah oceanpals.csv.

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

Setelah mengembalikan JSON dan CSV, kita telah merangkumi dua kes yang popular untuk API. Mari kita teruskan kepada cara kita mengembalikan data untuk laman web yang dilihat orang dalam pelayar.

Menyajikan HTML

HTML (HyperText Markup Language) ialah format yang paling biasa digunakan apabila kita mahu pengguna berinteraksi dengan server kita melalui pelayar web. Ia dicipta untuk menyusun kandungan web. Pelayar web dibina untuk memaparkan kandungan HTML, serta sebarang gaya yang kita tambah dengan CSS, satu lagi teknologi web hadapan yang membolehkan kita mengubah estetika laman web kita.

Mari kita buka semula html.js dengan penyunting teks kita:

nano html.js

Ubah suai fungsi requestListener() untuk mengembalikan pengepala Content-Type yang sesuai bagi respons HTML:

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

Sekarang, mari kita kembalikan kandungan HTML kepada pengguna. Tambahkan baris yang disorot ke html.js supaya ia kelihatan 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>`);
};
...

Kita mula-mula menambah kod status HTTP. Kemudian kita memanggil response.end() dengan argumen string yang mengandungi HTML yang sah. Apabila kita mengakses pelayan kita di pelayar, kita akan melihat halaman HTML dengan satu tag tajuk yang mengandungi This is HTML.

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

node html.js

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

Sekarang buka pelayar dan lawati http://localhost:8000. Halaman kita akan kelihatan seperti ini:

Mari hentikan pelayan yang sedang berjalan dengan CTRL+C dan kembali ke prompt terminal standard.

Adalah biasa untuk HTML ditulis dalam fail, berasingan daripada kod sisi pelayan seperti program Node.js kita. Seterusnya, mari kita lihat bagaimana kita boleh memulangkan respons HTML daripada fail.

Langkah 3 — Menyajikan Laman HTML Dari Fail

Kita boleh menyajikan HTML sebagai rentetan dalam Node.js kepada pengguna, tetapi adalah lebih baik jika kita memuatkan fail HTML dan menyajikan kandungannya. Dengan cara ini, apabila fail HTML berkembang, kita tidak perlu mengekalkan rentetan yang panjang dalam kod Node.js kita, menjadikannya lebih ringkas dan membolehkan kita bekerja pada setiap aspek laman web kita secara bebas. “Pemecahan tanggungjawab” ini biasa dalam banyak set up pembangunan web, jadi adalah baik untuk mengetahui cara memuatkan fail HTML untuk menyokongnya dalam Node.js

Untuk menyajikan fail HTML, kami memuatkan fail HTML dengan modul fs dan menggunakan datanya semasa menulis respons HTTP kami.

Pertama, kita akan membuat fail HTML yang akan dikembalikan oleh pelayan web. Buat fail HTML baru:

touch index.html

Sekarang buka index.html dalam penyunting teks:

nano index.html

Laman web kami akan ringkas. Ia akan mempunyai latar belakang oren dan akan memaparkan beberapa teks ucapan di tengah. Tambahkan kod ini ke dalam fail:

<!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 menunjukkan dua baris teks: Hello Again! dan This is served from a file. Baris-baris ini muncul di tengah halaman, satu di atas yang lain. Baris pertama teks dipaparkan dalam tajuk, bermakna ia akan besar. Baris kedua teks akan muncul sedikit lebih kecil. Semua teks akan berwarna putih dan halaman web mempunyai latar belakang oren.

Walaupun ini bukan skop artikel atau siri ini, jika anda berminat untuk mempelajari lebih lanjut tentang HTML, CSS, dan teknologi web front-end yang lain, anda boleh melihat panduan Memulakan dengan Web daripada Mozilla.

Itu sahaja yang kita perlukan untuk HTML, jadi simpan dan keluar dari fail dengan CTRL+X. Kita kini boleh beralih kepada kod server.

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

nano htmlFile.js

Oleh kerana kita perlu membaca fail, mari kita mulakan dengan mengimport modul fs:

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

Modul ini mengandungi fungsi readFile() yang akan kami gunakan untuk memuatkan fail HTML di tempatnya. Kami mengimport versi janji (promise) selaras dengan amalan terbaik JavaScript moden. Kami menggunakan janji kerana secara sintaksis ia lebih ringkas daripada panggilan balik (callbacks), yang perlu kami gunakan jika kami menetapkan fs hanya kepada require('fs'). Untuk mengetahui lebih lanjut tentang amalan terbaik pengaturcaraan tak segerak, anda boleh membaca panduan kami Cara Menulis Kod Tak Segerak (Asynchronous) dalam Node.js.

Kita mahu fail HTML kita dibaca apabila pengguna membuat permintaan kepada sistem kita. Mari kita mulakan dengan mengubah suai requestListener() untuk membaca fail tersebut:

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

Kami menggunakan kaedah fs.readFile() untuk memuatkan fail. Argumennya mempunyai __dirname + "/index.html". Pembolehubah khas __dirname mempunyai laluan mutlak di mana kod Node.js dijalankan. Kami kemudian menambahkan /index.html supaya kami dapat memuatkan fail HTML yang telah kami buat sebelum ini.

Sekarang mari kembalikan halaman HTML setelah ia 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() berjaya diselesaikan, ia akan mengembalikan datanya. Kita menggunakan kaedah then() untuk mengendalikan kes ini. Parameter contents mengandungi data fail HTML tersebut.

Kami mula-mula menetapkan header Content-Type kepada text/html untuk memberitahu klien bahawa kami mengembalikan data HTML. Kemudian kami menulis kod status untuk menunjukkan bahawa permintaan berjaya. Akhirnya, kami menghantar halaman HTML yang dimuatkan kepada klien, dengan data dalam pembolehubah contents.

Kaedah fs.readFile() kadang-kadang boleh gagal, jadi kita harus mengendalikan keadaan ini apabila kita mendapat ralat. Tambahkan ini ke dalam 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 fail dan keluar dari nano dengan CTRL+X.

Apabila satu janji menghadapi ralat, ia ditolak. Kami mengendalikan kes tersebut dengan kaedah catch(). Ia menetapkan kod status kepada 500 yang menandakan bahawa ralat dalaman telah berlaku, dan menghantar mesej ralat mesra pengguna. Dalam pengeluaran, anda tidak seharusnya mendedahkan butiran ralat mentah kepada pengguna, kerana ini boleh menyalurkan maklumat sensitif.

Jalankan pelayan kami dengan arahan node:

node htmlFile.js

Dalam pelayar web, lawati http://localhost:8000. Anda akan melihat halaman ini:

Anda kini telah memulangkan halaman HTML dari pelayan kepada pengguna. Anda boleh menghentikan pelayan yang sedang berjalan dengan CTRL+C. Anda akan melihat prompt terminal kembali apabila anda melakukannya.

Apabila menulis kod seperti ini dalam pengeluaran, anda mungkin tidak ingin memuatkan halaman HTML setiap kali anda mendapat permintaan HTTP. Walaupun halaman HTML ini saiznya kira-kira 800 bait, laman web yang lebih kompleks boleh mencapai saiz megabait. Fail besar boleh mengambil masa untuk dimuatkan. Jika laman anda menjangkakan lalu lintas yang tinggi, mungkin lebih baik memuatkan fail HTML semasa permulaan dan menyimpan kandungannya. Selepas ia dimuatkan, anda boleh menyiapkan pelayan dan membuatnya mendengar permintaan di alamat tertentu.

Untuk menunjukkan kaedah ini, mari kita lihat bagaimana kita boleh mengubahsuai pelayan kita supaya lebih cekap dan berskala.

Menyajikan HTML dengan Efisien

Daripada memuatkan HTML untuk setiap permintaan, dalam langkah ini kita akan memuatkannya sekali sahaja pada permulaan. Permintaan itu akan mengembalikan data yang kita muatkan ketika permulaan.

Di terminal, buka semula skrip Node.js dengan penyunting teks:

nano htmlFile.js

Mari kita mulakan dengan menambah pembolehubah baru sebelum kita membuat fungsi requestListener():

...
let indexFile;

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

Apabila kita menjalankan program ini, pembolehubah ini akan menyimpan kandungan fail HTML tersebut.

Sekarang, mari kita sesuaikan semula fungsi requestListener(). Daripada memuatkan fail, ia kini akan mengembalikan kandungan indexFile:

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

Seterusnya, kita mengalihkan logik pembacaan fail dari fungsi requestListener() ke permulaan pelayan kita. Lakukan perubahan berikut semasa kita mencipta pelayan:

...

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 fail dan keluar dari nano dengan CTRL+X.

Kod yang membaca fail adalah serupa dengan apa yang kami tulis dalam percubaan pertama kami. Walau bagaimanapun, apabila kami berjaya membaca fail, kami kini menyimpan kandungan tersebut ke dalam pembolehubah global indexFile kami. Kami kemudian memulakan pelayan dengan kaedah listen(). Perkara utama ialah fail dimuatkan sebelum pelayan dijalankan. Dengan cara ini, fungsi requestListener() pasti akan mengembalikan halaman HTML, kerana indexFile tidak lagi merupakan pembolehubah kosong.

Pengendali ralat kami juga telah diubah. Jika fail tidak dapat dimuatkan, kami akan menangkap ralat tersebut dan mencetaknya ke konsol kami. Kami kemudian keluar dari program Node.js menggunakan fungsi exit() tanpa memulakan pelayan. Dengan cara ini kami dapat melihat sebab mengapa pembacaan fail gagal, menyelesaikan masalah tersebut, dan kemudian memulakan semula pelayan.

Kami kini telah mencipta pelayan web yang berbeza yang memulangkan pelbagai jenis data kepada pengguna. Setakat ini, kami belum menggunakan sebarang data permintaan untuk menentukan apa yang harus dipulangkan. Kami perlu menggunakan data permintaan semasa menetapkan laluan atau jalan yang berbeza dalam pelayan Node.js, jadi seterusnya mari kita lihat bagaimana ia berfungsi bersama.

Langkah 4 — Mengurus Laluan Menggunakan Objek Permintaan HTTP

Kebanyakan laman web yang kami lawati atau API yang kami gunakan biasanya mempunyai lebih daripada satu titik hujung supaya kami boleh mengakses pelbagai sumber. Contoh yang baik adalah sistem pengurusan buku, seperti yang mungkin digunakan di perpustakaan. Ia bukan sahaja perlu mengurus data buku, tetapi juga akan mengurus data pengarang untuk memudahkan pengkatalogan dan pencarian.

Walaupun data untuk buku dan pengarang berkaitan, ia adalah dua objek yang berbeza. Dalam kes seperti ini, pembangun perisian biasanya menulis kod untuk setiap objek pada titik akhir yang berbeza sebagai satu cara untuk menunjukkan kepada pengguna API jenis data yang mereka sedang berinteraksi.

Mari kita buat pelayan baharu untuk sebuah perpustakaan kecil, yang akan memulangkan dua jenis data yang berbeza. Jika pengguna mengunjungi alamat pelayan kami di /books, mereka akan menerima senarai buku dalam format JSON. Jika mereka mengunjungi /authors, mereka akan menerima senarai maklumat pengarang dalam format JSON.

Setakat ini, pelayan memberikan respons yang sama untuk setiap permintaan. Untuk menyokong pelbagai sumber, simpan data anda dalam pembolehubah dan gunakan penghalaan bersyarat. Contohnya:

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. Sebarang URL lain akan memulangkan ralat 404.

Masalah dengan pemadanan string mudah: req.url adalah string mentah yang merangkumi segala-galanya selepas domain—laluan, string pertanyaan, dan fragmen. Jika seseorang melawat /books?id=123, case "/books" anda tidak akan sepadan kerana req.url adalah /books?id=123, bukan /books. Parameter pertanyaan boleh mengganggu penghalaan: /books?sort=name tidak akan sepadan dengan /books. Aksi-aksi yang diwakili dalam URL boleh mengganggu penghalaan: /books%20reviews (ruang dikodkan sebagai %20) tidak akan sepadan dengan /books reviews.

Gunakan API URL sebaliknya: API URL secara automatik memisahkan pathname daripada parameter pertanyaan, mengekodkan semula aksara URL, dan mengendalikan kes tepi dengan betul.

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 kaedah HTTP? Laluan yang sama mungkin menyokong kaedah HTTP yang berbeza (GET untuk mendapatkan, POST untuk mencipta). Tanpa pemeriksaan ini, permintaan POST ke /books akan mengembalikan senarai buku dan bukannya mencipta buku baru, yang menyalahi konvensyen REST API. Kod status 405 Method Not Allowed dengan header Allow memberitahu klien kaedah mana yang sah untuk titik hujung ini.

Pengendalian rentetan pertanyaan: API URL secara automatik memisahkan jalan (/books) daripada parameter pertanyaan (?id=123), jadi /books?id=123 akan diarahkan ke titik akhir buku dengan betul sementara anda masih boleh mengakses url.searchParams.get('id') jika perlu.

Uji pelbagai senario:

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 — Mengendalikan Permintaan POST dan Memproses Badan Permintaan

Sejauh ini, kami hanya mengendalikan permintaan GET. Kebanyakan API perlu menerima data daripada klien, yang biasanya berlaku melalui permintaan POST. Modul http memerlukan pemprosesan manual badan permintaan, yang diterima sebagai aliran.

Pandangan kritikal: Tidak seperti permintaan GET di mana data datang melalui URL, badan permintaan POST tiba sebagai aliran. Ini bermakna data tidak tiba sekaligus—ia datang dalam beberapa bahagian. Jika anda cuba memproses JSON sebelum semua bahagian tiba, anda akan mendapat data yang tidak lengkap atau rosak. Anda mesti mengumpul semua bahagian terlebih dahulu, kemudian memprosesnya.

Ini yang akan berlaku jika anda cuba menganalisis terlalu awal:

// 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 pelayan yang menerima permintaan POST dengan muatan JSON. Buat fail baru:

touch post.js

cp routes.js post.js

Buka post.js:

nano post.js

Berikut adalah parser badan JSON yang sedia untuk pengeluaran dan menangani kebimbangan keselamatan:

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' }));
    });
  });
}

Kenapa periksa Content-Type? Jika klien menghantar data borang dengan Content-Type: application/x-www-form-urlencoded, cuba memparsenya sebagai JSON akan gagal. Kod status 415 Unsupported Media Type memberitahu klien bahawa Content-Type mereka tidak diterima.

Mengapa mengehadkan saiz badan? Tanpa had, klien berniat jahat boleh menghantar data berbilang gigabait, menghabiskan memori pelayan anda. Respons 413 Payload Too Large dan req.destroy() menghentikan sambungan segera apabila had dilampaui.

Mengapa mengesahkan hasil yang diparse? JSON.parse('"hello"') berjaya tetapi mengembalikan string, bukan objek. Kod anda mungkin menjangkakan data.property berfungsi, tetapi string tidak mempunyai sifat. Semakan ini memastikan anda mendapat objek yang sebenar.

Gunakan fungsi ini dalam pengendali 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 — Menyediakan Fail Statik

Ramai aplikasi web perlu menyediakan aset statik seperti imej, fail CSS, fail JavaScript, atau sumber lain. Walaupun modul http tidak mempunyai perkhidmatan fail statik terbina dalam seperti express.static dalam Express, anda boleh melaksanakannya menggunakan modul fs dan path.

Jika anda secara naif menggunakan path.join(__dirname, 'public', req.url) tanpa pengesahan, penyerang boleh mengakses mana-mana fail di pelayan anda menggunakan serangan penelusuran direktori. Permintaan seperti GET /../../../etc/passwd boleh mendedahkan fail sistem. Pemeriksaan path.resolve() memastikan laluan yang diselesaikan tidak pernah keluar dari direktori public.

Inilah yang boleh berlaku tanpa pengesahan laluan yang betul:

// 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 pelayan fail statik yang selamat:

touch static.js

Buka static.js:

nano static.js

Hidangkan fail statik dengan cekap dan selamat dengan penstriman menggunakan fs.createReadStream() dan penyelesaian laluan yang selamat:

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 tambah fail ujian:

mkdir public

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

Jalankan pelayan:

node static.js

Lawati http://localhost:8000 dalam pelayar anda untuk melihat fail statik yang dipaparkan.

Kenapa menggunakan streaming dan bukan fs.readFile()? Untuk fail video 100MB, fs.readFile() memuatkan keseluruhan fail ke dalam memori sebelum dihantar. Dengan fs.createReadStream(), data mengalir terus dari cakera ke rangkaian dalam bahagian-bahagian kecil, menggunakan memori yang minimum tanpa mengira saiz fail.

Kenapa perlu semak stat.isFile()? Jika seseorang meminta laluan direktori seperti /images/, fs.stat() berjaya tetapi isFile() mengembalikan nilai palsu. Tanpa semakan ini, anda mungkin cuba menstrimkan direktori, yang akan gagal. Semakan ini memastikan anda hanya menghidangkan fail sebenar.

Uji perlindungan laluan traversa:

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

# Should return 403 Forbidden, not the system file

Langkah 7 — Pengendalian Ralat dan Perekodan

Pengendalian ralat yang kukuh adalah penting untuk server produksi. Modul http tidak menyediakan pengendalian ralat secara automatik, jadi anda perlu melaksanakannya sendiri.

Pengendalian ralat yang betul melibatkan menangkap ralat asynchronous, mencatatnya dengan sewajarnya, menghantar respons ralat yang mesra pengguna, dan mencegah server daripada terhenti akibat ralat yang tidak ditangani. Mari kita tingkatkan server kita dengan pengendalian ralat dan pencatatan yang menyeluruh:

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}`);
});

Penutupan Anggun (SIGTERM/SIGINT)

Apabila dijalankan di bawah pengurus proses seperti PM2 atau Docker, kendalikan isyarat penamatan untuk menutup pelayan 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'));

Amalan pengendalian ralat utama yang ditunjukkan:

  1. Blok cuba-tangkap di sekitar operasi async
  2. Pengendali ralat peringkat pelayan untuk masalah sambungan dan port
  3. Pengendalian ralat klien untuk permintaan yang cacat
  4. Log dengan cap masa dan tahap keterukan
  5. Mesej ralat peka persekitaran (jangan dedahkan jejak timbunan dalam pengeluaran)
  6. Respons ralat yang anggun dengan kod status HTTP yang sesuai

Amalan Terbaik: Dalam pengeluaran, pertimbangkan untuk menggunakan perpustakaan log berstruktur seperti Winston atau Pino, dan hantar ralat kritikal kepada perkhidmatan penjejakan ralat seperti Sentry atau Rollbar. Contoh ini menggunakan log konsol mudah untuk tujuan pendidikan.

Langkah 8 — Menggunakan Sintaks Modul ES (ESM)

Node.js moden menyokong ES Modules (ESM) menggunakan sintaks import/export dan bukannya CommonJS require/module.exports. Untuk menggunakan ESM, sama ada namakan fail anda dengan sambungan .mjs atau tetapkan "type": "module" dalam package.json anda.

Sintaks ESM menggunakan import dan bukannya require serta export dan bukannya module.exports. Berikut ialah pelayan asas yang sama menggunakan sintaks Modul ES:

Cipta 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:

node server.mjs

Perbezaan Utama: Dalam ESM, __dirname tidak tersedia—anda mesti mendapatkannya daripada import.meta.url. Selain itu, laluan fail dalam operasi fs mungkin memerlukan penyelesaian yang jelas. Selain daripada itu, API modul HTTP tetap sama.

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

{
  "type": "module"
}

Kemudian gunakan fail .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 Pengeluaran

Apabila bergerak dari pembangunan ke pengeluaran, beberapa pertimbangan penting perlu diambil kira:

Sokongan HTTPS

Modul http hanya berfungsi untuk HTTP. Untuk HTTPS, gunakan modul https dengan cara yang sama:

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);

Keselamatan: Jangan sekali-kali memasukkan kunci peribadi atau sijil ke dalam kawalan versi. Gunakan pembolehubah persekitaran atau perkhidmatan pengurusan rahsia yang selamat dalam pengeluaran.

Konfigurasi Proksi Terbalik

Dalam pengeluaran, pelayan Node.js biasanya dijalankan di belakang proksi terbalik seperti Nginx atau Apache. Proksi menguruskan penamatan SSL, fail statik, dan pengimbangan beban. Pelayan Node.js anda harus:

  • Dengar hanya pada localhost atau 127.0.0.1
  • Baca header X-Forwarded-For untuk IP klien
  • Percayai pengepala proksi hanya dari pelayan proksi anda

Lihat panduan kami tentang Cara Mengkonfigurasi Nginx sebagai Proksi Terbalik pada Ubuntu untuk butiran tetapan.

Pengurusan Proses

Untuk pengeluaran, gunakan pengurus proses seperti PM2 untuk mengendalikan:

  • Mulakan semula secara automatik apabila berlaku kerosakan
  • Penerapan tanpa henti
  • Pengurusan log
  • Mod kluster untuk menggunakan berbilang teras CPU

Ketahui lebih lanjut dalam panduan kami Cara Menyediakan Aplikasi Node.js untuk Pengeluaran di Ubuntu.

Pengoptimuman Prestasi

  • Pemcachedan fail: Muatkan fail statik semasa permulaan (seperti yang ditunjukkan dalam Langkah 3) daripada membaca dari cakera pada setiap permintaan. Ini secara signifikan mengurangkan beban I/O.

  • Alirkan fail besar: Untuk fail yang lebih daripada 1MB, gunakan fs.createReadStream() dan salurkan ke respons daripada memuatkan keseluruhan fail ke dalam memori.

  • Pengumpulan sambungan: Untuk sambungan pangkalan data, gunakan pengumpulan sambungan untuk mengelakkan beban tambahan daripada mencipta sambungan baru bagi setiap permintaan.

  • : Gunakan modul zlib untuk pemampatan gzip pada respons teks bagi mengurangkan penggunaan jalur lebar:

    const zlib = require('zlib');
    const gzip = zlib.createGzip();
    res.setHeader('Content-Encoding', 'gzip');
    gzip.pipe(res);
    gzip.write(data);
    gzip.end();
  • Mod kluster: Gunakan modul Node.js cluster untuk menjana proses pekerja yang sama dengan bilangan teras CPU, memaksimumkan kadar pemprosesan bagi operasi yang memerlukan CPU tinggi.

  • Permintaan melepasi masa: Tetapkan nilai masa tamat yang munasabah untuk mengelakkan pelanggan yang perlahan menggunakan sumber secara berlebihan:

    server.setTimeout(30000); // 30 seconds
  • Pemantauan memori: Jejaki penggunaan memori untuk mengesan kebocoran lebih awal. Dalam pengeluaran, pantau saiz timbunan dan laksanakan permulaan semula secara terkawal jika memori berkembang tanpa had.

Amalan Terbaik Penyimpanan Kencana dan Pemampatan

Untuk meningkatkan prestasi dan mengurangkan masa tindak balas, gunakan pengekodan dan pemampatan di tempat yang sesuai.

  • Dayakan caching untuk fail statik:
    Tambah Cache-Control dan ETag pada respon fail statik anda. Contohnya:

    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()}"`);

    Pelayar dan CDN akan menggunakan semula salinan yang disimpan dalam cache sehingga ETag atau tempoh cache tamat.

  • Gunakan pemampatan gzip atau Brotli:
    Teks berbentuk balasan seperti HTML, CSS, JS, dan JSON mampat dengan sangat baik. Anda boleh menggunakan pemampatan dengan modul zlib Node:

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

    Ini mengurangkan penggunaan jalur lebar dan mempercepat penghantaran untuk pelanggan dengan sambungan lebih perlahan.

  • Elakkan penyimpanan berlebihan bagi titik akhir dinamik:
    Bagi API atau data yang kerap dikemas kini, tetapkan tempoh cache yang lebih singkat (contohnya, max-age=60) atau gunakan no-store apabila perlu.

Pengoptimuman ini melengkapi penstriman fail statik dan penutupan secara teratur, membantu pelayan Node.js anda memberikan prestasi tinggi sambil kekal selamat dan boleh dijangka.

Gunakan modul Node.js http untuk API minimal, penghalaan tersuai, atau mempelajari dalaman HTTP.
Gunakan Express/Fastify apabila anda memerlukan middleware, templating, atau prototaip pantas.

Menyelesaikan Masalah Biasa

Port Sudah Digunakan (EADDRINUSE)

Ralat: Ralat: listen EADDRINUSE: alamat sudah digunakan :::8000

Penyelesaian:

  • Cari proses yang menggunakan port: lsof -i :8000 (macOS/Linux) atau netstat -ano | findstr :8000 (Windows)
  • Hentikan proses atau gunakan port yang berbeza
  • Tambah pengendalian ralat (seperti yang ditunjukkan di Langkah 7) untuk mengatasinya dengan baik

Badan Permintaan Tidak Diterima

Masalah: Badan permintaan POST kosong atau tidak ditakrif

Penyelesaian: Badan permintaan adalah aliran—anda mesti mengumpul bahagian-bahagiannya. Sentiasa gunakan pola yang ditunjukkan dalam Langkah 5:

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

Ralat CORS dalam Pelayar

Masalah: Pelayar menyekat permintaan disebabkan oleh polisi CORS

Penyelesaian: Tambahkan header CORS dalam 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");

Keselamatan: Menggunakan "*" untuk Access-Control-Allow-Origin membenarkan mana-mana origin. Dalam pengeluaran, nyatakan domain frontend anda secara jelas.

Fail Statik Pulang 404

Masalah: Fail statik tidak dijumpai walaupun ia wujud

Penyelesaian:

  • Periksa penyelesaian laluan fail
  • Sahkan __dirname menunjuk ke direktori yang betul
  • Pastikan kebenaran fail membenarkan pembacaan
  • Sahkan penapisan laluan tidak menyingkirkan aksara yang sah

Soalan Lazim

Apakah modul HTTP dalam Node.js?

Modul http ialah modul terbina dalam Node.js untuk mencipta pelayan dan klien HTTP. Ia menyediakan API tahap rendah untuk mengendalikan permintaan dan respons HTTP tanpa memerlukan kebergantungan luaran. Ia adalah sebahagian daripada teras Node.js, jadi ia tersedia dalam setiap pemasangan Node.js.

Bagaimana saya membuat pelayan web mudah dalam Node.js?

Cipta pelayan 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 mencipta sebuah pelayan yang mendengar pada port 8000 yang memberi respons kepada semua permintaan dengan "Hello World".

Apakah perbezaan antara modul HTTP dan Express.js?

Modul http adalah fungsi teras Node.js yang menyediakan pengendalian HTTP tahap rendah. Express.js adalah rangka kerja yang dibina di atas http yang menambah penghalaan, middleware, templating, dan kemudahan lain. Modul http memberi anda kawalan penuh tetapi memerlukan persediaan manual yang lebih, manakala Express.js menyediakan abstraksi dengan kos kebergantungan tambahan.

Gunakan modul http apabila:

  • Membangunkan API ringan atau perkhidmatan mikro
  • Belajar asas-asas HTTP
  • Anda mahu tiada pergantungan
  • Kawalan penuh ke atas pengendalian permintaan/respons

Gunakan Express.js apabila:

  • Membangunkan aplikasi kompleks dengan banyak laluan
  • Anda memerlukan ekosistem perisian perantara
  • Prototip pantas adalah keutamaan
  • Anda mahukan corak piawai komuniti

Untuk pengenalan menyeluruh tentang Express.js dan bagaimana ia memudahkan pembangunan web, lihat panduan kami tentang Asas Node.js Express.

Bolehkah saya membina REST API menggunakan Node.js tanpa Express?

Ya, sememangnya. Modul http sudah mencukupi untuk membina REST API. Anda perlu mengendalikannya secara manual:

  • Pemeriksaan laluan (menggunakan req.url dan req.method)
  • Parse badan permintaan (mengumpul cebisan aliran)
  • Pemformatan respons
  • Pengendalian ralat

Untuk API yang mudah, ini sering kali lebih baik daripada menambah kebergantungan rangka kerja. Lihat Langkah 4 dan 5 dalam tutorial ini untuk contoh penghalaan dan pengendalian permintaan POST.

Bagaimana saya mengendalikan pelbagai laluan menggunakan modul HTTP Node.js?

Analisis req.url dan gunakan logik bersyarat 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 penghalaan yang lebih kompleks, anda boleh menganalisis laluan URL, string pertanyaan menggunakan modul url, dan mengendalikan pelbagai kaedah HTTP.

Bagaimana saya menghantar respons HTML atau JSON dengan modul HTTP?

Tetapkan tajuk Content-Type dengan sewajarnya dan hantar data:

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 untuk mentafsirkan data respons.

Pelabuhan manakah yang patut saya gunakan untuk pelayan Node.js tempatan?

Port pembangunan biasa adalah:

  • 8000 atau 8080: Pembangunan HTTP umum
  • 3000: Popular dengan rangka kerja seperti Express
  • 5000: Juga biasa

Mana-mana port di atas 1024 berfungsi untuk pengguna bukan root. Port di bawah 1024 memerlukan keistimewaan pentadbir. Semak ketersediaan dengan lsof -i :PORT (macOS/Linux) sebelum digunakan.

Bagaimana saya boleh mengendalikan ralat 404 dalam pelayan HTTP Node.js?

Tambahkan kes lalai dalam logik penghalaan anda:

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

Sentiasa tetapkan kod status kepada 404 untuk laluan yang tidak wujud bagi mematuhi piawaian HTTP.

Bagaimana saya menyajikan fail statik tanpa Express?

Gunakan modul fs untuk membaca fail dan modul path untuk penentuan laluan yang selamat:

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');
    }
};

Sentiasa sahkan laluan untuk mengelakkan serangan traversal direktori (rujuk Langkah 6 untuk pelaksanaan yang selamat).

Bolehkah saya menggunakan ES Modules (import) dengan modul HTTP dalam Node.js?

Ya, Node.js menyokong Modul ES. Gunakan salah satu daripada:

  1. Fail dengan sambungan .mjs:
import http from 'http';
// ... rest of code
  1. Tetapkan "type": "module" dalam package.json, kemudian gunakan fail .js:
{
  "type": "module"
}

Perlu diingat bahawa ES Modules tidak mempunyai __dirname—peroleh ia daripada import.meta.url seperti yang ditunjukkan dalam Langkah 8.

Bagaimana saya menganalisis badan permintaan POST dengan modul HTTP?

Badan POST datang sebagai aliran. Kumpul bahagian-bahagian, kemudian tafsirkan:

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);
    });
};

Corak ini berfungsi untuk JSON, data borang, atau mana-mana jenis kandungan berasaskan teks.

Adakah modul HTTP sesuai untuk digunakan dalam produksi?

Ya, modul http sedia untuk produksi dan digunakan oleh banyak aplikasi Node.js. Walau bagaimanapun, pertimbangkan:

  • Pengurusan proses: Gunakan PM2 atau yang serupa untuk mulakan semula secara automatik
  • Proksi terbalik: Jalankan di belakang Nginx/Apache untuk SSL dan pengimbangan beban
  • Pengendalian ralat: Laksanakan pengendalian ralat dan pencatatan sepenuhnya
  • Keselamatan: Sahkan input, bersihkan laluan, tetapkan had saiz pada permintaan
  • Prestasi: Cache fail statik, gunakan pengelompokan untuk operasi yang bergantung kepada CPU

Banyak aplikasi produksi menggunakan modul http secara langsung, terutamanya mikroservis dan API ringan. Untuk panduan lengkap mengenai penyebaran produksi, lihat Cara Menyediakan Aplikasi Node.js untuk Produksi di Ubuntu 22.04.

Kesimpulan

Dalam tutorial ini, anda telah membina pelayan HTTP Node.js yang mampu berfungsi dalam persekitaran pengeluaran dari asasnya. Anda bermula dengan respons teks asas, kemudian berkembang melalui penyediaan pelbagai jenis kandungan (JSON, CSV, dan HTML). Anda telah melaksanakan penghalaan untuk mengendalikan URL yang berbeza, menganalisis badan permintaan POST, menyajikan fail statik dengan selamat, dan menambah penanganan ralat serta pencatatan yang komprehensif. Anda juga telah meneroka sintaks CommonJS dan ES Module, serta pertimbangan untuk penempatan dalam pengeluaran.

Anda kini bersedia untuk mencipta pelayan web yang mengendalikan pelbagai keperluan tanpa bergantung kepada rangka kerja. Dengan pengetahuan ini, anda boleh:

  • Bina API REST ringan untuk perkhidmatan mikro
  • Hidangkan laman web berbilang halaman dengan penghalaan yang betul
  • Urus muat naik dan muat turun fail
  • Melaksanakan logik middleware tersuai
  • Buat keputusan yang bijak tentang bila kerangka kerja menambah nilai berbanding kerumitan yang tidak perlu

Langkah seterusnya:

  • Selami lebih mendalam asas-asas Node.js: Kembali ke siri Cara Menulis Kod dalam Node.js untuk liputan yang menyeluruh
  • Terokai dokumentasi rasmi: Dokumentasi modul HTTP Node.js mengandungi rujukan API yang terperinci dan kes penggunaan lanjutan
  • : Pertimbangkan asas Express.js untuk pembangunan pantas dengan sokongan middleware dan templat

Memahami asas HTTP ini menjadikan anda seorang pembangun Node.js yang lebih mahir. Sama ada anda memilih untuk menggunakan rangka kerja atau bekerja terus dengan modul http, anda kini memahami mekanik asas yang menggerakkan semua aplikasi web Node.js. Pengetahuan ini membantu anda menyahpepijat masalah, mengoptimumkan prestasi, dan mereka bentuk sistem yang berskala dengan berkesan.

Terima kasih kerana belajar bersama Komuniti Linux-Console.net.