Skip to main content

Guide

decimal.js vs big.js vs bignumber.js 2026

Compare decimal.js, big.js, and bignumber.js for arbitrary precision in JavaScript. Financial calculations, rounding modes, database storage, and performance.

·PkgPulse Team·
0

TL;DR

decimal.js is the most feature-rich arbitrary precision library — supports trigonometric functions, logarithms, modular arithmetic, and is the basis for Decimal128 in databases. big.js is the smallest and simplest — 6KB, covers the most common use cases (add, subtract, multiply, divide with configurable precision), no trig. bignumber.js sits between them — same author as big.js and decimal.js, broader base support (binary, octal, hex), more features than big.js, lighter than decimal.js. For financial calculations: big.js (simple, fast, focused). For scientific computing with trig/log: decimal.js. For general-purpose arbitrary precision with base conversion: bignumber.js.

Quick Comparison

big.jsbignumber.jsdecimal.js
Weekly Downloads~35M~25M~10M
Bundle Size~6KB~20KB~30KB
Trig/Log
Arbitrary Base✅ (2–36)
Rounding Modes499
Decimal128 Compat
TypeScript
AuthorMikeMclMikeMclMikeMcl

Key Takeaways

  • decimal.js: ~10M weekly downloads — most features, trig/log, Decimal128-compatible
  • big.js: ~35M weekly downloads — smallest (6KB), focused on decimal arithmetic
  • bignumber.js: ~25M weekly downloads — middle ground, arbitrary base support
  • All three solve the 0.1 + 0.2 !== 0.3 floating point problem
  • For money: reach for integer cents arithmetic first — arbitrary precision is usually overkill
  • All three are from the same author (MikeMcl) — similar APIs, different feature sets

Why Arbitrary Precision?

// JavaScript floating point failures:
0.1 + 0.2              // 0.30000000000000004
(0.1 + 0.2) === 0.3    // false
1.005.toFixed(2)        // "1.00" (not "1.01")
9007199254740992 + 1    // 9007199254740992 (MAX_SAFE_INTEGER overflow)
Math.sqrt(2)            // 1.4142135623730951 (limited precision)

// When you need exact arithmetic:
// - Financial calculations beyond cents
// - Scientific computing requiring many significant digits
// - Cryptography (large integer arithmetic)
// - Arbitrary-precision constants (π, e, etc.)
// - Tax calculations with complex rounding rules

big.js

big.js — small, focused decimal arithmetic:

Basic usage

import Big from "big.js"

// Create:
const price = new Big("19.99")    // Always pass strings for exact values
const tax = new Big("0.08")

// Arithmetic:
const taxAmount = price.times(tax)
console.log(taxAmount.toFixed(2))  // "1.60" (not 1.5992000000000002)
console.log(price.plus(taxAmount).toFixed(2))  // "21.59"

// Chaining:
const total = new Big("19.99")
  .times("1.08")           // 8% tax included
  .round(2)                // Round to 2 decimal places
  .toFixed(2)              // "21.59"

// Comparisons:
const a = new Big("10.1")
const b = new Big("10.10")

a.eq(b)    // true (equal)
a.gt(b)    // false (not greater than)
a.lt(b)    // false (not less than)
a.gte(b)   // true (greater than or equal)

Configuring precision and rounding

import Big from "big.js"

// Global settings (affects all Big instances):
Big.DP = 20     // Decimal places for division (default: 20)
Big.RM = 1      // Rounding mode (0=down, 1=half-up, 2=half-even, 3=up)

// Per-calculation control:
const result = new Big("10").div("3").toFixed(5)
// "3.33333"

// Round to specific places:
new Big("1.555").round(2)           // Big("1.56") — half-up
new Big("1.555").round(2, 0)        // Big("1.55") — round down
new Big("1.555").round(2, 2)        // Big("1.56") — half-even (banker's)

// Convert to number (use only when exact precision no longer needed):
const price = new Big("19.99")
Number(price)       // 19.99
price.toNumber()    // 19.99

Financial use case

import Big from "big.js"

interface LineItem {
  description: string
  unitPrice: string  // Store as string to avoid floating point
  quantity: number
}

