Working with binary representations of IDs using toBytes and fromBytes
Many ID formats have canonical binary representations that are more efficient for storage and network transmission. Uniku provides toBytes() and fromBytes() for converting between string and binary formats.
Why don’t CUID2 and Nanoid have byte conversion?CUID2 and Nanoid are string-native formats with variable-length, Base36/Base64 encoding. They don’t have a standardized binary representation. You could convert the string to UTF-8 bytes, but that’s just storing the string representation.
Store IDs as binary for more efficient storage and indexing:
import { uuidv7 } from 'uniku/uuid/v7'// PostgreSQL example with binary UUID storageconst id = uuidv7()const bytes = uuidv7.toBytes(id)// Store as BYTEA (more compact than TEXT)await db.query( 'INSERT INTO users (id, name) VALUES ($1, $2)', [bytes, 'Alice'])// Retrieve and convert backconst result = await db.query('SELECT id FROM users WHERE name = $1', ['Alice'])const storedId = uuidv7.fromBytes(result.rows[0].id)
Storage savings:
UUID string: 36 bytes (with dashes) or 32 bytes (without)
import { uuidv7 } from 'uniku/uuid/v7'// Allocate bufferconst buffer = new Uint8Array(32)// Write first UUID at offset 0uuidv7(undefined, buffer, 0)// Write second UUID at offset 16uuidv7(undefined, buffer, 16)// Now buffer contains two UUIDs (32 bytes total)
You can provide custom random bytes and write to a buffer simultaneously:
import { uuidv4 } from 'uniku/uuid/v4'const customRandom = new Uint8Array(16)crypto.getRandomValues(customRandom)const buffer = new Uint8Array(32)// Generate UUID with custom random bytes, write to buffer at offset 8uuidv4({ random: customRandom }, buffer, 8)// Note: customRandom is modified in-place for version/variant bits
Important: For UUID v4, bytes at index 6 and 8 of the random array are modified in-place to set the version and variant bits. If you need to preserve the original random bytes, make a copy first.
import { uuidv7 } from 'uniku/uuid/v7'import { Pool } from 'pg'const pool = new Pool()// Create table with UUID typeawait pool.query(` CREATE TABLE users ( id UUID PRIMARY KEY, name TEXT NOT NULL )`)// Insert using binaryconst id = uuidv7()const bytes = uuidv7.toBytes(id)await pool.query( 'INSERT INTO users (id, name) VALUES ($1, $2)', [bytes, 'Alice'])// Query using string (Postgres handles conversion)const result = await pool.query( 'SELECT id FROM users WHERE id = $1', [id] // Can use string)
import { ulid } from 'uniku/ulid'import Database from 'better-sqlite3'const db = new Database('app.db')db.exec(` CREATE TABLE IF NOT EXISTS records ( id BLOB PRIMARY KEY, data TEXT )`)const insert = db.prepare('INSERT INTO records (id, data) VALUES (?, ?)')const id = ulid()const bytes = ulid.toBytes(id)insert.run(bytes, 'some data')// Queryconst select = db.prepare('SELECT id, data FROM records WHERE id = ?')const row = select.get(bytes)const retrievedId = ulid.fromBytes(row.id)
Use toBytes() for database storage (50%+ size savings)
Use fromBytes() when retrieving from binary storage
Write directly to buffers for batch operations
Reuse buffers for maximum performance
Use binary format for network transmission
When to use strings:
JSON APIs
Human-readable logs
URLs and file names
When interoperability matters
For most applications, the convenience of strings outweighs the storage/performance benefits of bytes. Use binary formats when you have specific optimization needs.