Buffer #
Buffer adalah representasi data biner mentah di memori — urutan byte yang belum memiliki interpretasi apapun sampai kamu memberinya konteks. Saat bekerja dengan file gambar, data jaringan, kriptografi, protokol biner, atau encoding karakter non-standar, kamu bekerja dengan Buffer. Di JavaScript browser ada ArrayBuffer dan TypedArray; di Node.js ada Buffer yang merupakan subclass dari Uint8Array — kompatibel dengan Web API tapi punya metode tambahan yang sangat praktis untuk operasi sehari-hari. Memahami Buffer bukan hanya soal API-nya, tapi juga memahami encoding karakter dan bagaimana data direpresentasikan sebagai byte.
Membuat Buffer #
Ada beberapa cara membuat Buffer, masing-masing untuk use case yang berbeda.
// Buffer.from() — cara paling umum, dari berbagai sumber
// dari string dengan encoding (default: utf-8)
const buf1 = Buffer.from("Hello, World!", "utf-8");
console.log(buf1); // <Buffer 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21>
console.log(buf1.length); // 13
// dari string hex
const buf2 = Buffer.from("48656c6c6f", "hex");
console.log(buf2.toString("utf-8")); // "Hello"
// dari string base64
const buf3 = Buffer.from("SGVsbG8sIFdvcmxkIQ==", "base64");
console.log(buf3.toString("utf-8")); // "Hello, World!"
// dari array of bytes
const buf4 = Buffer.from([72, 101, 108, 108, 111]); // ASCII: H, e, l, l, o
console.log(buf4.toString()); // "Hello"
// dari Buffer lain — membuat SALINAN, bukan referensi
const buf5 = Buffer.from(buf1);
buf5[0] = 0x68; // ubah 'H' jadi 'h'
console.log(buf1.toString()); // "Hello, World!" — tidak berubah
console.log(buf5.toString()); // "hello, World!" — hanya salinan yang berubah
// dari ArrayBuffer (Web API) — berbagi memori yang sama!
const arrayBuf = new ArrayBuffer(4);
const buf6 = Buffer.from(arrayBuf);
Buffer.alloc vs Buffer.allocUnsafe #
// Buffer.alloc() — alokasi Buffer baru yang diisi nol (aman)
const bufAman = Buffer.alloc(10);
console.log(bufAman); // <Buffer 00 00 00 00 00 00 00 00 00 00>
// Buffer.alloc() dengan nilai awal
const bufTerisi = Buffer.alloc(5, 0xff);
console.log(bufTerisi); // <Buffer ff ff ff ff ff>
// Buffer.alloc() dengan string sebagai isian
const bufString = Buffer.alloc(10, "ab", "utf-8");
console.log(bufString.toString()); // "ababababab"
// ANTI-PATTERN: Buffer.allocUnsafe() tanpa segera mengisi nilainya
const bufTidakAman = Buffer.allocUnsafe(10);
// ✗ bufTidakAman mungkin berisi data lama dari memori yang belum di-nol-kan
// JANGAN baca isinya sebelum mengisi seluruhnya
// BENAR: allocUnsafe hanya jika kamu LANGSUNG mengisi seluruh buffer
const bufUnsafeBenar = Buffer.allocUnsafe(10);
bufUnsafeBenar.fill(0); // atau segera tulis data ke seluruh buffer
// ✓ sekarang aman dibaca
// allocUnsafe lebih cepat dari alloc karena tidak melakukan zero-fill
// gunakan hanya saat performa kritis dan kamu yakin mengisi seluruh buffer
Buffer.allocUnsafe()lebih cepat dariBuffer.alloc()karena tidak melakukan zero-fill, tapi ini berarti buffer mungkin berisi data lama dari memori yang sebelumnya digunakan proses lain — termasuk data sensitif seperti password atau token. Selalu gunakanBuffer.alloc()kecuali kamu punya alasan performa yang jelas dan yakin mengisi seluruh buffer sebelum dibaca.
Encoding dan Decoding #
Encoding menentukan bagaimana byte diinterpretasikan sebagai teks, atau bagaimana teks dikonversi ke byte. Node.js mendukung beberapa encoding bawaan.
flowchart LR
A["String\n'Hello'"] -- "Buffer.from(str, enc)" --> B["Buffer\n48 65 6c 6c 6f"]
B -- "buf.toString(enc)" --> A
C["Hex String\n'48656c6c6f'"] -- "Buffer.from(hex, 'hex')" --> B
B -- "buf.toString('hex')" --> C
D["Base64\n'SGVsbG8='"] -- "Buffer.from(b64, 'base64')" --> B
B -- "buf.toString('base64')" --> DEncoding yang Didukung #
const teks = "Halo, Dunia! 🌏";
// utf-8 (default) — encoding standar untuk teks Unicode
const bufUtf8 = Buffer.from(teks, "utf-8");
console.log(bufUtf8.length); // 19 — emoji butuh 4 byte
// utf-16le — digunakan Windows dan beberapa format file Microsoft
const bufUtf16 = Buffer.from(teks, "utf-16le");
console.log(bufUtf16.length); // 30 — setiap karakter minimal 2 byte
// ascii — hanya 7-bit ASCII, karakter di luar rentang dipotong
const bufAscii = Buffer.from("Hello", "ascii");
console.log(bufAscii.length); // 5
// latin1 / binary — satu byte per karakter (ISO-8859-1)
const bufLatin1 = Buffer.from("café", "latin1");
console.log(bufLatin1.length); // 4
// hex — representasi hexadecimal
const bufHex = Buffer.from("deadbeef", "hex");
console.log(bufHex.length); // 4 — setiap dua karakter hex = 1 byte
// base64 — encoding standar untuk data biner dalam teks
const bufBase64 = Buffer.from("SGVsbG8=", "base64");
console.log(bufBase64.toString()); // "Hello"
// base64url — base64 tanpa +, /, = (aman untuk URL)
const bufB64url = Buffer.from("SGVsbG8", "base64url");
console.log(bufB64url.toString()); // "Hello"
Konversi Antar Encoding #
// pola konversi: string → Buffer → string (encoding berbeda)
function konversiEncoding(
input: string,
dariEncoding: BufferEncoding,
keEncoding: BufferEncoding
): string {
return Buffer.from(input, dariEncoding).toString(keEncoding);
}
// hex ke base64
const hex = "48656c6c6f2c20576f726c6421";
const base64 = konversiEncoding(hex, "hex", "base64");
console.log(base64); // "SGVsbG8sIFdvcmxkIQ=="
// base64 ke hex
const base64Input = "SGVsbG8sIFdvcmxkIQ==";
const hexOutput = konversiEncoding(base64Input, "base64", "hex");
console.log(hexOutput); // "48656c6c6f2c20576f726c6421"
// string utf-8 ke base64 — berguna untuk Basic Auth header
function encodeBase64(str: string): string {
return Buffer.from(str, "utf-8").toString("base64");
}
function decodeBase64(b64: string): string {
return Buffer.from(b64, "base64").toString("utf-8");
}
// Basic Auth header
const credentials = encodeBase64("username:password");
console.log(`Authorization: Basic ${credentials}`);
// "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ="
// base64url untuk JWT atau token URL-safe
function encodeBase64URL(data: Buffer): string {
return data.toString("base64url");
}
function decodeBase64URL(b64url: string): Buffer {
return Buffer.from(b64url, "base64url");
}
Membaca dan Menulis Data Numerik #
Buffer menyediakan metode untuk membaca dan menulis tipe data numerik dalam format byte yang tepat — penting saat bekerja dengan protokol biner atau format file terstruktur.
Endianness #
Sebelum membaca/menulis angka multi-byte, kamu perlu tahu endianness — urutan penyimpanan byte.
Big-Endian (BE): byte paling signifikan disimpan di alamat terkecil
Angka 0x12345678 → [0x12, 0x34, 0x56, 0x78]
Little-Endian (LE): byte paling tidak signifikan disimpan di alamat terkecil
Angka 0x12345678 → [0x78, 0x56, 0x34, 0x12]
Network byte order = Big-Endian (digunakan protokol jaringan TCP/IP)
x86/x64 CPU = Little-Endian (digunakan sebagian besar komputer modern)
const buf = Buffer.alloc(8);
// menulis integer 32-bit
buf.writeUInt32BE(0x12345678, 0); // Big-Endian di offset 0
console.log(buf.subarray(0, 4)); // <Buffer 12 34 56 78>
buf.writeUInt32LE(0x12345678, 4); // Little-Endian di offset 4
console.log(buf.subarray(4, 8)); // <Buffer 78 56 34 12>
// membaca integer 32-bit
console.log(buf.readUInt32BE(0).toString(16)); // "12345678"
console.log(buf.readUInt32LE(4).toString(16)); // "12345678"
// semua metode read/write yang tersedia:
// readUInt8, readInt8
// readUInt16BE, readUInt16LE, readInt16BE, readInt16LE
// readUInt32BE, readUInt32LE, readInt32BE, readInt32LE
// readBigUInt64BE, readBigUInt64LE, readBigInt64BE, readBigInt64LE
// readFloatBE, readFloatLE
// readDoubleBE, readDoubleLE
// contoh: membaca header protokol biner sederhana
interface HeaderPaket {
magic: number; // 4 byte — magic number identifikasi protokol
versi: number; // 1 byte — versi protokol
tipe: number; // 1 byte — tipe pesan
panjangPayload: number; // 4 byte — panjang payload dalam byte
}
function bacaHeader(buf: Buffer): HeaderPaket {
return {
magic: buf.readUInt32BE(0),
versi: buf.readUInt8(4),
tipe: buf.readUInt8(5),
panjangPayload: buf.readUInt32BE(6),
};
}
function tulisHeader(header: HeaderPaket): Buffer {
const buf = Buffer.alloc(10); // total 10 byte header
buf.writeUInt32BE(header.magic, 0);
buf.writeUInt8(header.versi, 4);
buf.writeUInt8(header.tipe, 5);
buf.writeUInt32BE(header.panjangPayload, 6);
return buf;
}
// membuat paket lengkap: header + payload
function buatPaket(tipe: number, payload: Buffer): Buffer {
const header = tulisHeader({
magic: 0xCAFEBABE, // magic number kustom
versi: 1,
tipe,
panjangPayload: payload.length,
});
return Buffer.concat([header, payload]);
}
Operasi Buffer #
Slice dan Subarray #
const buf = Buffer.from("Hello, World!", "utf-8");
// subarray — view ke Buffer yang sama (berbagi memori!)
const sub = buf.subarray(7, 12);
console.log(sub.toString()); // "World"
// PENTING: subarray berbagi memori dengan buffer asli
sub[0] = 0x77; // ubah 'W' jadi 'w'
console.log(buf.toString()); // "Hello, world!" — buffer asli juga berubah!
// BENAR: gunakan Buffer.from() untuk membuat salinan independen
const salinan = Buffer.from(buf.subarray(7, 12));
salinan[0] = 0x57; // ubah kembali ke 'W'
console.log(buf.toString()); // "Hello, world!" — buffer asli tidak berubah
Concat — Menggabungkan Buffer #
const buf1 = Buffer.from("Hello, ");
const buf2 = Buffer.from("World");
const buf3 = Buffer.from("!");
// ANTI-PATTERN: gabung Buffer dengan + seperti string
// Buffer + Buffer tidak berfungsi seperti yang diharapkan
// BENAR: Buffer.concat()
const gabungan = Buffer.concat([buf1, buf2, buf3]);
console.log(gabungan.toString()); // "Hello, World!"
// dengan panjang total yang diketahui — lebih efisien
const totalPanjang = buf1.length + buf2.length + buf3.length;
const gabunganEfisien = Buffer.concat([buf1, buf2, buf3], totalPanjang);
// gabungkan chunks dari stream
async function kumpulkanStream(stream: NodeJS.ReadableStream): Promise<Buffer> {
const chunks: Buffer[] = [];
for await (const chunk of stream) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}
return Buffer.concat(chunks);
}
Copy dan Fill #
const sumber = Buffer.from("Hello, World!");
const tujuan = Buffer.alloc(5);
// copy — salin byte dari satu buffer ke buffer lain
sumber.copy(
tujuan, // target buffer
0, // offset di target (mulai tulis di sini)
7, // offset mulai baca dari sumber
12 // offset akhir baca dari sumber (eksklusif)
);
console.log(tujuan.toString()); // "World"
// fill — isi buffer dengan nilai tertentu
const bufFill = Buffer.alloc(10);
bufFill.fill(0xAA);
console.log(bufFill); // <Buffer aa aa aa aa aa aa aa aa aa aa>
bufFill.fill(0, 5); // isi dari offset 5 dengan nol
console.log(bufFill); // <Buffer aa aa aa aa aa 00 00 00 00 00>
bufFill.fill("ab", 0, 4, "utf-8"); // isi dengan string
console.log(bufFill.toString("utf-8", 0, 4)); // "abab"
Perbandingan Buffer #
const a = Buffer.from("abc");
const b = Buffer.from("abc");
const c = Buffer.from("abd");
// equals — cek apakah dua buffer identik
console.log(a.equals(b)); // true
console.log(a.equals(c)); // false
// compare — bandingkan secara lexicographic
// mengembalikan 0 (sama), 1 (a > b), -1 (a < b)
console.log(a.compare(b)); // 0
console.log(a.compare(c)); // -1 (karena 'c' < 'd')
console.log(c.compare(a)); // 1
// urutkan array of Buffer
const buffers = [
Buffer.from("banana"),
Buffer.from("apple"),
Buffer.from("cherry"),
];
buffers.sort(Buffer.compare);
console.log(buffers.map((b) => b.toString()));
// ["apple", "banana", "cherry"]
// indexOf — cari posisi byte atau sub-buffer
const haystack = Buffer.from("Hello, World! Hello!");
const needle = Buffer.from("Hello");
console.log(haystack.indexOf(needle)); // 0
console.log(haystack.indexOf(needle, 1)); // 14 — mulai cari dari offset 1
// includes — cek apakah buffer mengandung byte atau sub-buffer
console.log(haystack.includes("World")); // true
console.log(haystack.includes(0x21)); // true (! = 0x21)
Buffer dan Stream #
Buffer dan stream adalah dua sisi dari koin yang sama — stream mengalirkan data dalam bentuk chunk Buffer.
import { Writable, Readable, Transform } from "stream";
// kumpulkan seluruh stream menjadi satu Buffer
async function streamKeBuffer(readable: NodeJS.ReadableStream): Promise<Buffer> {
const chunks: Buffer[] = [];
for await (const chunk of readable) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
}
return Buffer.concat(chunks);
}
// ubah Buffer menjadi Readable stream (berguna untuk testing atau piping)
function bufferKeStream(buf: Buffer): Readable {
const stream = new Readable();
stream.push(buf);
stream.push(null); // sinyal end of stream
return stream;
}
// Transform stream: proses data chunk by chunk
class XORTransform extends Transform {
constructor(private kunci: number) {
super();
}
_transform(chunk: Buffer, _encoding: string, callback: () => void): void {
const hasil = Buffer.alloc(chunk.length);
for (let i = 0; i < chunk.length; i++) {
hasil[i] = chunk[i] ^ this.kunci; // XOR setiap byte dengan kunci
}
this.push(hasil);
callback();
}
}
// contoh: enkripsi sederhana dengan XOR (hanya untuk ilustrasi, bukan production!)
async function xorFile(input: Buffer, kunci: number): Promise<Buffer> {
const chunks: Buffer[] = [];
const transform = new XORTransform(kunci);
transform.on("data", (chunk: Buffer) => chunks.push(chunk));
await new Promise<void>((resolve, reject) => {
transform.on("end", resolve);
transform.on("error", reject);
transform.end(input);
});
return Buffer.concat(chunks);
}
Pola Umum di Aplikasi Nyata #
Membaca File Biner dan Parsing Strukturnya #
import { promises as fs } from "fs";
// contoh: parse header file PNG
// PNG signature: 8 byte pertama selalu 89 50 4E 47 0D 0A 1A 0A
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
interface InfoPNG {
valid: boolean;
lebar: number;
tinggi: number;
bitDepth: number;
colorType: number;
}
async function bacaInfoPNG(filePath: string): Promise<InfoPNG> {
const buf = await fs.readFile(filePath);
// verifikasi signature
const signature = buf.subarray(0, 8);
if (!signature.equals(PNG_SIGNATURE)) {
return { valid: false, lebar: 0, tinggi: 0, bitDepth: 0, colorType: 0 };
}
// chunk IHDR dimulai di offset 8
// struktur IHDR: 4 byte panjang + 4 byte tipe + 13 byte data + 4 byte CRC
// data IHDR: lebar (4) + tinggi (4) + bitDepth (1) + colorType (1) + ...
const ihdrOffset = 8 + 4 + 4; // lewati length dan type chunk
return {
valid: true,
lebar: buf.readUInt32BE(ihdrOffset),
tinggi: buf.readUInt32BE(ihdrOffset + 4),
bitDepth: buf.readUInt8(ihdrOffset + 8),
colorType: buf.readUInt8(ihdrOffset + 9),
};
}
// contoh: serialize dan deserialize data terstruktur ke format biner kompak
interface RecordData {
id: number; // 4 byte uint32
timestamp: bigint; // 8 byte uint64
nilai: number; // 8 byte double
label: string; // 2 byte panjang + N byte utf-8
}
function serializeRecord(record: RecordData): Buffer {
const labelBuf = Buffer.from(record.label, "utf-8");
const totalSize = 4 + 8 + 8 + 2 + labelBuf.length;
const buf = Buffer.alloc(totalSize);
let offset = 0;
buf.writeUInt32BE(record.id, offset); offset += 4;
buf.writeBigUInt64BE(record.timestamp, offset); offset += 8;
buf.writeDoubleBE(record.nilai, offset); offset += 8;
buf.writeUInt16BE(labelBuf.length, offset); offset += 2;
labelBuf.copy(buf, offset);
return buf;
}
function deserializeRecord(buf: Buffer): RecordData {
let offset = 0;
const id = buf.readUInt32BE(offset); offset += 4;
const timestamp = buf.readBigUInt64BE(offset); offset += 8;
const nilai = buf.readDoubleBE(offset); offset += 8;
const labelLength = buf.readUInt16BE(offset); offset += 2;
const label = buf.toString("utf-8", offset, offset + labelLength);
return { id, timestamp, nilai, label };
}
// round-trip test
const record: RecordData = {
id: 42,
timestamp: BigInt(Date.now()),
nilai: 3.14159,
label: "pengukuran-sensor-A",
};
const serialized = serializeRecord(record);
const deserialized = deserializeRecord(serialized);
console.log(deserialized.id); // 42
console.log(deserialized.label); // "pengukuran-sensor-A"
Konversi Data untuk API dan Web #
// encode file ke base64 untuk dikirim via JSON API
async function fileKeBase64(filePath: string): Promise<string> {
const buf = await fs.readFile(filePath);
return buf.toString("base64");
}
// decode base64 dari API dan simpan sebagai file
async function base64KeFile(base64: string, outputPath: string): Promise<void> {
const buf = Buffer.from(base64, "base64");
await fs.writeFile(outputPath, buf);
}
// buat data URL dari file (untuk embed di HTML/CSS)
async function fileKeDataURL(filePath: string, mimeType: string): Promise<string> {
const buf = await fs.readFile(filePath);
const base64 = buf.toString("base64");
return `data:${mimeType};base64,${base64}`;
}
// contoh: embed logo ke HTML
const logoDataURL = await fileKeDataURL("logo.png", "image/png");
const html = `<img src="${logoDataURL}" alt="Logo" />`;
// hitung hash MD5 dari Buffer (untuk cache busting atau ETag)
import { createHash } from "crypto";
function hashBuffer(buf: Buffer, algoritma = "md5"): string {
return createHash(algoritma).update(buf).digest("hex");
}
// generate ETag untuk response HTTP
function generateETag(konten: Buffer | string): string {
const buf = Buffer.isBuffer(konten) ? konten : Buffer.from(konten);
return `"${hashBuffer(buf, "sha256").slice(0, 16)}"`;
}
// cek apakah dua file identik secara efisien
async function fileIdentik(path1: string, path2: string): Promise<boolean> {
const [buf1, buf2] = await Promise.all([
fs.readFile(path1),
fs.readFile(path2),
]);
if (buf1.length !== buf2.length) return false;
return buf1.equals(buf2);
}
Buffer Pool — Alokasi Efisien #
Untuk aplikasi yang sering mengalokasikan Buffer kecil dalam jumlah besar, pooling memori bisa meningkatkan performa secara signifikan.
// Node.js sudah punya built-in pooling untuk Buffer.allocUnsafe() < 4KB
// tapi untuk kontrol lebih, bisa buat pool sendiri
class BufferPool {
private pool: Buffer;
private offset: number = 0;
private readonly ukuranPool: number;
constructor(ukuranPool: number = 64 * 1024) { // default 64KB
this.ukuranPool = ukuranPool;
this.pool = Buffer.allocUnsafe(ukuranPool);
}
// ambil slice dari pool — sangat cepat, tidak alokasi memori baru
ambil(ukuran: number): Buffer {
if (ukuran > this.ukuranPool) {
// terlalu besar untuk pool — alokasi normal
return Buffer.allocUnsafe(ukuran);
}
if (this.offset + ukuran > this.ukuranPool) {
// pool habis — buat pool baru
this.pool = Buffer.allocUnsafe(this.ukuranPool);
this.offset = 0;
}
const slice = this.pool.subarray(this.offset, this.offset + ukuran);
this.offset += ukuran;
return slice;
}
}
const pool = new BufferPool();
// alokasi buffer kecil dari pool — lebih efisien dari Buffer.alloc setiap kali
const buf = pool.ambil(128);
buf.fill(0); // selalu isi dulu karena dari allocUnsafe
Kapan Menggunakan Buffer #
Gunakan Buffer untuk:
✓ Membaca dan menulis file biner — gambar, PDF, audio, video
✓ Data jaringan — socket TCP, protokol biner kustom
✓ Kriptografi — input/output hash, enkripsi, tanda tangan digital
✓ Encoding konversi — hex ↔ base64 ↔ utf-8
✓ Parsing format biner terstruktur — header file, protokol jaringan
✓ Transfer data biner via JSON menggunakan base64
✓ Operasi yang butuh kontrol langsung atas byte individual
Tidak perlu Buffer jika:
✗ Hanya bekerja dengan teks — gunakan string biasa
✗ Data sudah dalam format JSON — tidak perlu konversi ke Buffer
✗ Operasi string sederhana — split, replace, match
Ringkasan #
Buffer.from()untuk membuat Buffer dari sumber yang ada — string, hex, base64, array of bytes, atau Buffer lain. Selalu tentukan encoding secara eksplisit untuk menghindari ambiguitas.Buffer.alloc()bukanBuffer.allocUnsafe()untuk kode umum —allocmelakukan zero-fill yang aman;allocUnsafehanya untuk optimasi performa saat kamu yakin mengisi seluruh buffer sebelum dibaca.subarray()berbagi memori dengan buffer asli — modifikasi pada subarray mengubah buffer aslinya. GunakanBuffer.from(buf.subarray(...))untuk mendapatkan salinan yang independen.Buffer.concat()untuk menggabungkan buffer — tidak ada operator+untuk Buffer; selalu gunakanBuffer.concat([...buffers]).- Tentukan endianness dengan tepat — gunakan
BE(Big-Endian) untuk protokol jaringan danLE(Little-Endian) untuk format file yang dibuat di x86/x64; salah endianness menghasilkan angka yang salah tanpa error.equals()untuk membandingkan isi buffer — operator===hanya membandingkan referensi, bukan isi. Untuk keamanan, gunakancrypto.timingSafeEqual().Buffer.concat(chunks)setelah mengumpulkan semua chunk stream — kumpulkan chunk ke array terlebih dahulu, baru concat sekali di akhir; jangan concat satu per satu di dalam loop karena menghasilkan alokasi memori O(n²).- Base64 untuk transfer data biner via JSON — gunakan
buf.toString('base64')untuk encode danBuffer.from(str, 'base64')untuk decode saat perlu menyertakan data biner dalam JSON API.