function calculateInvoice(items: LineItem[], taxRate: string) {
  const subtotal = items.reduce(
    (sum, item) => sum.plus(new Big(item.unitPrice).times(item.quantity)),
    new Big("0")
  )

  const tax = subtotal.times(new Big(taxRate)).round(2, Big.roundHalfUp)
  const total = subtotal.plus(tax)

  return {
    subtotal: subtotal.toFixed(2),
    tax: tax.toFixed(2),
    total: total.toFixed(2),
  }
}

const invoice = calculateInvoice(
  [
    { description: "Plan A", unitPrice: "99.99", quantity: 12 },
    { description: "Add-on", unitPrice: "19.99", quantity: 3 },
  ],
  "0.09"  // 9% tax
)
// { subtotal: "1259.85", tax: "113.39", total: "1373.24" }

decimal.js

decimal.js — full-featured arbitrary precision:

Basic usage

import Decimal from "decimal.js"

// Create:
const x = new Decimal("1.23456789012345678901234567890")

// Trigonometric functions:
Decimal.sin(new Decimal("1.5707963267948966"))   // sin(π/2) ≈ 1
Decimal.cos(new Decimal("0"))                    // cos(0) = 1
Decimal.tan(new Decimal("0.7853981633974484"))   // tan(π/4) ≈ 1

// Logarithms:
Decimal.ln(new Decimal("2.718281828459045"))     // ln(e) ≈ 1
Decimal.log10(new Decimal("100"))                // 2
Decimal.log2(new Decimal("8"))                   // 3

// Power and square root:
new Decimal("2").pow(new Decimal("0.5"))         // √2 = 1.4142135623...
Decimal.sqrt(new Decimal("2"))                   // Same result

High precision constants

import Decimal from "decimal.js"

// Set precision to 50 significant digits:
Decimal.set({ precision: 50 })

// Compute π to 50 digits:
Decimal.acos(-1).toString()
// "3.1415926535897932384626433832795028841971693993751"

// Euler's number e to 50 digits:
Decimal.exp(1).toString()
// "2.7182818284590452353602874713526624977572470936999"

Configuration

import Decimal from "decimal.js"

// Configure globally:
Decimal.set({
  precision: 20,          // Significant digits (default: 20)
  rounding: 4,            // Half-up (default: 4)
  // Rounding modes:
  // 0 = round down (truncate)
  // 4 = half up (round half away from zero)
  // 5 = half even (banker's rounding)
  // 6 = half down
  toExpNeg: -7,
  toExpPos: 21,
})

// Scoped Decimal constructor:
const D = Decimal.clone({ precision: 50, rounding: 5 })
const pi = D.acos(-1)  // π to 50 digits with banker's rounding

bignumber.js

bignumber.js — general-purpose, multi-base:

Basic usage

import BigNumber from "bignumber.js"

const x = new BigNumber("1234567890.123456789")

x.plus("0.000000001").toFixed()  // "1234567890.123456790"
x.times("2").toFixed()           // "2469135780.246913578"

// Locale-aware formatting:
x.toFormat(2)          // "1,234,567,890.12"
x.toFormat(2, { groupSeparator: ".", decimalSeparator: "," })
// "1.234.567.890,12" (European format)

Multi-base conversion

import BigNumber from "bignumber.js"

const decimal = new BigNumber(255)

decimal.toString(16)    // "ff" (hex)
decimal.toString(2)     // "11111111" (binary)
decimal.toString(8)     // "377" (octal)

new BigNumber("ff", 16).toNumber()       // 255
new BigNumber("11111111", 2).toNumber()  // 255

Feature Comparison

Featurebig.jsbignumber.jsdecimal.js
Bundle size~6KB~20KB~30KB
Decimal precision
Trig/log functions
Arbitrary base✅ (2-36)
Formatting (toFormat)
Rounding modes499
Decimal128 compat
TypeScript
ESM
Browser

Financial Arithmetic Best Practices

The most common mistake in financial JavaScript code is reaching for an arbitrary precision library before considering whether integer cents arithmetic would be simpler and sufficient. Integer cents means representing all monetary values as integers: $19.99 becomes 1999, and all arithmetic happens in whole numbers. This approach has no floating point drift at all because JavaScript integers up to 2^53 are exact. Addition and subtraction are perfectly accurate, and rounding only happens at display time when you divide by 100 and format with two decimal places.

