Back to Blog
Node.jsBufferBinaryData

Node.js Buffer Handling

Master Node.js Buffers. From creation to manipulation to encoding and binary data.

B
Bootspring Team
Engineering
August 13, 2020
6 min read

Buffers handle binary data in Node.js. Here's how to use them effectively.

Creating Buffers#

1// Allocate buffer with size 2const buf1 = Buffer.alloc(10); // 10 bytes, initialized to 0 3const buf2 = Buffer.allocUnsafe(10); // 10 bytes, not initialized (faster) 4 5// From string 6const buf3 = Buffer.from('Hello World'); 7const buf4 = Buffer.from('Hello World', 'utf8'); 8const buf5 = Buffer.from('48656c6c6f', 'hex'); 9const buf6 = Buffer.from('SGVsbG8=', 'base64'); 10 11// From array 12const buf7 = Buffer.from([72, 101, 108, 108, 111]); // 'Hello' 13 14// From another buffer 15const buf8 = Buffer.from(buf3); 16 17// From ArrayBuffer 18const arrayBuffer = new ArrayBuffer(10); 19const buf9 = Buffer.from(arrayBuffer); 20 21// Concatenate buffers 22const combined = Buffer.concat([buf1, buf3]);

Reading and Writing#

1const buf = Buffer.alloc(10); 2 3// Write methods 4buf.writeUInt8(255, 0); // Write 8-bit unsigned int at offset 0 5buf.writeUInt16LE(1000, 1); // Write 16-bit little-endian at offset 1 6buf.writeUInt16BE(1000, 3); // Write 16-bit big-endian at offset 3 7buf.writeInt32LE(-100, 5); // Write 32-bit signed little-endian 8 9// Read methods 10const value1 = buf.readUInt8(0); // 255 11const value2 = buf.readUInt16LE(1); // 1000 12const value3 = buf.readUInt16BE(3); // 1000 13const value4 = buf.readInt32LE(5); // -100 14 15// Float and double 16const floatBuf = Buffer.alloc(8); 17floatBuf.writeFloatLE(3.14, 0); 18floatBuf.writeDoubleLE(3.14159265359, 0); 19 20const pi = floatBuf.readFloatLE(0); 21const piPrecise = floatBuf.readDoubleLE(0); 22 23// BigInt (64-bit) 24const bigBuf = Buffer.alloc(8); 25bigBuf.writeBigInt64LE(BigInt('9223372036854775807'), 0); 26const bigValue = bigBuf.readBigInt64LE(0);

String Encoding#

1const str = 'Hello, 世界!'; 2 3// Encode to buffer 4const utf8Buf = Buffer.from(str, 'utf8'); 5const utf16Buf = Buffer.from(str, 'utf16le'); 6 7console.log(utf8Buf.length); // 14 bytes 8console.log(utf16Buf.length); // 22 bytes 9 10// Decode to string 11const decoded = utf8Buf.toString('utf8'); 12 13// Supported encodings: 14// - utf8, utf-8 15// - utf16le, utf-16le 16// - latin1, binary 17// - base64, base64url 18// - hex 19// - ascii 20 21// Encoding conversions 22const hexString = utf8Buf.toString('hex'); 23// '48656c6c6f2c20e4b896e7958c21' 24 25const base64 = utf8Buf.toString('base64'); 26// 'SGVsbG8sIOS4lueVjCE=' 27 28// Convert between encodings 29function convertEncoding(str, from, to) { 30 return Buffer.from(str, from).toString(to); 31} 32 33convertEncoding('48656c6c6f', 'hex', 'utf8'); // 'Hello'

Buffer Manipulation#

1const buf = Buffer.from('Hello World'); 2 3// Slice (creates view, not copy) 4const slice = buf.slice(0, 5); // 'Hello' 5slice[0] = 74; // Changes original too! 6 7// Subarray (same as slice) 8const sub = buf.subarray(6, 11); // 'World' 9 10// Copy (creates actual copy) 11const copy = Buffer.alloc(5); 12buf.copy(copy, 0, 0, 5); 13 14// Fill 15const fillBuf = Buffer.alloc(10); 16fillBuf.fill('a'); // 'aaaaaaaaaa' 17fillBuf.fill(0); // All zeros 18 19// Compare 20const a = Buffer.from('abc'); 21const b = Buffer.from('abd'); 22console.log(a.compare(b)); // -1 (a < b) 23console.log(Buffer.compare(a, b)); // -1 24 25// Equals 26console.log(a.equals(Buffer.from('abc'))); // true 27 28// Index operations 29const indexBuf = Buffer.from('Hello'); 30console.log(indexBuf.indexOf('l')); // 2 31console.log(indexBuf.lastIndexOf('l')); // 3 32console.log(indexBuf.includes('ell')); // true

Working with Files#

1const fs = require('fs'); 2 3// Read file as buffer 4const data = fs.readFileSync('image.png'); 5console.log(data); // <Buffer 89 50 4e 47 ...> 6 7// Check file type by magic bytes 8function getFileType(buffer) { 9 const signatures = { 10 png: [0x89, 0x50, 0x4e, 0x47], 11 jpg: [0xff, 0xd8, 0xff], 12 gif: [0x47, 0x49, 0x46, 0x38], 13 pdf: [0x25, 0x50, 0x44, 0x46], 14 }; 15 16 for (const [type, sig] of Object.entries(signatures)) { 17 if (sig.every((byte, i) => buffer[i] === byte)) { 18 return type; 19 } 20 } 21 22 return 'unknown'; 23} 24 25// Write buffer to file 26const imageData = Buffer.from([0x89, 0x50, 0x4e, 0x47, /* ... */]); 27fs.writeFileSync('output.png', imageData); 28 29// Stream processing 30const readStream = fs.createReadStream('large.bin'); 31readStream.on('data', (chunk) => { 32 // chunk is a Buffer 33 processChunk(chunk); 34});

