thirty-two

A tiny RFC 3548 Base32 encoder and decoder for Node.js Buffers and strings.

Library
npm
v1.0.2
36stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
23/100Needs Attention
Development Activity0
Maintenance0
Community20
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
44/100Fair
Architecture55
Code Quality35
Innovation45
Learning Curve40

thirty-two is a minimal Node.js library that implements RFC 3548 Base32 encoding and decoding. It exposes a two-function API, encode and decode, that accepts either a Buffer or a string and always returns a Buffer, using the standard uppercase Base32 alphabet (A-Z, 2-7) with ’=’ padding.

The implementation works directly on byte buffers with manual bit-shifting rather than delegating to an intermediate string representation, which keeps it dependency-free and binary-safe for arbitrary byte sequences, not just UTF-8 text. It ships as a small standalone package with no runtime dependencies, making it a common building block wherever Base32 output is needed, for example encoding shared secrets for TOTP/HOTP two-factor authentication or generating human-typeable identifiers.

What You Get

  • encode() converts a Buffer or string into a padded Base32-encoded Buffer using the standard A-Z2-7 alphabet.
  • decode() converts a Base32-encoded Buffer or string back into the original binary Buffer, ignoring ’=’ padding.
  • Binary-safe handling that operates on raw bytes rather than assuming UTF-8 text, so arbitrary binary payloads round-trip correctly.
  • A bundled TypeScript declaration file (lib/types/index.d.ts) for typed consumers.
  • Zero runtime dependencies, shipping as a single small module.

Common Use Cases

  • Encoding TOTP/HOTP shared secrets for two-factor authentication key provisioning URIs and QR codes.
  • Generating case-insensitive, human-typeable identifiers or tokens from binary data.
  • Encoding binary data for contexts where Base64’s punctuation characters are disallowed, such as DNS labels or filenames.
  • Interoperating with other RFC 3548 Base32 implementations across languages in a shared protocol.

Under The Hood

Architecture thirty-two is a single-purpose module with almost no internal architecture: the entire codec lives in lib/thirty-two/index.js, a self-contained file exporting a default object with encode and decode functions, plus a bundled lib/types/index.d.ts declaration for TypeScript consumers. Notably, the root-level index.js does not match this: it requires ./lib/base32/, a directory that does not exist anywhere in the current tree, meaning the package’s actual entry point is only reachable via the main field in package.json (./lib/thirty-two/index.js), while the root file is dead, broken code left over from an earlier restructuring. There is no layering, no dependency injection, and no internal module boundary to speak of; encode() and decode() are pure functions operating directly on Buffer objects via manual bit-shifting over a hardcoded character/byte lookup table, so the only thing that could break if the core abstraction changed is the codec logic itself.

Tech Stack The package is pure, dependency-free JavaScript (largely ES5-style, using var and CommonJS exports) targeting any Node.js runtime declared via engines: node >=0.2.6 in package.json, with no runtime dependencies listed at all. Type consumers get a hand-written lib/types/index.d.ts referenced via the types field, but there is no build step generating it, no tsconfig, and no compiled TypeScript source, so it is maintained by hand alongside the plain JS implementation. Testing is wired through a Makefile target that shells out to the long-unmaintained jasmine-node CLI against spec/thirty-two_spec.js, a global tool that is not declared as a devDependency, and there is no CI configuration anywhere in the repo to run it automatically.

Code Quality The single spec file exercises encode/decode round-trips, including a binary-safety test using raw non-UTF-8 byte buffers, but it relies on a hand-rolled expect() shim wrapping Node’s assert.strictEqual rather than a real assertion library, and Jasmine itself is not declared as a project dependency, so the suite requires a global install to run at all and is not exercised automatically on changes. Error handling is minimal but present: encode() and decode() both throw on non-Buffer, non-string input, but there is no linter or formatter configuration, and the code mixes ES5 var declarations with modern Buffer.from()/Buffer.alloc() calls, suggesting incremental patching rather than a maintained style guide.

API Design thirty-two’s public API is about as minimal as an encoding library can be: a single default export with two functions, encode and decode, each accepting either a Buffer or string and always returning a Buffer, which means effectively zero configuration and a very short learning curve for the one supported case, at the cost of any string-output convenience (callers must call .toString() themselves, as the README’s own examples show). Documentation is limited to a short usage snippet in the README with no deeper API reference, no support for alternate alphabets such as Crockford’s Base32, and no examples covering error cases, so ergonomics are frictionless for the narrow supported path but offer little guidance beyond it.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search