Integer cents covers the majority of financial use cases: retail pricing, subscription billing, e-commerce totals, and straightforward tax calculations with a single rate. Where it breaks down is when the domain genuinely requires fractional cents — foreign currency exchange with many decimal places, tax calculations that involve per-unit rates applied to large quantities where rounding must happen at a specific step rather than at display time, or financial instruments with sub-cent precision requirements. In these cases, an arbitrary precision library is the right tool.

When you do reach for big.js, the discipline around initialization is critical. Always construct Big instances from strings, never from JavaScript number literals. new Big(0.1) is actually new Big(0.1000000000000000055511151231257827021181583404541015625) — the JavaScript float is the input, and big.js preserves it exactly. new Big("0.1") is exactly 0.1 because string parsing is exact. This applies across all three libraries: decimal.js, big.js, and bignumber.js all accept strings and should always receive them for decimal values.

Tax calculations add another dimension: the order and timing of rounding affects the result, and different jurisdictions mandate different rounding rules. Some states require rounding per line item before summing; others require summing exact values and rounding only the final total. Big.js's rounding modes map directly to these requirements — Big.roundHalfUp for most commercial rounding, Big.roundHalfEven (banker's rounding) for financial institutions and statistical work where systematic bias from always rounding 0.5 up would compound over many calculations.

Tax calculation with explicit rounding modes

import Big from "big.js"

// Rounding modes: 0=down, 1=half-up, 2=half-even, 3=up
const HALF_UP = 1
const HALF_EVEN = 2  // Banker's rounding

// Round per line item (some jurisdictions):
function calculateTaxPerLine(
  unitPrice: string,
  quantity: number,
  taxRate: string
): { subtotal: string; tax: string; total: string } {
  const lineSubtotal = new Big(unitPrice).times(quantity)
  // Round tax per line before accumulating:
  const lineTax = lineSubtotal.times(taxRate).round(2, HALF_UP)
  return {
    subtotal: lineSubtotal.toFixed(2),
    tax: lineTax.toFixed(2),
    total: lineSubtotal.plus(lineTax).toFixed(2),
  }
}

// Round only the final total (other jurisdictions):
function calculateTaxOnTotal(
  items: Array<{ price: string; qty: number }>,
  taxRate: string
): string {
  const subtotal = items.reduce(
    (acc, item) => acc.plus(new Big(item.price).times(item.qty)),
    new Big("0")
  )
  const tax = subtotal.times(taxRate).round(2, HALF_EVEN)
  return subtotal.plus(tax).toFixed(2)
}

Database Storage Patterns

Storing arbitrary precision values in a database requires deliberate type choices. PostgreSQL has two relevant types: NUMERIC (also called DECIMAL) and MONEY. The NUMERIC type stores exact arbitrary precision values without rounding — it is the correct type for financial data. MONEY is a fixed-point type with platform-dependent precision and should be avoided in modern applications. When reading a NUMERIC column from PostgreSQL with node-postgres, the value arrives as a string by default — which is exactly what you want to pass to new Big(...).

import { Pool } from "pg"
import Big from "big.js"

const pool = new Pool()

// PostgreSQL: store as NUMERIC(19, 4) for financial data
// 19 total digits, 4 decimal places covers most financial use cases

async function storePrice(productId: string, price: string) {
  // Pass the string directly — pg handles NUMERIC correctly
  await pool.query(
    "UPDATE products SET price = $1::numeric WHERE id = $2",
    [price, productId]
  )
}

async function getPrice(productId: string): Promise<Big> {
  const { rows } = await pool.query(
    "SELECT price::text FROM products WHERE id = $1",
    [productId]
  )
  // Cast to text in the query to receive a string, not a float
  return new Big(rows[0].price)
}

MongoDB's Decimal128 type is designed explicitly for this use case and maps to decimal.js's Decimal constructor. When using Mongoose, define the field as mongoose.Schema.Types.Decimal128 and convert to/from decimal.js in your model layer. This ensures values survive the round trip from application code through the database and back without any floating point corruption.

import Decimal from "decimal.js"
import mongoose from "mongoose"

const productSchema = new mongoose.Schema({
  name: String,
  price: mongoose.Schema.Types.Decimal128,
})

// Convert Decimal128 → decimal.js on read:
function getDecimalPrice(doc: any): Decimal {
  return new Decimal(doc.price.toString())
}

