jsguides

Buffer

The Buffer class is a global type in Node.js for working with binary data directly. It represents a fixed-length sequence of bytes. Many Node.js APIs use Buffers to handle file I/O, network communications, and other operations that involve raw binary data.

In early Node.js versions, Buffers were the primary way to work with binary data because JavaScript had no native support for typed arrays. While TypedArrays now exist in the ECMAScript standard, Buffer remains essential in Node.js for performance-critical operations and interoperability with file systems, networks, and native modules.

Creating Buffers

Buffer provides several static methods for creating new buffers:

Buffer.alloc()

Allocates a zero-filled buffer of a specific size:

const buf = Buffer.alloc(10);
console.log(buf); // <Buffer 00 00 00 00 00 00 00 00 00 00>

The zero-filled allocation is the safe default for general use. When the buffer will hold string data, you can pass a fill value directly to Buffer.alloc() to avoid a separate fill() call later. This is both cleaner and eliminates a pass over the memory.

You can pre-fill the buffer with a specific value and encoding:

const buf = Buffer.alloc(10, 'a');
console.log(buf.toString()); // "aaaaaaaaaa"

const utf8Buf = Buffer.alloc(10, '\u00ff', 'utf8');

Buffer.allocUnsafe()

Creates an uninitialized buffer. This is faster than Buffer.alloc() because it skips zero-filling, but the buffer may contain old data from memory. Always overwrite the contents before use. The performance gain matters in hot paths where you immediately fill every byte with your own data, but the tradeoff is that any uninitialized bytes may leak previous memory contents like passwords or keys.

const buf = Buffer.allocUnsafe(10);
console.log(buf); // May contain random data from memory
buf.fill(0); // Always initialize before use

Buffer.from()

Creates a buffer from an existing source. Unlike alloc() which gives you empty space, from() wraps data you already have—a string, an array of byte values, or even another buffer. Each source type is handled with the right encoding automatically.

const buf1 = Buffer.from('hello');
const buf2 = Buffer.from([1, 2, 3, 4]);
const buf3 = Buffer.from(Buffer.from('world'));

Reading and Writing

Writing strings

Use buf.write() to write a string at a specific offset. This allows you to build up buffer contents piece by piece—write a header at offset 0, then append payload data further in, all within the same allocation. The method returns the number of bytes written, which you can check to confirm the write succeeded.

const buf = Buffer.alloc(10);
buf.write('hello');
console.log(buf.toString()); // "hello"
buf.write('world', 5); // Write at offset 5
console.log(buf.toString()); // "helloworld"

Reading as string

Use buf.toString() to convert buffer contents back to a string. You can pass a start and end offset to extract a slice without allocating a separate buffer, which is handy when parsing binary protocols where different fields sit at known byte positions.

const buf = Buffer.from('hello world');
console.log(buf.toString()); // "hello world"
console.log(buf.toString('utf8', 0, 5)); // "hello"

Reading and writing numeric values

Buffer provides methods for reading and writing integers and floats at specific byte offsets. Each method accepts an offset into the buffer and supports both big-endian (BE) and little-endian (LE) byte order, which is critical when exchanging data with network protocols or binary file formats.

const buf = Buffer.alloc(8);

// Write a 32-bit integer at offset 0
buf.writeInt32BE(12345, 0);
console.log(buf.readInt32BE(0)); // 12345

// Write a 16-bit unsigned integer
buf.writeUInt16LE(256, 4);
console.log(buf.readUInt16LE(4)); // 256

// Little-endian vs big-endian
buf.writeUInt32LE(0x12345678, 0);
console.log(buf.readUInt32LE(0).toString(16)); // "12345678"

The LE suffix means little-endian byte order. Use BE for big-endian.

Buffer Operations

concat()

Merges multiple buffers into a single buffer. This is useful when you receive data in chunks—from a stream or a series of read operations—and need to reassemble the complete payload before processing.

const buf1 = Buffer.from('hello');
const buf2 = Buffer.from(' ');
const buf3 = Buffer.from('world');

const combined = Buffer.concat([buf1, buf2, buf3]);
console.log(combined.toString()); // "hello world"

copy()

Copies data from one buffer to another. Unlike concat() which creates a new buffer, copy() writes into an existing target buffer at the offset you specify, making it the better choice when you need to layer data into a pre-allocated region.