Binary Protocols#

1// Simple binary protocol 2class BinaryProtocol { 3 static encode(message) { 4 const payload = Buffer.from(JSON.stringify(message)); 5 const header = Buffer.alloc(4); 6 header.writeUInt32BE(payload.length, 0); 7 return Buffer.concat([header, payload]); 8 } 9 10 static decode(buffer) { 11 const length = buffer.readUInt32BE(0); 12 const payload = buffer.slice(4, 4 + length); 13 return JSON.parse(payload.toString()); 14 } 15} 16 17// Fixed-length record 18class Record { 19 constructor(id, name, score) { 20 this.id = id; 21 this.name = name.padEnd(20, '\0'); 22 this.score = score; 23 } 24 25 toBuffer() { 26 const buf = Buffer.alloc(28); // 4 + 20 + 4 27 buf.writeUInt32BE(this.id, 0); 28 buf.write(this.name, 4, 20, 'utf8'); 29 buf.writeFloatBE(this.score, 24); 30 return buf; 31 } 32 33 static fromBuffer(buf) { 34 const id = buf.readUInt32BE(0); 35 const name = buf.slice(4, 24).toString('utf8').replace(/\0/g, ''); 36 const score = buf.readFloatBE(24); 37 return new Record(id, name, score); 38 } 39} 40 41// Usage 42const record = new Record(1, 'Alice', 95.5); 43const buf = record.toBuffer(); 44const restored = Record.fromBuffer(buf);

Crypto Operations#

1const crypto = require('crypto'); 2 3// Hash a buffer 4function hashBuffer(buffer, algorithm = 'sha256') { 5 return crypto.createHash(algorithm).update(buffer).digest(); 6} 7 8const data = Buffer.from('Hello World'); 9const hash = hashBuffer(data); 10console.log(hash.toString('hex')); 11 12// Generate random bytes 13const randomBytes = crypto.randomBytes(32); 14 15// Encrypt/decrypt with buffer 16function encrypt(buffer, key, iv) { 17 const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); 18 return Buffer.concat([cipher.update(buffer), cipher.final()]); 19} 20 21function decrypt(buffer, key, iv) { 22 const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); 23 return Buffer.concat([decipher.update(buffer), decipher.final()]); 24} 25 26// HMAC 27function hmac(buffer, key) { 28 return crypto.createHmac('sha256', key).update(buffer).digest(); 29}

ArrayBuffer Interop#

1// Buffer to ArrayBuffer 2const buffer = Buffer.from('Hello'); 3const arrayBuffer = buffer.buffer.slice( 4 buffer.byteOffset, 5 buffer.byteOffset + buffer.byteLength 6); 7 8// ArrayBuffer to Buffer 9const ab = new ArrayBuffer(8); 10const buf = Buffer.from(ab); 11 12// TypedArray interop 13const uint8 = new Uint8Array([1, 2, 3, 4]); 14const bufFromTyped = Buffer.from(uint8.buffer); 15 16// Shared memory (careful!) 17const sharedBuf = Buffer.from(uint8.buffer); 18sharedBuf[0] = 99; 19console.log(uint8[0]); // 99 - they share memory! 20 21// Safe copy 22const safeCopy = Buffer.from(uint8); 23safeCopy[0] = 99; 24console.log(uint8[0]); // 1 - original unchanged

Performance Tips#

1// Pre-allocate for known sizes 2const fixedBuf = Buffer.allocUnsafe(1024); 3 4// Reuse buffers 5class BufferPool { 6 constructor(size, count) { 7 this.pool = Array.from({ length: count }, () => Buffer.alloc(size)); 8 this.available = [...this.pool]; 9 } 10 11 acquire() { 12 return this.available.pop() || Buffer.alloc(this.pool[0].length); 13 } 14 15 release(buffer) { 16 buffer.fill(0); 17 if (this.available.length < this.pool.length) { 18 this.available.push(buffer); 19 } 20 } 21} 22 23// Avoid string concatenation in hot paths 24// BAD: 25let result = ''; 26for (const chunk of chunks) { 27 result += chunk.toString(); 28} 29 30// GOOD: 31const result = Buffer.concat(chunks).toString();

Best Practices#

Memory: ✓ Use Buffer.alloc for sensitive data ✓ Use Buffer.allocUnsafe for performance ✓ Pool buffers when allocating many ✓ Clear sensitive data when done Encoding: ✓ Always specify encoding ✓ Use Buffer.byteLength for string sizes ✓ Handle multi-byte characters properly ✓ Validate input encodings Safety: ✓ Check bounds before reading ✓ Use slice/subarray carefully ✓ Copy when sharing externally ✓ Zero sensitive buffers Performance: ✓ Avoid unnecessary conversions ✓ Use concat over += for binary ✓ Pre-allocate when size is known ✓ Stream large data

Conclusion#

Buffers are essential for binary data in Node.js. Understand encoding conversions, use appropriate read/write methods for data types, and be mindful of memory sharing. Use Buffer.alloc for security, allocUnsafe for performance, and always validate bounds when working with binary protocols.

Share this article

Help spread the word about Bootspring