// Convert decimal.js → Decimal128 on write:
function setDecimalPrice(value: Decimal): mongoose.Types.Decimal128 {
  return mongoose.Types.Decimal128.fromString(value.toFixed(4))
}

The key discipline in both cases is the same: the value should be a string at the application boundary. Query PostgreSQL with ::text to receive a string. Call .toString() on a Decimal128 value before passing it to the Decimal constructor. Return toFixed(n) or toString() when writing back. Never pass a JavaScript float through any step of this chain.


Performance Comparison

Performance benchmarks for arbitrary precision libraries are counterintuitive because the libraries are inherently slower than native JavaScript numbers — that is the price of exactness. The practical question is how much slower, and whether that matters in your application.

In informal benchmarks using Node.js 22 with a simple loop performing 100,000 additions:

Native JavaScript number addition runs at roughly 500 million operations per second — essentially free. big.js addition runs at approximately 2–4 million operations per second. bignumber.js is similar to big.js. decimal.js is the slowest of the three at roughly 1–2 million operations per second, reflecting its larger feature set and more complex internal representation.

For typical application code — calculating an invoice total with 50 line items, computing a tax amount, formatting a currency value — this performance difference is completely invisible. The arbitrary precision operation takes microseconds regardless of which library you choose. Performance becomes relevant only in tight numerical loops: financial simulation, Monte Carlo modeling of prices, or batch processing millions of transaction records in memory. In those scenarios, the integer cents approach (native numbers) is worth serious consideration, and if arbitrary precision is genuinely required, big.js's simpler internals give it a meaningful advantage over decimal.js in tight loops.

The bundle size difference matters more for browser applications than for Node.js servers. big.js at 6KB is essentially free in a browser bundle. decimal.js at 30KB is noticeable but not prohibitive. If you're adding arbitrary precision to a client-side app that also ships React and a routing library, decimal.js's 30KB is a reasonable cost for the features it provides.


Common Pitfalls

