Kenapa ini penting
Node datang dengan "kotak perkakas" bawaan yang langsung bisa dipakai tanpa npm install: membaca dan menulis file, menyusun path, membaca info sistem, membuat event, mengalirkan data besar, sampai membuat ID acak yang aman. Banyak paket npm populer sebenarnya hanya membungkus modul-modul ini. Kalau kamu paham perkakas bawaannya, kamu bisa menghindari dependensi yang tidak perlu, dan setiap dependensi yang tidak diinstal berarti satu celah keamanan dan satu sumber masalah upgrade yang hilang.
Di modul CRUD nanti, kamu akan menyimpan data ke file JSON memakai node:fs/promises, membangun path dengan node:path, dan membuat ID dengan node:crypto. Jadi modul ini langsung terpakai.
Konsep inti
node:fs/promises: bekerja dengan file
Modul fs (file system) punya tiga gaya: callback (fs.readFile(path, cb)), sinkron (fs.readFileSync), dan Promise (node:fs/promises). Untuk server, pakai versi Promise dengan await, karena tidak memblokir event loop.
| Fungsi | Kegunaan |
|---|---|
readFile(path, "utf8") |
Membaca seluruh isi file sebagai string |
writeFile(path, data) |
Menulis (menimpa) file |
appendFile(path, data) |
Menambah di akhir file |
mkdir(path, { recursive: true }) |
Membuat folder beserta induknya, tidak error kalau sudah ada |
readdir(path) |
Daftar isi folder |
stat(path) |
Info file: ukuran, tanggal ubah, apakah folder |
rename(lama, baru) |
Memindah/mengganti nama file |
rm(path, { recursive: true, force: true }) |
Menghapus file/folder (hati-hati) |
Kalau file tidak ada, error yang dilempar punya err.code === "ENOENT" (Error NO ENTry). Kode error seperti ini lebih bisa diandalkan daripada membaca teks pesan.
Versi sinkron (readFileSync) masih wajar dipakai untuk skrip kecil atau saat startup (misalnya membaca config sekali sebelum server menerima request), tapi jangan dipakai di dalam handler request.
node:path: menyusun path dengan aman
Windows memakai \, macOS/Linux memakai /. Menyambung string manual seperti folder + "/" + nama rawan salah. path membereskannya:
path.join("data", "tugas.json")→data/tugas.jsonpath.resolve("data")→ path absolut dari folder kerja saat inipath.basename("/a/b/foto.png")→foto.png,path.extname(...)→.pngpath.dirname("/a/b/foto.png")→/a/b
Kombinasikan dengan import.meta.dirname supaya path selalu relatif terhadap lokasi file kode, bukan terhadap folder tempat kamu mengetik perintah.
node:os: info sistem
os.cpus(), os.totalmem(), os.freemem(), os.platform(), os.homedir(), os.tmpdir(), dan os.EOL (karakter akhir baris: \n di Unix, \r\n di Windows). Berguna untuk endpoint health check atau menentukan folder sementara.
node:events: pola pemancar event
Banyak objek di Node adalah EventEmitter: server HTTP, stream, proses. Polanya mirip addEventListener di browser. Analoginya bel di dapur warung: koki memencet bel (emit) dan siapa pun yang sedang menunggu bel itu (on) akan bereaksi. Koki tidak perlu tahu siapa saja yang mendengar.
emitter.on("nama", fn)mendaftarkan listeneremitter.once("nama", fn)hanya sekaliemitter.emit("nama", data)memicu semua listener secara sinkronemitter.off("nama", fn)melepas listener
node:stream: mengalirkan data besar
Membaca file 2 GB dengan readFile berarti memuat 2 GB ke memori. Stream memproses data per potongan (chunk), seperti air yang mengalir lewat pipa alih-alih diangkut satu ember raksasa. Ada empat jenis: Readable (sumber), Writable (tujuan), Duplex, dan Transform (pengubah di tengah, misalnya kompresi gzip).
Cara paling aman menyambung stream adalah pipeline dari node:stream/promises: ia meneruskan error dan menutup semua stream kalau ada yang gagal. Request dan response HTTP di Node sendiri adalah stream, itulah kenapa nanti kita membaca body dengan for await.
node:crypto: ID dan hash
randomUUID() menghasilkan UUID versi 4 acak (36 karakter) yang aman secara kriptografi, cocok untuk ID data. Fungsi ini juga tersedia global sebagai crypto.randomUUID(). Modul crypto juga menyediakan hash (createHash("sha256")), randomBytes, dan scrypt untuk password (dibahas di bagian keamanan).
Contoh kode
// core.js, jalankan: node core.js
import { readFile, writeFile, mkdir } from "node:fs/promises";
import path from "node:path";
import os from "node:os";
import { EventEmitter } from "node:events";
import { randomUUID } from "node:crypto";
// 1. File & path: folder "data" di sebelah file ini, bukan di folder terminal
const folder = path.join(import.meta.dirname, "data");
await mkdir(folder, { recursive: true });
const file = path.join(folder, "catatan.txt");
await writeFile(file, "Belajar Node hari ini\n", "utf8");
const isi = await readFile(file, "utf8");
console.log("Isi file:", isi.trim());
console.log("Ekstensi:", path.extname(file), "| nama:", path.basename(file));
// 2. OS
console.log("CPU:", os.cpus().length > 0, "| EOL ada:", JSON.stringify(os.EOL));
// 3. Events
const toko = new EventEmitter();
toko.on("pesanan", (p) => console.log("Pesanan masuk:", p.menu));
toko.emit("pesanan", { menu: "Nasi goreng" });
// 4. Crypto
const id = randomUUID();
console.log("UUID panjang:", id.length);
// 5. Error file tidak ada
try {
await readFile("tidak-ada.txt", "utf8");
} catch (err) {
console.log("Error code:", err.code);
}
// → Isi file: Belajar Node hari ini
// → Ekstensi: .txt | nama: catatan.txt
// → CPU: true | EOL ada: "\n"
// → Pesanan masuk: Nasi goreng
// → UUID panjang: 36
// → Error code: ENOENT
Stream: mengompres file 1,7 MB tanpa memuat semuanya ke memori sekaligus.
// stream.js
import { createReadStream, createWriteStream } from "node:fs";
import { writeFile, stat } from "node:fs/promises";
import { pipeline } from "node:stream/promises";
import { createGzip } from "node:zlib";
await writeFile("besar.txt", "baris log contoh\n".repeat(100_000));
// sumber → pengubah (gzip) → tujuan, error di mana pun ditangkap oleh pipeline
await pipeline(createReadStream("besar.txt"), createGzip(), createWriteStream("besar.txt.gz"));
const asli = (await stat("besar.txt")).size;
const kecil = (await stat("besar.txt.gz")).size;
console.log(`Asli: ${asli} byte, gzip: ${kecil} byte`);
// → Asli: 1700000 byte, gzip: 4174 byte
(Rasio kompresinya ekstrem karena isinya satu baris yang diulang; file log asli biasanya mengecil 5 sampai 10 kali.)
Kesalahan umum
- Memakai path relatif polos seperti
readFile("data/tugas.json"). Path itu dihitung dari folder terminal (process.cwd()), jadi kalau server dijalankan dari folder lain, file tidak ketemu. Gunakanpath.join(import.meta.dirname, ...). - Lupa
"utf8"saatreadFile. Tanpa encoding, hasilnyaBuffer(data biner), bukan string, danJSON.parsebisa memberi hasil membingungkan. - Menelan semua error file.
catch {}yang mengabaikan error membuat file JSON rusak terlihat seperti "data kosong". Periksaerr.code === "ENOENT"dan lempar ulang error lainnya. - Menyambung stream dengan
.pipe()tanpa penanganan error. Kalau satu stream gagal, yang lain bisa menggantung. Pakaipipeline. - Membuat ID dengan
Math.random()atauDate.now(). Bisa bentrok dan mudah ditebak. PakairandomUUID().
Latihan
- Mudah, Buat skrip yang mencetak nama file, ekstensi, dan folder dari path
/home/budi/dokumen/laporan-final.pdfmemakainode:path. - Sedang, Buat skrip
catat.jsyang menambahkan baris berformat[waktu ISO] pesanke filelog.txtsetiap dijalankan (node catat.js "server start"), lalu mencetak jumlah baris di file. - Menantang, Buat kelas
Antreanyang extendsEventEmitter. Metodetambah(item)memancarkan event"masuk", dan saat isi antrean mencapai 3, memancarkan event"penuh"dengan array isinya lalu mengosongkan antrean.
Lihat solusi
1.
import path from "node:path";
const p = "/home/budi/dokumen/laporan-final.pdf";
console.log(path.basename(p)); // → laporan-final.pdf
console.log(path.extname(p)); // → .pdf
console.log(path.dirname(p)); // → /home/budi/dokumen
console.log(path.basename(p, ".pdf")); // → laporan-final
2.
// catat.js
import { appendFile, readFile } from "node:fs/promises";
import path from "node:path";
const file = path.join(import.meta.dirname, "log.txt");
const pesan = process.argv[2] ?? "(tanpa pesan)";
await appendFile(file, `[${new Date().toISOString()}] ${pesan}\n`);
const isi = await readFile(file, "utf8");
console.log("Jumlah baris:", isi.trim().split("\n").length);
3.
import { EventEmitter } from "node:events";
class Antrean extends EventEmitter {
#isi = [];
tambah(item) {
this.#isi.push(item);
this.emit("masuk", item);
if (this.#isi.length === 3) {
this.emit("penuh", [...this.#isi]);
this.#isi = [];
}
}
}
const a = new Antrean();
a.on("masuk", (x) => console.log("masuk:", x));
a.on("penuh", (semua) => console.log("penuh, proses:", semua));
["A", "B", "C", "D"].forEach((x) => a.tambah(x));
// → masuk: A
// → masuk: B
// → masuk: C
// → penuh, proses: [ 'A', 'B', 'C' ]
// → masuk: D
Ringkasan
- Modul bawaan di-import dengan prefix
node:dan tidak perlu diinstal. - Pakai
node:fs/promisesdi server; periksaerr.code(misalnyaENOENT) untuk menangani error file. node:pathplusimport.meta.dirnamemembuat path aman lintas OS dan tidak bergantung folder terminal.EventEmitteradalah pola publish/subscribe yang dipakai di banyak bagian Node.- Stream memproses data besar per potongan; sambungkan dengan
pipelinedarinode:stream/promises. randomUUID()darinode:cryptoadalah cara standar membuat ID unik.