Buffers
A Buffer is Node's view onto a chunk of raw binary data — a fixed-length sequence of bytes living outside the V8 JavaScript heap. Long before JavaScript had typed arrays, Node needed a way to handle the bytes flowing from files, sockets, and processes; Buffer is that tool. Today it's a subclass of Uint8Array, so it's both a Node original and a first-class typed array. Understanding buffers is the key to streams, networking, and any binary protocol.
Why buffers exist
JavaScript strings are sequences of characters (UTF-16 code units) — great for text, useless for a PNG, a TCP packet, or a gzip stream. Those are sequences of bytes. A Buffer represents exactly that: a contiguous block of memory you can read and write one byte at a time, with no charset assumptions until you choose one.
Creating buffers
// From text (default UTF-8):
const a = Buffer.from('héllo') // bytes of the UTF-8 encoding
// From a byte array:
const b = Buffer.from([0x48, 0x69]) // <Buffer 48 69> → 'Hi'
// Zero-filled, fixed size (safe):
const c = Buffer.alloc(8) // <Buffer 00 00 00 00 00 00 00 00>
// Uninitialized, faster, but contains OLD memory (unsafe):
const d = Buffer.allocUnsafe(8) // <Buffer ?? ?? ...> — must overwrite!Buffers and encodings
A buffer is just bytes. Turning bytes ↔ text always requires choosing an encoding. Get it wrong and you get garbled output or corrupted data:
const buf = Buffer.from('hello', 'utf8')
buf.toString('utf8') // 'hello'
buf.toString('hex') // '68656c6c6f'
buf.toString('base64') // 'aGVsbG8='
// Round-trip through base64:
const encoded = Buffer.from('data').toString('base64') // 'ZGF0YQ=='
Buffer.from(encoded, 'base64').toString('utf8') // 'data'Encoding | Use for |
|---|---|
| Default; text (multi-byte aware) |
| Legacy 7-bit text (strips high bit) |
| Two hex chars per byte — debugging, hashes |
| Binary-safe transport (email, URLs, JSON) |
| One byte per char, 0–255 — raw byte↔char |
| UTF-16, little-endian |
Length is in bytes, not characters
'héllo'.length // 5 (characters)
Buffer.from('héllo').length // 6 (UTF-8 bytes)
Buffer.byteLength('héllo', 'utf8') // 6 (size it correctly)5 6 6
Reading and writing bytes
Buffers are indexable like arrays, and offer typed read/write methods for multi-byte integers with explicit endianness — the bread and butter of binary protocols:
const buf = Buffer.alloc(4) buf[0] = 0xde // index access, one byte (0–255) buf.writeUInt16BE(0xbeef, 1) // write 2 bytes big-endian at offset 1 console.log(buf) // <Buffer de be ef 00> buf.readUInt8(0) // 222 (0xde) buf.readUInt16BE(1) // 48879 (0xbeef)
Slicing shares memory — beware
const original = Buffer.from([1, 2, 3, 4]) const view = original.subarray(0, 2) // shares memory view[0] = 99 console.log(original) // <Buffer 63 02 03 04> — original changed! const copy = Buffer.from(original.subarray(0, 2)) // independent copy[0] = 0 console.log(original[0]) // 99 — unaffected
Combining and comparing
const a = Buffer.from('foo')
const b = Buffer.from('bar')
Buffer.concat([a, b]) // <Buffer 66 6f 6f 62 61 72> → 'foobar'
Buffer.concat([a, b], 4) // limit total length to 4 bytes
a.equals(Buffer.from('foo')) // true — byte-for-byte comparison
Buffer.compare(a, b) // 1 / 0 / -1 — for sortingBuffer is a Uint8Array
Every Buffer is a Uint8Array, so it works anywhere a typed array does — TextDecoder, crypto, fetch bodies, the Web Streams API. The reverse isn't automatic, but you can wrap a typed array's memory without copying:
const u8 = new Uint8Array([72, 105]) const buf = Buffer.from(u8.buffer) // shares the ArrayBuffer (no copy) buf instanceof Uint8Array // true new TextDecoder().decode(buf) // 'Hi' (Web-standard decoding)
Quick reference
Need | Use |
|---|---|
Safe zero-filled buffer |
|
Fast buffer (will overwrite) |
|
From text |
|
Bytes → text |
|
Correct text byte size |
|
Join buffers |
|
Independent copy of a slice |
|