Always use strings for initialization. This is the single most important rule when using any of these libraries. new Big(0.1) captures the floating point representation of 0.1 — the entire problem you were trying to avoid. new Big("0.1") is exact. Enforce this with a TypeScript wrapper type or a lint rule that flags new Big( followed by a number literal.

Understand toFixed() vs toString(). These methods behave differently and the distinction matters: toFixed(n) returns a string with exactly n decimal places, padding with zeros or rounding as needed. toString() returns the number without trailing zeros. new Big("1.50").toFixed(2) returns "1.50". new Big("1.50").toString() returns "1.5". For currency display, always use toFixed(2). For database storage, toString() is usually preferable to avoid storing unnecessary trailing zeros.

Don't mix Big instances with primitives in comparisons. new Big("10") === 10 is always false — you're comparing an object to a number. Use .eq(), .gt(), .lt() for all comparisons. Similarly, arithmetic between a Big instance and a plain number works (big.js accepts numbers in arithmetic operations) but is a code smell — if you're receiving a plain number, convert it explicitly with new Big(numberValue.toString()) rather than passing it directly.

Global configuration is global. Changing Big.DP or Big.RM affects every Big instance in the application. If you need different precision settings in different parts of your code, use Decimal.clone() (decimal.js) or BigNumber.clone() (bignumber.js) to create isolated constructors with their own configuration. This is especially important in libraries or shared modules that might be used by code that has its own precision requirements.


Currency Formatting Integration

Arbitrary precision libraries handle the arithmetic; currency formatting for display is a separate concern. The standard approach is to complete all arithmetic in the precision library, extract the final value as a string with toFixed(2), parse it back to a number for Intl.NumberFormat, and format for display:

import Big from "big.js"

function formatCurrency(
  amount: Big,
  locale: string = "en-US",
  currency: string = "USD"
): string {
  // toFixed(2) gives "1234.56" — safe to parse as float since it's already rounded
  const formatted = new Intl.NumberFormat(locale, {
    style: "currency",
    currency,
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }).format(parseFloat(amount.toFixed(2)))

  return formatted
}

const total = new Big("1234.567").round(2)
formatCurrency(total, "en-US", "USD")  // "$1,234.57"
formatCurrency(total, "de-DE", "EUR")  // "1.234,57 €"

The key insight: Intl.NumberFormat is for display only. Do not use it to perform arithmetic. The float passed to Intl.NumberFormat is already rounded to cents — the float representation of a two-decimal-place number is safe because JavaScript floats have enough precision to represent all values up to $90,071,992,547.41 exactly (9 * 10^13 cents).

bignumber.js's toFormat() method handles this in one step for simpler cases, with configurable group separators and decimal characters. It's useful when you don't need the full locale awareness of Intl.NumberFormat but want formatting to be part of the same library call.


Testing Financial Code

Testing financial code requires explicit attention to equality comparisons and rounding behavior. Do not compare Big instances with === or even ==. Use .eq() for exact equality, and be intentional about what "equal" means in your domain.

import Big from "big.js"
import { describe, it, expect } from "vitest"

describe("invoice calculation", () => {
  it("rounds tax to exactly 2 decimal places", () => {
    const price = new Big("19.99")
    const taxRate = new Big("0.08")
    const tax = price.times(taxRate).round(2)
    // Use toFixed to get a string for comparison — avoid floating point in assertions
    expect(tax.toFixed(2)).toBe("1.60")
  })

  it("total equals subtotal plus tax", () => {
    const subtotal = new Big("99.95")
    const tax = new Big("8.00")
    const total = subtotal.plus(tax)
    expect(total.toFixed(2)).toBe("107.95")
  })

  it("handles the 0.1 + 0.2 case", () => {
    const result = new Big("0.1").plus("0.2")
    expect(result.eq("0.3")).toBe(true)
    // Not: expect(result.toNumber()).toBe(0.3) — avoid converting to float
  })
})

A useful invariant test for financial code: the sum of all line item totals should equal the overall total, regardless of rounding order. Write tests that verify this property explicitly, especially when rounding happens at the line-item level. If line items round independently, the sum of rounded line items may differ from the rounded sum of exact line items by a penny — this is expected behavior in some jurisdictions and a bug in others.


Migration: From Native Numbers to big.js

When floating point precision bugs appear in production financial code, migrating from native numbers to big.js is straightforward. The library is designed to be easy to adopt incrementally. The migration strategy:

Step 1: Identify the boundary points. Find where monetary values enter your application (form inputs, API responses, database reads) and where they exit (database writes, API responses, display). These are the places to add conversion to and from Big instances.

Step 2: Replace arithmetic operations. a + b becomes new Big(a).plus(b). a * b becomes new Big(a).times(b). a / b becomes new Big(a).div(b). The API is intentionally close to natural language.

Step 3: Replace toFixed() calls on native numbers. (1.005).toFixed(2) is "1.00" — wrong. new Big("1.005").toFixed(2) is "1.01" — correct. Every toFixed() call on a monetary value is a potential bug to fix.

Step 4: Update storage. Change database columns storing monetary values from FLOAT to NUMERIC(19,4). Read values as strings. Store toFixed(4) output.

The migration does not require a big bang rewrite. You can migrate one calculation at a time, starting with the ones where precision bugs have been reported.


JavaScript Floating Point: When It Actually Matters

JavaScript uses IEEE 754 double-precision floating point for all numbers. This gives you 15–17 significant decimal digits of precision. The problems appear at the edges: 0.1 + 0.2 === 0.30000000000000004 is the famous example, but the real-world failures are more subtle. A subscription billing system calculating 12 monthly charges at $99.99 may round to a total that is off by one cent. A tax calculation chaining multiple multiplications and additions can drift from the expected result depending on the order of operations. A running balance accumulating thousands of small transactions can show a non-zero balance when the correct answer is exactly zero.

For financial code specifically: if integer cents does not work because your domain involves fractional cents or complex rounding rules mandated by tax law, big.js is the appropriate tool — purpose-built for exactly this use case. For scientific work requiring trig or logarithm functions at arbitrary precision, decimal.js is the right choice. For applications that also need base conversion or advanced locale-aware formatting, bignumber.js fills the gap between the two.


When to Use Each

Choose big.js if:

  • Financial calculations requiring exact decimal arithmetic
  • You want the smallest possible bundle for browser use
  • Simple add/subtract/multiply/divide with configurable rounding
  • API simplicity over feature richness

Choose bignumber.js if:

  • You need base conversion (binary/hex/octal ↔ decimal)
  • Better locale-aware number formatting with toFormat()
  • More rounding modes than big.js provides
  • General-purpose arbitrary precision without trig

Choose decimal.js if:

  • Scientific computing requiring trigonometric or logarithmic functions
  • You need a Decimal128-compatible value for MongoDB or PostgreSQL
  • Complex mathematical operations beyond basic arithmetic
  • Maximum precision (up to ~1e9 significant digits)

Stick with native numbers if:

// Integer cents approach covers 99% of financial use cases:
const priceInCents = 1999
const taxInCents = Math.round(priceInCents * 0.08)   // 160
const totalInCents = priceInCents + taxInCents         // 2159
const display = (totalInCents / 100).toFixed(2)        // "21.59"

JavaScript Floating Point: When It Actually Matters

JavaScript uses IEEE 754 double-precision floating point for all numbers. This is excellent for most use cases — 64-bit floats handle integers up to 2^53 exactly and give you 15-17 significant decimal digits of precision. The problems appear at the edges: 0.1 + 0.2 === 0.30000000000000004 is the famous example, but the real-world failures in production code are more subtle. A subscription billing system that calculates 12 monthly charges at $99.99 may round to a total that's off by one cent on some inputs. A tax calculation that chains multiple multiplications and additions can drift from the expected result depending on the order of operations. A running balance that accumulates thousands of small transactions can show a non-zero balance when the correct answer is exactly zero.

The integer-cents approach is the right first response to financial precision requirements. Store prices as integers in cents — 1999 instead of 19.99 — perform all arithmetic in integer space, and only divide by 100 when displaying a formatted value. This approach handles the vast majority of financial use cases without any external library, avoids floating point drift entirely, and has zero runtime overhead. Before reaching for big.js or decimal.js, ask whether integer cents covers your requirements.

Arbitrary precision libraries are the right tool when: (1) precision requirements exceed 15 significant digits (certain scientific computations, cryptographic calculations involving large numbers), (2) the domain requires non-decimal number bases such as binary or hexadecimal manipulation, (3) mathematical operations compound floating point errors enough to matter across many iterations such as statistical modeling or simulation work, or (4) you need trig and logarithm functions at precision beyond what Math provides. For financial code specifically: if integer cents doesn't work (because your domain involves fractional cents or complex rounding rules mandated by tax law), big.js is the appropriate tool — purpose-built for exactly this use case.

Choosing Between big.js, bignumber.js, and decimal.js for Financial Code

All three libraries solve the decimal precision problem, but they have distinct design points that matter when you're evaluating them for financial code. big.js is purpose-built for decimal arithmetic — it has the smallest API surface (add, subtract, multiply, divide, power, square root, absolute value, round) and the smallest bundle size at approximately 6KB minified. Every operation a billing system or invoicing engine needs is present and well-documented. The limitations are intentional: no trigonometric functions, no base conversion, a limited but sufficient set of rounding modes. The constraint is a feature — it means the library has one job and does it well.

bignumber.js, by the same author, adds capabilities that matter for certain financial and display use cases. The toFormat() method provides locale-aware number formatting: thousands separators, decimal characters, and grouping patterns configurable for different regional conventions. This is genuinely useful for applications that display monetary values to users in multiple countries — building the formatting yourself is easy to get wrong. bignumber.js also supports arbitrary base arithmetic (binary, octal, hexadecimal), which is occasionally useful in financial systems that interface with older mainframe formats or certain blockchain representations.

decimal.js is the heavyweight of the three: designed for scientific computing where trig and logarithm functions are needed at arbitrary precision, and where the Decimal128 database type (used in MongoDB and supported in PostgreSQL) requires a compatible representation. For billing systems, invoicing, and e-commerce: start with big.js. If you need locale-aware number formatting beyond what Intl.NumberFormat provides, evaluate bignumber.js. Reach for decimal.js only for genuinely scientific work or when Decimal128 database compatibility is a hard requirement.


Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on big.js v6.x, bignumber.js v9.x, and decimal.js v10.x. All three libraries are maintained by the same author (MikeMcl).

Compare math and utility packages on PkgPulse →

See also: ohash vs object-hash vs hash-wasm, acorn vs @babel/parser vs espree, and AVA vs Jest vs Vitest.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.