const source = Buffer.from('hello world');
const target = Buffer.alloc(20);

source.copy(target);
console.log(target.toString()); // "hello world"

source.copy(target, 0, 6, 11); // Copy from offset 6 to 11
console.log(target.toString()); // "world world"

slice()

Returns a new buffer referencing a portion of the original. The returned buffer shares memory with the original. This shared-memory design means modifying the slice also mutates the source buffer, so treat slices as views rather than independent copies.

const buf = Buffer.from('hello world');
const slice = buf.slice(0, 5);

console.log(slice.toString()); // "hello"
slice[0] = 97; // Changes both buffers (shared memory)
console.log(buf.toString()); // "aello world"

fill()

Fills the buffer with a specified value. When you pass start and end offsets, fill() only touches that range, leaving surrounding bytes intact. This is convenient for resetting a sub-region without re-allocating the entire buffer, which is especially useful when managing reusable buffer pools.

const buf = Buffer.alloc(10);
buf.fill('a');
console.log(buf.toString()); // "aaaaaaaaaa"

buf.fill('b', 2, 5); // Fill from index 2 to 5
console.log(buf.toString()); // "aabbbaaaaa"

Buffers and TypedArrays

Buffer is a subclass of Uint8Array. This means you can use TypedArray methods on buffers and pass buffers wherever TypedArrays are expected. This inheritance gives Buffer access to the full TypedArray API without any conversion step—methods like subarray(), includes(), and indexOf() work directly.

const buf = Buffer.from([1, 2, 3, 4, 5]);

// Use TypedArray methods
const subarray = buf.subarray(1, 3);
console.log(subarray.toString()); // "2,3"

console.log(buf.includes(3)); // true
console.log(buf.indexOf(3)); // 2

Buffer also works the other way—pass a TypedArray to Buffer.from() to copy its contents. This is the bridge when you have data from a Web API that returns TypedArrays (like fetch response bodies or canvas pixel data) and you need to hand it off to a Node.js API that expects a Buffer.

const typedArray = new Uint8Array([1, 2, 3, 4, 5]);
const buf = Buffer.from(typedArray);
console.log(buf.toString()); // "\x01\x02\x03\x04\x05"

The underlying ArrayBuffer can be shared between Buffer and TypedArrays for zero-copy operations. Instead of copying data, you can point a TypedArray at the same underlying memory as a Buffer. Changes made through either view are visible through the other, which eliminates allocation and copy overhead when passing data between Web and Node.js APIs.

const buf = Buffer.alloc(10);
const typedArray = new Uint8Array(buf.buffer, buf.byteOffset, buf.length);

Character Encodings

When converting between buffers and strings, you can specify a character encoding. The choice of encoding affects both the byte count of the resulting buffer and whether certain characters survive the round trip—picking the wrong one can silently corrupt data.

EncodingDescription
utf8Default. Multi-byte Unicode (default)
utf16leLittle-endian UTF-16
latin1ISO-8859-1, single byte per character
base64Base64-encoded string
hexHexadecimal, two chars per byte
const str = 'Hello, 世界';

const utf8 = Buffer.from(str, 'utf8');
console.log(utf8.toString('hex')); // "48656c6c6f2c e4b896e7958c"
console.log(utf8.toString('base64')); // "SGVsbG8g5Li55aG9"
console.log(utf8.toString('latin1')); // "Hello, ????"

Security Note

Buffer.allocUnsafe() and Buffer.allocUnsafeSlow() allocate memory without initializing it. The memory may contain sensitive data from previous operations, including passwords, encryption keys, or other private information.

Never use these methods when:

  • Creating buffers that might be exposed to other code
  • Handling sensitive data without immediately filling the buffer

For most cases, use Buffer.alloc() instead. It is slightly slower but guarantees the buffer is zero-filled.

If you must use Buffer.allocUnsafe() for performance reasons, always fill the buffer completely before exposing it:

const buf = Buffer.allocUnsafe(1024);
// ... write your data to the buffer ...
// Do NOT expose buf until you have written to every byte

See Also

  • Fs — File system operations that commonly use Buffers
  • Stream — Streams often consume and produce Buffer data
  • Crypto — Cryptographic operations work extensively with binary data