kodingup.camp

Beranda/Node.js Backend: Dasar, Express & REST API

Modul Bawaan

Modul 3 dari 104 menit bacaCore Modules

Isi modul

    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:

    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.

    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

    1. 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. Gunakan path.join(import.meta.dirname, ...).
    2. Lupa "utf8" saat readFile. Tanpa encoding, hasilnya Buffer (data biner), bukan string, dan JSON.parse bisa memberi hasil membingungkan.
    3. Menelan semua error file. catch {} yang mengabaikan error membuat file JSON rusak terlihat seperti "data kosong". Periksa err.code === "ENOENT" dan lempar ulang error lainnya.
    4. Menyambung stream dengan .pipe() tanpa penanganan error. Kalau satu stream gagal, yang lain bisa menggantung. Pakai pipeline.
    5. Membuat ID dengan Math.random() atau Date.now(). Bisa bentrok dan mudah ditebak. Pakai randomUUID().

    Latihan

    1. Mudah, Buat skrip yang mencetak nama file, ekstensi, dan folder dari path /home/budi/dokumen/laporan-final.pdf memakai node:path.
    2. Sedang, Buat skrip catat.js yang menambahkan baris berformat [waktu ISO] pesan ke file log.txt setiap dijalankan (node catat.js "server start"), lalu mencetak jumlah baris di file.
    3. Menantang, Buat kelas Antrean yang extends EventEmitter. Metode tambah(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

    Video pendamping

    Kalau penjelasan di atas belum masuk, tonton ini. Video dari kanal YouTube pihak lain.

    Node.js Tutorial - 26 - fs ModuleCodevolution · Bahasa InggrisBuka di YouTube

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.