Node.js adalah runtime JavaScript yang menjalankan kode JavaScript di luar peramban, terutama di sisi server. Dua komponen utamanya adalah mesin V8 dari proyek Chromium dan pustaka libuv yang menyediakan antarmuka I/O asinkron lintas platform. Topik ini membahas komponen pembentuk Node.js, model konkurensi non-blocking yang dijalankannya, serta ranah penggunaan tempat Node.js unggul dan tempat Node.js kurang cocok.
Komponen runtime Node.js#
Node.js menyatukan tiga lapisan utama menjadi satu proses yang dapat dijalankan dari command line. Lapisan pertama adalah mesin V8, yang mengompilasi dan mengeksekusi JavaScript. Lapisan kedua adalah libuv, pustaka C yang menyediakan event loop serta antarmuka asinkron untuk berkas, jaringan, dan timer. Lapisan ketiga adalah binding Node.js yang ditulis dalam C++, yang membungkus V8 dan libuv menjadi API JavaScript seperti fs.readFile dan http.createServer.
Ketiga lapisan tersebut tidak dapat saling dipertukarkan. V8 tidak tahu cara membaca berkas atau menerima koneksi TCP, sedangkan libuv tidak mengeksekusi JavaScript. Binding Node.jslah yang menjembatani keduanya sehingga sebuah panggilan fs.readFile dapat diteruskan ke libuv, dijalankan di luar thread JavaScript, lalu hasilnya dikembalikan ke V8 sebagai nilai callback.
Pemahaman tentang lapisan-lapisan tersebut membantu saat membaca pesan error. Pesan yang menyebut node:internal/... berasal dari binding Node.js, sedangkan pesan dari V8 biasanya menyebut baris kode JavaScript tertentu. Diagram berikut memperlihatkan posisi masing-masing lapisan.
Versi ketiga komponen tersebut dilaporkan langsung oleh Node.js lewat objek process.versions. Nilai ini diambil dari biner yang sedang berjalan, bukan dari dokumen.
console.log(process.versions.v8);
console.log(process.versions.uv);Keluaran pada Node.js 24 di sandbox:
13.6.233.17-node.63
1.52.1Model konkurensi non-blocking#
Node.js mengeksekusi kode JavaScript pada satu thread utama. Pekerjaan I/O yang lambat (berkas, jaringan, timer) tidak menunggu pada thread tersebut, melainkan diteruskan ke libuv yang menjalankannya secara asinkron lewat event loop. Ketika operasi I/O selesai, libuv memasukkan callback yang terdaftar kembali ke antrian event loop untuk dieksekusi V8.
Akibat pola ini, kode Node.js jarang berbentuk urutan sinkron yang memblokir. Sebagai gantinya, fungsi yang memicu I/O menerima callback atau mengembalikan promise yang diselesaikan belakangan. Program berikut memperlihatkan urutan eksekusi yang khas. Tiga perintah console.log ditulis berurutan, tetapi satu di antaranya dijadwalkan lewat setTimeout sehingga baru dipanggil setelah kode sinkron selesai.
console.log("A");
setTimeout(() => console.log("B"), 0);
console.log("C");A
C
BWalaupun setTimeout diberi penundaan nol milidetik, urutan keluaran menjadi A, C, B. Hal ini terjadi karena callback setTimeout tidak dijalankan saat pemanggilan, melainkan diantrekan ke event loop. V8 menyelesaikan dulu seluruh kode sinkron pada script ("A" dan "C") sebelum event loop boleh memproses antrian callback.
Konsekuensi langsung dari model ini adalah bahwa pekerjaan yang memblokir thread utama akan memblokir seluruh proses. Sebuah perulangan while yang berjalan selama sepuluh detik membuat semua callback yang sudah diantrekan tertunda sepuluh detik juga. Untuk beban kerja yang terikat CPU (pengodean, kompresi, algoritma berat), pola worker thread atau pemindahan pekerjaan ke proses terpisah lebih tepat daripada mengandalkan event loop tunggal.
Ranah yang cocok untuk Node.js#
Node.js unggul pada aplikasi yang I/O-bound, yaitu aplikasi yang sebagian besar waktunya habis untuk menunggu operasi I/O daripada menghitung. Beberapa ranah yang khas adalah server HTTP API, server WebSocket, proksi, gateway API, aplikasi real-time seperti pesan langsung dan papan skor, serta pemroses berkas dan stream.
Pada ranah-ranah itu, event loop tunggal menjadi keuntungan karena satu proses Node.js dapat melayani ribuan koneksi bersamaan tanpa biaya pembuatan thread per koneksi. Hal ini berbeda dengan model satu thread per permintaan pada server tradisional, yang memboroskan memori ketika koneksi banyak namun sebagian besar menganggur menunggu I/O.
Node.js kurang cocok untuk pekerjaan yang benar-benar terikat CPU, seperti kompresi video, pelatihan model machine learning, atau algoritma kriptografi berat. Pada kasus tersebut, durasi komputasi yang lama akan memblokir event loop sehingga permintaan lain tertunda. Solusi yang umum diambil adalah memindahkan komputasi ke worker thread (modul worker_threads), ke proses terpisah, atau ke layanan yang ditulis dalam bahasa lain.
Kesalahan umum#
Menganggap Node.js sebagai framework web seperti Django atau Laravel. Node.js adalah runtime, bukan framework: ia menyediakan eksekusi JavaScript dan modul inti, tetapi tidak menetapkan struktur direktori, ORM, atau templat default. Framework seperti Express, Fastify, atau NestJS dibangun di atas Node.js, bukan sebaliknya. Pemahaman ini penting agar tidak mengharapkan fitur framework dari runtime itu sendiri.
Mengira Node.js menjalankan JavaScript secara multi-threaded secara default. V8 mengeksekusi kode JavaScript pada satu thread utama, sehingga perulangan berat atau pemanggilan fungsi sinkron yang lama akan memblokir seluruh proses. libuv memang memiliki thread pool untuk beberapa operasi I/O (misalnya fs.readFile pada sistem berkas lokal), tetapi callback hasilnya tetap dijalankan pada thread utama. Untuk komputasi berat yang harus berjalan paralel, modul worker_threads atau proses terpisah perlu dipakai secara eksplisit.
Membandingkan Node.js dengan pasangan PHP-FPM dan Nginx tanpa memperhatikan perbedaan model proses. Pada PHP-FPM, setiap permintaan ditangani oleh worker baru yang berdiri sendiri. Pada Node.js, satu proses menangani banyak permintaan bergantian lewat event loop, sehingga state di memori dapat dibagikan antar permintaan. Akibatnya, kebocoran memori atau variabel global yang berubah dapat memengaruhi permintaan lain dalam proses yang sama.
Ringkasan#
- Node.js adalah runtime JavaScript yang dibangun dari mesin V8 (eksekusi JavaScript), pustaka libuv (event loop dan I/O asinkron), serta binding C++ yang membungkus keduanya menjadi API JavaScript.
- Eksekusi JavaScript berjalan pada satu thread utama, sedangkan operasi I/O diteruskan ke libuv dan diselesaikan secara asinkron lewat event loop, sehingga callback dapat berjalan setelah kode sinkron selesai.
- Pekerjaan yang memblokir thread utama akan memblokir seluruh proses, sehingga Node.js paling cocok untuk aplikasi I/O-bound dan kurang cocok untuk komputasi terikat CPU tanpa pemisahan ke worker atau proses lain.
Referensi#
- About Node.js — Node.js — Penjelasan resmi tentang apa itu Node.js, riwayat, dan filosofi runtime.
- libuv documentation — libuv — Dokumentasi pustaka libuv yang menjadi dasar event loop dan I/O asinkron Node.js.
- V8 JavaScript engine — V8 — Situs resmi mesin JavaScript V8 yang dipakai Node.js untuk mengeksekusi kode.