Selama dua chapter terakhir kita sibuk membaca sisi kirim: jalur dan body request. Kini kita balik badan ke sisi terima — bagaimana server menyusun jawaban yang informatif. Response yang baik bukan sekadar body; status code yang tepat, header seperti Location dan Set-Cookie, serta cara pengiriman yang bertahap semuanya ikut menentukan pengalaman klien. Pada chapter ini kita kendalikan response dari tiga sisi sekaligus: cara menetapkan status dan header, header khusus untuk pengalihan, cookie, dan CORS, lalu pengiriman body secara bertahap memakai write().
Dua Cara Menetapkan Status dan Header #
Object res menyediakan dua gaya pengaturan. Gaya pertama memakai writeHead(status, headers) — satu panggilan untuk status dan seluruh header sekaligus, cocok untuk response yang bentuknya sudah pasti. Gaya kedua memisahkan langkah: properti res.statusCode menetapkan status, sedangkan res.setHeader(nama, nilai)
menetapkan header satu per satu — nyata saat header dihitung secara bertahap sebelum body ditulis. Keduanya boleh dipakai bergantian di satu server, seperti demo berikut; hanya saja writeHead dan setHeader untuk nama yang sama jangan dicampur dalam satu response.
import http from 'node:http';
const server = http.createServer((req, res) => {
if (req.url === '/teks') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.write('baris pertama\n');
res.write('baris kedua\n');
res.end('baris penutup');
return;
}
if (req.url === '/pindah') {
res.writeHead(302, { Location: '/teks' });
res.end();
return;
}
if (req.url === '/kue') {
res.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8',
'Set-Cookie': 'nama=Budi; Path=/; HttpOnly',
});
res.end('kue sudah dikirim');
return;
}
if (req.url === '/cors') {
res.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json; charset=utf-8',
});
res.end(JSON.stringify({ pesan: 'boleh dibaca siapa saja' }));
return;
}
if (req.method === 'POST' && req.url === '/simpan') {
res.writeHead(201, { Location: '/tugas/7' });
res.end();
return;
}
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('tidak ada');
});
server.listen(0, '127.0.0.1', async () => {
const port = server.address().port;
const dasar = `http://127.0.0.1:${port}`;
const r1 = await fetch(dasar + '/teks');
console.log('teks :', r1.status, r1.headers.get('content-type'));
console.log('isi :', JSON.stringify(await r1.text()));
const r2 = await fetch(dasar + '/pindah', { redirect: 'manual' });
console.log('pindah :', r2.status, '->', r2.headers.get('location'));
const r3 = await fetch(dasar + '/pindah');
console.log('pindah ikut:', r3.status, '->', (await r3.text()).split('\n')[0]);
const r4 = await fetch(dasar + '/kue');
console.log('kue :', r4.status, '->', r4.headers.get('set-cookie'));
const r5 = await fetch(dasar + '/cors');
console.log('cors :', r5.status, '->', r5.headers.get('access-control-allow-origin'));
const r6 = await fetch(dasar + '/simpan', { method: 'POST' });
console.log('simpan :', r6.status, '->', r6.headers.get('location'));
server.close();
});Output yang Dihasilkan:
teks : 200 text/plain; charset=utf-8
isi : "baris pertama\nbaris kedua\nbaris penutup"
pindah : 302 -> /teks
pindah ikut: 200 -> baris pertama
kue : 200 -> nama=Budi; Path=/; HttpOnly
cors : 200 -> *
simpan : 201 -> /tugas/7Mari kita telaah bagian-bagian utamanya:
- Baris 4-11 (
/teks): cabang ini memakai gaya bertahap —res.statusCode,res.setHeader, tiga kalires.write, lalures.end. Tiga potongan teks dikirim sebagai body yang sama-sama lengkap;JSON.stringifypada hasilr1.text()memperlihatkan\npemisah barisnya. - Baris 12-16 (
/pindah): pengalihan membalas302dengan headerLocationdan body kosong. Inilah mekanisme di balik URL pindahan: klien yang mengikutiLocationakan mengirim request baru ke tujuannya. - Baris 17-24 (
/kue): headerSet-Cookiemengirim satu cookie bernamanamake klien. AtributPath=/membuat cookie berlaku untuk seluruh situs, sedangkanHttpOnlymelarang JavaScript di sisi browser membacanya — pelindung sederhana terhadap pencurian cookie lewat skrip halaman. - Baris 25-32 (
/cors):Access-Control-Allow-Origin: *menyatakan bahwa response ini boleh dibaca JavaScript dari domain mana pun. Tanpa header ini, browser memblokir permintaan lintas domain — kita hanya melihat sisi servernya di sini karenafetchbawaan Node.js tidak menerapkan kebijakan CORS. - Baris 33-37 (
/simpan): pola standar API setelah data dibuat — status201 Createdplus headerLocationyang menunjuk ke alamat sumber daya baru. Klien cukup membacaLocationtanpa menebak di mana datanya berakhir. - Baris 46-63: enam pemeriksaan
fetchmencetak hanya status dan header yang memang kita tetapkan sendiri —content-type,location,set-cookie,access-control-allow-origin. Baris 50 memakai opsiredirect: 'manual'agarfetchberhenti di respons302alih-alih mengikutinya, sehingga status danLocationaslinya terlihat.
Pengalihan: 301, 302, dan Perilaku Klien #
Status 301 dan 302 sama-sama berarti “sumber daya ada di tempat lain”, tetapi berbeda niat: 301 Moved Permanently menyatakan pindahan permanen sehingga klien dan mesin pencari boleh mengingat alamat baru, sedangkan 302 Found adalah pindahan sementara untuk saat ini saja. Pada demo, fetch kedua (baris 53) memperlihatkan perilaku bawaan klien modern: pengalihan diikuti secara otomatis, sehingga yang terlihat adalah hasil akhir 200 dari /teks, bukan 302 di tengah jalan. Opsi redirect: 'manual' pada pemeriksaan pertama mematikan perilaku tersebut — teknik yang berguna saat menulis pengujian untuk logika pengalihan kita sendiri.
Mengirim Body Secara Bertahap #
Tiga baris res.write() pada cabang /teks adalah pengantar pola streaming. Daripada menahan seluruh isi di memori lalu mengirim sekaligus, server bisa menulis potongan demi potongan segera setelah data tersedia — berkas besar, hasil ekspor panjang, atau peristiwa realtime dikirim dengan cara ini. Node.js melengkapi response dengan write() dan end() persis seperti writable stream yang kita pelajari di Section Buffers dan Streams; perlu diingat bahwa header terkirim begitu write() pertama dipanggil, sehingga keputusan status dan header harus selesai sebelum potongan pertama berangkat. Untuk berkas yang datang dari disk, pola lengkapnya — membaca stream berkas dan menyalurkannya ke response — akan kita rangkai pada Chapter Static File Server.
Kendali penuh atas response membuat server kita siap menjadi mitra percakapan yang sopan. Sampai titik ini kita selalu berperan sebagai server; pada chapter berikutnya kita berganti kursi menjadi klien — memanggil API orang lain dengan fetch, menangani status yang gagal, dan membatalkan permintaan yang terlalu lama — keahlian yang dibutuhkan hampir setiap aplikasi Node.js.