Skip to content

orhanayd/nope-id

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

36 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

nope-id

🌐 Read in another language: English Β· TΓΌrkΓ§e Β· Русский

A tiny, secure, URL-friendly unique string ID generator for JavaScript.

A faster, more secure alternative to nanoid with extra features!

  • Faster - 3x to 9x faster than nanoid (CSPRNG, full URL-safe alphabet); wins all 5 core benchmarks (see benchmarks)
  • Security Hardened - Timing attack prevention, modulo bias elimination, prototype pollution protection (see security)
  • Well Tested - 307 tests including security & entropy tests (see testing)
  • Cryptographically Secure - Uses webcrypto.getRandomValues() (CSPRNG)
  • Zero Dependencies - No external dependencies
  • URL-safe - Uses A-Za-z0-9_- characters
  • Dual Module - Works with both ESM (import) and CommonJS (require)
  • TypeScript - Full type definitions included
  • Collision-resistant - Monotonic sortable IDs, distributed-safe IDs
  • Many ID Formats - UUID v4 & v7, ULID (spec-compliant + monotonic factory), Snowflake, MongoDB ObjectId
  • Extra Features - Prefixed IDs, sortable IDs, Sqids (reversible encoding), typed IDs, format validators, and more!

Installation

npm install nope-id

Quick Start

ES Modules (import)

import { nopeid } from 'nope-id'

const id = nopeid() // "V1StGXR8_Z5jdHi6B-myT"

CommonJS (require)

const { nopeid } = require('nope-id')

const id = nopeid() // "V1StGXR8_Z5jdHi6B-myT"

Default Export

// ES Modules
import nopeid from 'nope-id'
const id = nopeid() // "V1StGXR8_Z5jdHi6B-myT"

// CommonJS
const nopeid = require('nope-id')
const id = nopeid() // "V1StGXR8_Z5jdHi6B-myT"

API Reference

Core Functions

nopeid(size = 21)

Generates a secure, URL-safe unique ID.

// ES Modules
import { nopeid } from 'nope-id'

nopeid()    // "V1StGXR8_Z5jdHi6B-myT" (21 characters)
nopeid(10)  // "IRFa-VaY2b" (10 characters)
nopeid(32)  // "V1StGXR8_Z5jdHi6B-myTV1StGXR8_Z5" (32 characters)
nopeid(0)   // "" (empty string for zero/negative)

// CommonJS
const { nopeid } = require('nope-id')
const id = nopeid() // "V1StGXR8_Z5jdHi6B-myT"

customAlphabet(alphabet, defaultSize = 21)

Creates a custom ID generator with your own alphabet.

// ES Modules
import { customAlphabet } from 'nope-id'

// Hex IDs
const hexId = customAlphabet('0123456789abcdef', 16)
hexId()   // "4f90d13a42f17f80"
hexId(8)  // "a3b2c1d4"

// Numbers only
const numericId = customAlphabet('0123456789', 8)
numericId() // "48293751"

// Custom characters
const customId = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 6)
customId() // "BKWXYZ"

// Binary IDs
const binaryId = customAlphabet('01', 16)
binaryId() // "1010110011001010"

// CommonJS
const { customAlphabet } = require('nope-id')
const hexGen = customAlphabet('0123456789abcdef', 16)
const id = hexGen() // "4f90d13a42f17f80"

customRandom(alphabet, defaultSize, getRandom)

Creates a custom ID generator with your own random function.

// ES Modules
import { customRandom } from 'nope-id'

// Use custom random source
const customGen = customRandom(
  'abcdef',
  10,
  (size) => new Uint8Array(size).fill(1)  // Deterministic for testing
)
customGen() // Deterministic output

// CommonJS
const { customRandom } = require('nope-id')

random(bytes)

Generates cryptographically secure random bytes.

// ES Modules
import { random } from 'nope-id'

const bytes = random(16)  // Uint8Array(16) with random bytes
const bytes2 = random(32) // Uint8Array(32) with random bytes

// CommonJS
const { random } = require('nope-id')
const bytes = random(16)

ID Generation Functions

prefixedId(prefix, size = 21, separator = '_')

Generates an ID with a prefix - perfect for database entries!

// ES Modules
import { prefixedId } from 'nope-id'

prefixedId('user')           // "user_V1StGXR8_Z5jdHi6B-myT"
prefixedId('order', 10)      // "order_IRFa-VaY2b"
prefixedId('prod', 8, '-')   // "prod-Z5jdHi6B"
prefixedId('cust', 12, ':')  // "cust:a1b2c3d4e5f6"

// Real-world examples
const userId = prefixedId('usr')       // "usr_V1StGXR8_Z5jdHi6B-myT"
const orderId = prefixedId('ord')      // "ord_IRFa-VaY2bKwxyz..."
const productId = prefixedId('prod')   // "prod_Z5jdHi6B-myT..."
const transactionId = prefixedId('txn') // "txn_8_Z5jdHi6B..."

// CommonJS
const { prefixedId } = require('nope-id')
const id = prefixedId('user') // "user_V1StGXR8_Z5jdHi6B-myT"

sortableId(size = 22)

Generates a ULID-like sortable ID with monotonic guarantee. Uses Crockford's Base32 for lexicographic sorting.

Features:

  • 10 chars timestamp + 12 chars random = 22 chars default
  • Chronologically sortable (lexicographic order)
  • Same-millisecond IDs are guaranteed monotonically increasing
  • Can decode timestamp with decodeTime()
  • Sizes > 22 are padded with extra Crockford Base32 random chars; sizes < 22 truncate to the timestamp prefix (use size β‰₯ 22 to keep the monotonic guarantee)
// ES Modules
import { sortableId, decodeTime } from 'nope-id'

const id1 = sortableId() // "01HGW2BBK0QZRMTX12345A"
// wait some time...
const id2 = sortableId() // "01HGW2BBK1ABCDEFGHIJKL"

// Chronologically sortable
console.log(id1 < id2) // true

// Same millisecond - monotonically increasing
const ids = []
for (let i = 0; i < 5; i++) {
  ids.push(sortableId())
}
// All IDs are unique and strictly increasing
// ids[0] < ids[1] < ids[2] < ids[3] < ids[4]

// Custom size
sortableId(30) // 30 character sortable ID

// Decode timestamp
const id = sortableId()
const date = decodeTime(id)
console.log(date) // Date object when ID was created

// CommonJS
const { sortableId, decodeTime } = require('nope-id')
const id = sortableId()
const timestamp = decodeTime(id)

decodeTime(sortableIdStr)

Extracts the timestamp from a sortable ID.

// ES Modules
import { sortableId, decodeTime } from 'nope-id'

const id = sortableId()
const date = decodeTime(id)

console.log(date)            // 2024-01-15T10:30:00.000Z
console.log(date.getTime())  // 1705314600000

// Error handling
try {
  decodeTime('invalid')  // throws Error
} catch (e) {
  console.log('Invalid sortable ID')
}

// CommonJS
const { sortableId, decodeTime } = require('nope-id')

generateMany(count, size = 21)

Generates multiple unique IDs at once.

// ES Modules
import { generateMany } from 'nope-id'

const ids = generateMany(5)
// ["V1StGXR8_Z5jdHi6B-myT", "IRFa-VaY2bKwxyz...", ...]

const shortIds = generateMany(100, 8)
// 100 short IDs of 8 characters each

generateMany(0)   // [] (empty array)
generateMany(-5)  // [] (empty array)

// Bulk insert example
const userIds = generateMany(1000, 21)
await db.users.insertMany(
  userIds.map(id => ({ id, createdAt: new Date() }))
)

// CommonJS
const { generateMany } = require('nope-id')
const ids = generateMany(100)

uuid()

Generates a UUID v4 compatible string.

// ES Modules
import { uuid } from 'nope-id'

uuid() // "110ec58a-a0f2-4ac4-8393-c866d813b8d1"
uuid() // "f47ac10b-58cc-4372-a567-0e02b2c3d479"

// Use in databases expecting UUID
const user = {
  id: uuid(),
  name: 'John'
}

// CommonJS
const { uuid } = require('nope-id')
const id = uuid()

slugId(size = 12)

Generates a slug-friendly ID (lowercase + numbers only). Perfect for URLs!

// ES Modules
import { slugId } from 'nope-id'

slugId()   // "a8b3c9d2e1f4"
slugId(8)  // "x7y2z9w4"
slugId(16) // "a1b2c3d4e5f6g7h8"

// URL-friendly usage
const articleSlug = `my-article-${slugId()}`
// "my-article-a8b3c9d2e1f4"

// CommonJS
const { slugId } = require('nope-id')
const slug = slugId()

shortId(size = 8)

Generates a short ID without similar-looking characters (no 0/O, 1/l/I). Perfect for user-facing codes!

// ES Modules
import { shortId } from 'nope-id'

shortId()   // "aBc7XyZ9"
shortId(6)  // "Kp3Wn8"
shortId(10) // "aBc7XyZ9Kp"

// Perfect for:
// - Verification codes
// - Short URLs
// - User-readable references
// - Ticket numbers

const verificationCode = shortId(6) // "Kp3Wn8"
const ticketNumber = shortId(8)     // "aBc7XyZ9"

// CommonJS
const { shortId } = require('nope-id')
const code = shortId(6)

nopeidAsync(size = 21)

Async version for non-blocking operations.

// ES Modules
import { nopeidAsync } from 'nope-id'

const id = await nopeidAsync()     // "V1StGXR8_Z5jdHi6B-myT"
const id2 = await nopeidAsync(32)  // 32 character async ID

// Parallel generation
const ids = await Promise.all([
  nopeidAsync(),
  nopeidAsync(),
  nopeidAsync()
])

// CommonJS
const { nopeidAsync } = require('nope-id')
const id = await nopeidAsync()

Distributed System Functions

getFingerprint()

Gets a unique fingerprint for the current process/device. Generated once and cached.

// ES Modules
import { getFingerprint } from 'nope-id'

const fp = getFingerprint() // "aB3x" (4 characters)
const fp2 = getFingerprint() // "aB3x" (same value, cached)

// Useful for distributed systems to identify origin
console.log(`ID generated by process: ${getFingerprint()}`)

// CommonJS
const { getFingerprint } = require('nope-id')
const fingerprint = getFingerprint()

distributedId(size = 25)

Generates a distributed-safe ID with process fingerprint. Perfect for multi-node environments!

Format: fingerprint_randomPart

// ES Modules
import { distributedId, getFingerprint } from 'nope-id'

distributedId()   // "aB3x_V1StGXR8_Z5jdHi6B" (25 chars)
distributedId(30) // "aB3x_V1StGXR8_Z5jdHi6B-myT1" (30 chars)

// In a distributed system, each node generates IDs with its own fingerprint
// Node 1: "aB3x_V1StGXR8_Z5jdHi6B"
// Node 2: "kL9m_IRFa-VaY2bKwxyz12"
// Node 3: "pQ7r_Z5jdHi6B-myTV1St8"

// Identify which node generated an ID
const id = distributedId()
const nodeFingerprint = id.split('_')[0]
console.log(`Generated by node: ${nodeFingerprint}`)

// CommonJS
const { distributedId } = require('nope-id')
const id = distributedId()

Utility Functions

isValid(id, alphabet = urlAlphabet)

Validates if a string is a valid ID for the given alphabet.

// ES Modules
import { isValid, urlAlphabet, alphabets } from 'nope-id'

isValid('V1StGXR8_Z5jdHi6B-myT')  // true
isValid('')                        // false
isValid(null)                      // false
isValid('abc@#$')                  // false (invalid chars)

// Validate with custom alphabet
isValid('abc123', 'abc123')        // true
isValid('ABC', 'abc')              // false
isValid('12345678', alphabets.numbers) // true

// CommonJS
const { isValid } = require('nope-id')
const valid = isValid('V1StGXR8_Z5jdHi6B-myT')

collisionProbability(idLength, alphabetSize = 64)

Calculate collision probability for given parameters.

// ES Modules
import { collisionProbability } from 'nope-id'

const info = collisionProbability(21)
console.log(info)
// {
//   totalPossible: 9007199254740991,   // clamped to MAX_SAFE_INTEGER (use totalPossibleBigInt for exact)
//   totalPossibleBigInt: 85070591730234615865843651857942052864n, // 64^21 β‰ˆ 8.5e37
//   probabilityForBillion: 0,          // ~5.9e-21, rounds to 0 in double precision
//   safeCount: 1.086e+19,              // ~50% collision after generating this many IDs
//   yearsFor1Percent: 4.133e+7         // years at 1 ID/ms before 1% collision
// }

// Compare different lengths
collisionProbability(8)   // Much higher collision risk
collisionProbability(32)  // Very low collision risk

// Custom alphabet size
collisionProbability(21, 62)  // Alphanumeric only
collisionProbability(21, 16)  // Hex only

// CommonJS
const { collisionProbability } = require('nope-id')
const info = collisionProbability(21)

Pre-built Alphabets

// ES Modules
import { alphabets, customAlphabet } from 'nope-id'

// Available alphabets
alphabets.alphanumeric    // "0-9A-Za-z" (62 chars)
alphabets.lowercase       // "a-z" (26 chars)
alphabets.uppercase       // "A-Z" (26 chars)
alphabets.numbers         // "0-9" (10 chars)
alphabets.hexLower        // "0-9a-f" (16 chars)
alphabets.hexUpper        // "0-9A-F" (16 chars)
alphabets.nolookalikes    // No confusing chars (49 chars)
alphabets.nolookalikesSafe // Extra safe version (34 chars)
alphabets.binary          // "01" (2 chars)
alphabets.octal           // "0-7" (8 chars)
alphabets.base32          // RFC 4648 Base32 (32 chars)
alphabets.base32Lower     // Lowercase Base32 (32 chars)
alphabets.base58          // Bitcoin Base58 (58 chars)
alphabets.filename        // Filename-safe (64 chars)

// Usage with customAlphabet
const hexGen = customAlphabet(alphabets.hexLower, 32)
hexGen() // "a1b2c3d4e5f67890abcdef1234567890"

const base58Gen = customAlphabet(alphabets.base58, 22)
base58Gen() // Bitcoin-style ID

const safeGen = customAlphabet(alphabets.nolookalikesSafe, 8)
safeGen() // Very readable code

// CommonJS
const { alphabets, customAlphabet } = require('nope-id')
const gen = customAlphabet(alphabets.hexLower, 16)

Additional ID Formats & Helpers

These secure-only generators and helpers are exposed as tree-shakeable named exports and never touch the hot path of nopeid(); import only what you use.

uuidv7()

Time-ordered UUID (RFC 9562), database-index friendly and sortable by creation time. The first 48 bits encode the Unix-ms timestamp.

import { uuidv7, isValidUUID } from 'nope-id'

uuidv7()                  // "0192f3c1-8e2a-7b3c-9d4e-5f60718293a4"
isValidUUID(uuidv7(), 7)  // true

ulid(seedTime?) & monotonicFactory()

Spec-compliant 26-char ULID (Crockford Base32: 10 timestamp + 16 random). ulid() uses fresh randomness per call; monotonicFactory() returns a generator with isolated state that guarantees strictly increasing IDs within the same millisecond (without touching the global sortableId() state).

import { ulid, monotonicFactory, decodeTime } from 'nope-id'

ulid()              // "01ARZ3NDEKTSV4RRFFQ69G5FAV"
decodeTime(ulid())  // Date

const next = monotonicFactory()
next() < next()     // true (same ms, strictly increasing)

snowflakeFactory(options), snowflake() & decodeSnowflake(id)

Twitter-style 64-bit distributed IDs returned as strings (BigInt-safe). Layout: 41-bit timestamp Β· 10-bit node id Β· 12-bit sequence. Each factory owns its own sequence state (coordination-free per node).

import { snowflakeFactory, decodeSnowflake, snowflake } from 'nope-id'

const next = snowflakeFactory({ nodeId: 1 })  // optional: { epoch }
const id = next()                              // "1838219834728448001"
decodeSnowflake(id)  // { timestamp: Date, nodeId: 1, sequence: 0 }

snowflake()  // default single-node generator (node id derived from fingerprint)

objectId() & decodeObjectIdTime(id)

MongoDB ObjectId-compatible 24-char hex (4-byte timestamp + 5-byte per-process value + 3-byte counter).

import { objectId, decodeObjectIdTime } from 'nope-id'

objectId()                      // "65f1c3e2a1b2c3d4e5f60718"
decodeObjectIdTime(objectId())  // Date

sqidsFactory(options): reversible integer encoding

Encode arrays of non-negative integers into short, URL-safe, reversible strings, for example to hide sequential database IDs in URLs. This is obfuscation, not encryption.

import { sqidsFactory } from 'nope-id'

const sqids = sqidsFactory()        // { alphabet?, minLength?, blocklist? }
const id = sqids.encode([1, 2, 3])  // "86Rf07"
sqids.decode(id)                    // [1, 2, 3]

The default has no profanity blocklist (to stay tiny). Pass your own blocklist if you need one.

defineId(prefix, options?): typed prefixed IDs

Stripe-style typed IDs with a generator, a type guard, and a parser. In TypeScript the generated id is typed as `${prefix}_${string}`.

import { defineId } from 'nope-id'

const UserId = defineId('user')   // { size?, separator?, alphabet? }
const id = UserId.generate()      // type: `user_${string}`
UserId.is(id)                     // true (type guard narrows the type)
UserId.parse('user_abc')          // { prefix: 'user', id: 'abc' }  (or null)

isValidUUID(id, version?) & isValidULID(id)

Format validators for UUID and ULID strings.

import { isValidUUID, isValidULID } from 'nope-id'

isValidUUID('110ec58a-a0f2-4ac4-8393-c866d813b8d1')  // true
isValidUUID(uuidv7(), 7)                              // true (version-pinned)
isValidULID('01ARZ3NDEKTSV4RRFFQ69G5FAV')             // true

These formats use cryptographic randomness and are secure-version only; they are not part of nope-id/non-secure.


Non-Secure Version

For non-critical use cases (UI element IDs, temporary keys, etc.), you can use the faster non-secure version:

// ES Modules
import { nopeid, sortableId, prefixedId } from 'nope-id/non-secure'

nopeid()      // Uses Math.random() - faster but not cryptographically secure
sortableId()  // Still has monotonic guarantee
prefixedId('user') // Still works the same way

// CommonJS
const { nopeid, sortableId } = require('nope-id/non-secure')
const id = nopeid()

Warning: Do not use for security-sensitive purposes like tokens, passwords, or session IDs!

Use cases for non-secure version:

  • UI element IDs (React keys, etc.)
  • Temporary file names
  • Log correlation IDs
  • Test fixtures
  • Any case where cryptographic security is not required

Real-World Examples

Database Primary Keys

// ES Modules
import { prefixedId, sortableId } from 'nope-id'

// User table with prefixed IDs
const user = {
  id: prefixedId('usr'),  // "usr_V1StGXR8_Z5jdHi6B-myT"
  email: 'john@example.com'
}

// Orders with sortable IDs (auto-sorted by creation time)
const order = {
  id: sortableId(),  // "01HGW2BBK0QZRMTX12345A"
  userId: user.id,
  total: 99.99
}

// CommonJS
const { prefixedId, sortableId } = require('nope-id')

API Token Generation

// ES Modules
import { nopeid, prefixedId } from 'nope-id'

// API keys
const apiKey = prefixedId('sk', 32)  // "sk_V1StGXR8_Z5jdHi6B-myTV1StGXR8_"

// Refresh tokens
const refreshToken = nopeid(64)  // 64 char secure token

// CommonJS
const { nopeid, prefixedId } = require('nope-id')

URL Shortener

// ES Modules
import { slugId, shortId } from 'nope-id'

// Short URLs
const shortUrl = `https://short.ly/${slugId(8)}`
// "https://short.ly/a8b3c9d2"

// User-friendly codes
const shareCode = shortId(6)  // "Kp3Wn8" (no confusing characters)

// CommonJS
const { slugId, shortId } = require('nope-id')

Distributed Systems

// ES Modules
import { distributedId, sortableId, getFingerprint } from 'nope-id'

// Multi-node safe IDs
const eventId = distributedId()
// "aB3x_V1StGXR8_Z5jdHi6B"

// Log with node identification
console.log(`[${getFingerprint()}] Processing event ${eventId}`)

// Time-series data with sortable IDs
const metric = {
  id: sortableId(),
  timestamp: new Date(),
  value: 42
}

// CommonJS
const { distributedId, getFingerprint } = require('nope-id')

React / Next.js

// ES Modules (React)
import { nopeid, prefixedId } from 'nope-id'

function TodoList({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={nopeid()}>{item.text}</li>
      ))}
    </ul>
  )
}

function CreateUser() {
  const [userId] = useState(() => prefixedId('usr'))
  // ...
}

// For non-critical UI keys, use non-secure version for better performance
import { nopeid } from 'nope-id/non-secure'

Express.js API

// CommonJS
const express = require('express')
const { prefixedId, sortableId, uuid } = require('nope-id')

const app = express()

app.post('/users', (req, res) => {
  const user = {
    id: prefixedId('usr'),
    ...req.body
  }
  // Save user...
  res.json(user)
})

app.post('/orders', (req, res) => {
  const order = {
    id: sortableId(),  // Sortable by creation time
    ...req.body
  }
  // Save order...
  res.json(order)
})

// For systems expecting UUID
app.post('/legacy-api', (req, res) => {
  const record = {
    id: uuid(),
    ...req.body
  }
  res.json(record)
})

Comparison with nanoid

Feature nope-id nanoid
Secure random βœ… βœ…
URL-safe βœ… βœ…
TypeScript βœ… βœ…
ESM + CommonJS βœ… ⚠️ ESM-only (dropped CJS in v4)
Prefixed IDs βœ… ❌
Sortable IDs (ULID-like) βœ… ❌
Monotonic guarantee βœ… ❌
Timestamp decoding βœ… ❌
Distributed IDs βœ… ❌
Process fingerprint βœ… ❌
UUID v4 βœ… ❌
UUID v7 (time-ordered) βœ… ❌
ULID (spec-compliant) βœ… ❌
Monotonic ULID factory βœ… ❌
Snowflake IDs βœ… ❌
MongoDB ObjectId βœ… ❌
Sqids (reversible encoding) βœ… ❌
Typed IDs (TS template types) βœ… ❌
Format validators (UUID/ULID) βœ… ❌
Slug IDs βœ… ❌
Short IDs (no lookalikes) βœ… ❌
Batch generation βœ… ❌
Validation βœ… ❌
Collision calculator βœ… ❌
Pre-built alphabets βœ… (14) Limited

Browser Support

Works in all modern browsers with Web Crypto API support.

<script type="module">
  import { nopeid, prefixedId } from 'https://unpkg.com/nope-id/index.browser.js'

  console.log(nopeid())        // "V1StGXR8_Z5jdHi6B-myT"
  console.log(prefixedId('user')) // "user_V1StGXR8_..."
</script>

Security

nope-id is designed with security as a top priority. We've implemented multiple security hardening measures that go beyond basic cryptographic randomness.

Cryptographic Security

  • Node.js: webcrypto.getRandomValues() (CSPRNG)
  • Browser: crypto.getRandomValues() (CSPRNG)

Security Hardening

Security Feature Description
Timing Attack Prevention isValid() uses constant-time comparison to prevent timing side-channel attacks that could leak information about valid characters
Modulo Bias Elimination Uses rejection sampling to ensure perfectly uniform distribution for all alphabet sizes (not just powers of 2)
Prototype Pollution Protection alphabets object is frozen with null prototype - immune to prototype pollution attacks
Integer Overflow Protection collisionProbability() uses BigInt for accurate calculations with astronomically large numbers
DoS Prevention sortableId() has iteration limits to prevent infinite loops from frozen system clocks
Buffer Safety Pool management handles >65536 byte requests safely with chunked filling

Best Practices

  • Use the secure version (default) for tokens, session IDs, API keys
  • Use sortableId() for time-based ordering with collision resistance
  • Use distributedId() in multi-node deployments
  • Only use nope-id/non-secure for non-security-critical purposes

Testing

nope-id has comprehensive test coverage with 307 tests across 6 test suites, including security-specific tests.

Run Tests

# Run all tests
npm test

# Run specific test suites
npm run test:core        # Core functions (nopeid, customAlphabet, random)
npm run test:features    # Features (prefixedId, sortableId, uuid, etc.)
npm run test:utils       # Utilities (isValid, collisionProbability)
npm run test:non-secure  # Non-secure version tests
npm run test:idtypes     # New ID types (uuidv7, ulid, snowflake, objectId)
npm run test:encoding    # Sqids, typed IDs, format validators

Test Coverage

Test Suite Tests Description
Core 81 nopeid, customAlphabet, customRandom, random, alphabets
Features 78 prefixedId, sortableId, uuid, slugId, shortId, distributedId
Utils 56 isValid, collisionProbability, security tests
Non-Secure 29 Math.random() based version
ID Types 31 uuidv7, ulid, monotonicFactory, snowflake, objectId
Encoding 32 sqids, defineId, isValidUUID, isValidULID
Total 307 All tests passing

Security Tests

We have dedicated security tests that verify our hardening measures:

πŸ“¦ isValid() Security
  βœ… rejects strings with null bytes
  βœ… rejects strings with unicode zero-width chars
  βœ… constant-time validation (timing attack prevention)

πŸ“¦ Prototype Pollution Prevention
  βœ… alphabets object is frozen
  βœ… alphabets has null prototype
  βœ… alphabets cannot be modified
  βœ… alphabets does not inherit Object.prototype properties

πŸ“¦ Modulo Bias Prevention (customAlphabet)
  βœ… uniform distribution for non-power-of-2 alphabet
  βœ… uniform distribution for 3-char alphabet

πŸ“¦ Integer Overflow Prevention
  βœ… collisionProbability returns BigInt for large values
  βœ… BigInt is accurate for values exceeding MAX_SAFE_INTEGER
  βœ… totalPossible is clamped to MAX_SAFE_INTEGER

πŸ“¦ Security: Entropy Quality
  βœ… random bytes have good entropy distribution
  βœ… generated IDs have good character distribution
  βœ… no predictable patterns in sequential IDs
  βœ… chi-square test for randomness

Stress Tests

πŸ“¦ Stress Tests
  βœ… rapid sequential generation (10000 IDs) - all unique
  βœ… pool exhaustion and refill cycle
  βœ… varying sizes in sequence
  βœ… alternating between different generators

Randomness Comparison Test (vs nanoid)

We have a dedicated randomness comparison test against nanoid:

npm run test:randomness
Test nope-id nanoid
Chi-Square Distribution βœ… χ²=44.69 βœ… χ²=48.96
Uniqueness (100K IDs) βœ… 0 duplicates βœ… 0 duplicates
Bit Distribution βœ… 50.03/49.97% βœ… 49.95/50.05%
Sequential Correlation βœ… 0.316 avg βœ… 0.329 avg
Alphabet Coverage βœ… 64/64 (100%) βœ… 64/64 (100%)
Modulo Bias (3-char) βœ… 1.05% deviation βœ… 1.15% deviation

Performance

About these numbers. These tables are refreshed automatically by a GitHub Actions workflow on every PR against main, running on a shared ubuntu-latest runner. CI runners are typically slower than modern developer machines and production servers, so on real hardware nope-id often hits higher numbers than what you see here. Run npm run benchmark locally to see numbers on your own machine.

Last refreshed: 2026-05-27, Node v26.x, ubuntu-latest (GitHub Actions).

nope-id vs nanoid Benchmark

nope-id wins all 5 core benchmarks vs nanoid 5.1.11, and ships many extra ID formats and security hardening on top.

Run the benchmark yourself:

npm run benchmark

Results (Node.js 20+, nanoid 5.1.11, auto-calibrated ~120 ms Γ— best of 7 trials, with a global warmup for fairness; absolute numbers vary by machine):

Test nanoid 5.1.11 nope-id Winner
Basic (21 chars) ~5.4M ops/sec ~40.1M ops/sec nope-id ~7.5x
Small (10 chars) ~10.1M ops/sec ~44.5M ops/sec nope-id ~4.4x
Large (64 chars) ~2.1M ops/sec ~18.9M ops/sec nope-id ~9.1x
Custom Alphabet ~5.7M ops/sec ~19.9M ops/sec nope-id ~3.5x
Batch (100 IDs) ~54K ops/sec ~438K ops/sec nope-id ~8.1x

Result: nope-id wins 5/5 against nanoid for URL-safe IDs, while providing many extra features and security hardening.

Where the speed comes from (and where it doesn't)

nope-id is the fastest JavaScript library for a specific, very common job: a cryptographically secure, URL-safe id over a full 64-character alphabet (the nanoid niche). In that category it beats nanoid by a wide margin at every standard size (see the comparison table above for exact ratios), and it also produces UUIDv7 and ULID far faster than the dedicated packages (see the UUID and ULID tables below).

The speed comes from the engineering, not from cutting corners on randomness:

  • Cached pool string: the CSPRNG byte pool is translated in place to alphabet character codes on refill, then decoded once to a flat one-byte string (idPool.toString('latin1')) cached as idPoolStr. Each nopeid() call then returns a single idPoolStr.substring(start, end), a V8 SlicedString (O(1), zero-copy) for sizes β‰₯ 13 and a tiny inline copy below that, instead of paying Buffer.toString's ~50 ns fixed cost per call. Same trick powers customAlphabet, uuid(), and friends.
  • 16-bit batch refill: the in-place translation walks the pool in 16-bit chunks via a precomputed 64 KiB Uint16Array table that maps any two random bytes directly to two alphabet codes, halving the refill iteration count (endian-agnostic by construction).
  • Pooled CSPRNG: one crypto.getRandomValues() fill covers thousands of IDs at the default size instead of one syscall per ID. uuid() goes further: it pre-formats 4096 v4 UUIDs (with version + RFC 4122 variant bits already patched per slot) into one 144 KiB string, so each call is just a substring(start, start+36).
  • Bitmask, no modulo: alphabet index comes from byte & 63 on a 64-character alphabet, so there is no rejection sampling or modulo bias on the hot path.
  • Precomputed tables for everything else: byte to hex for uuid()/uuidv7()/objectId(), char codes for Crockford in ulid()/monotonicFactory(), plus reusable module-scope scratch buffers (UUID_BUF, SORT_BUF, ULID_BUF) so the per-call path never allocates.
  • Allocation-free customAlphabet: reads the shared byte pool directly, mapping rejected-sampled bytes into a pre-decoded char-code pool whose hot path is again substring(start, end); this also speeds up slugId() and shortId().

What nope-id does not try to beat:

  • Smaller-alphabet generators like uid (16-char hex). A 21-char uid is ~84 bits; nope-id's 21 chars are ~126 bits, so at equal entropy nope-id is actually faster, but for short hex IDs uid is still a fine choice.

So nope-id's goal is to be the fastest while preserving maximum randomness per character, in one zero-dependency, dual-module package.

UUID generation vs the uuid package and native crypto.randomUUID()

A benchmark is only meaningful against more than one tool (thanks to nanoid's author for the nudge in #4). Here's the honest picture for UUIDs:

Generator ops/sec
crypto.randomUUID() (Node native, v4) ~21.7M C++ binding (plain v4 only)
nope-id uuid() (v4) ~25.1M πŸ₯‡ fastest pure-JS v4
@lukeed/uuid v4() ~6.9M optimized pure-JS v4
uuid package v4() ~5.9M
nope-id uuidv7() ~5.0M ~11x the uuid package's v7
uuid package v7() ~445K

Honest take: nope-id's uuid() pre-formats 4096 v4 UUIDs per CSPRNG refill, so each call is just a substring(). Result: at least on par with native crypto.randomUUID(), and ahead of it in current CI. The two trade places on real hardware (shared CSPRNG entropy path plus runner noise), so treat them as effectively tied for speed. If a plain v4 UUID is all you need and you do not want a dependency, the stdlib does the job. But if you are already using nope-id for anything else (UUIDv7, ULID, Snowflake, ObjectId, Sqids, typed IDs, nanoid-style short IDs, or just faster URL-safe IDs than nanoid), there is no reason to reach for native; uuid() is at least as fast, dual-module, and zero-dependency.

ULID (sortable) vs the ulid package

ULIDs are an attractive alternative to UUIDs: lexicographically sortable, a compact 26 characters (vs UUID's 36), Crockford base32 (URL-safe, case-insensitive), 128-bit and UUID-compatible, with a monotonic option that handles the same millisecond correctly. Unlike random UUID v4, their time prefix keeps database indexes from fragmenting.

nope-id ships a spec-compliant ulid() plus an isolated monotonicFactory(). Same 26-char format as the ulid package, both crypto-backed:

Generator ops/sec
nope-id ulid() ~2.9M
ulid package ~32K
nope-id monotonicFactory() ~8.5M
ulid package (monotonic) ~2.1M

nope-id is far faster for plain ulid() because it draws randomness from a pooled buffer (one fill per 16 IDs), whereas the ulid package fetches randomness per character. Decode the timestamp from either with decodeTime(). (The ulid package is also zero-dependency.)

Sortable head-to-head: sparkid vs nope-id sortableId()

Both are CSPRNG-backed, time-sortable + monotonic generators with similar internal architecture (pre-translated random-byte pool, lookup tables, cached prefix string). Defaults differ though: sparkid is 21-char Base58, nope-id's sortableId() is 22-char Crockford Base32 (22 is the safe minimum to keep the strong monotonic guarantee). So this is a natural-defaults comparison rather than a strict format-identical one.

Generator ops/sec
nope-id sortableId() (22-char Crockford) ~6.4M
sparkid (21-char Base58) ~9.8M

Speed vs entropy: where each library lands

Two things matter for an id generator: speed and entropy, the amount of real randomness each id carries (its security against being guessed). These are all good libraries; the table shows the trade-off each one makes, measured at 21 characters where the length is configurable:

Generator ops/sec entropy / id randomness source
nope-id nopeid() ~40.1M ~126 bits (64-char URL-safe) CSPRNG
uid/secure ~6.1M ~84 bits (16-char hex) CSPRNG
nanoid ~5.4M ~126 bits (64-char URL-safe) CSPRNG
sparkid ~9.8M ~76 bits random (Base58, time-sortable) CSPRNG
rndm ~2.8M ~125 bits, but predictable Math.random (not secure)
secure-random-string ~405K ~126 bits (base64, not URL-safe) CSPRNG
cuid2 createId() ~5.6K 24-char, hash-derived CSPRNG + SHA-3

Read as two axes, speed and security, every other library gives something up on one of them:

  • uid/secure is fast, but pays with a smaller alphabet: ~84 bits per 21-char id against nope-id's ~126. For equal security you would have to make uid longer, at which point nope-id is already ahead per bit.
  • rndm is fast too, but it is built on Math.random, so its bits are predictable; its own README calls it "not cryptographically secure."
  • secure-random-string matches nope-id's entropy but is roughly 80x slower and emits base64 (not URL-safe).
  • cuid2 spends speed on purpose for a hardened, sharding-safe, hash-based model.
  • sparkid is CSPRNG-backed, time-sortable + monotonic, and very fast in its niche; it spends 8 of its 21 chars on a Base58 timestamp prefix, leaving ~76 bits of unguessable randomness per id (close to ULID territory). If you want maximum randomness per id, nope-id's nopeid() keeps the full 126 bits at the same length. If you specifically want sortable + monotonic, sparkid holds its own (see the head-to-head above); nope-id also offers sortableId(), ulid(), and monotonicFactory() for ULID-compatible 26-char Crockford output.
  • nanoid matches nope-id's entropy exactly (same 64-char alphabet); nope-id is simply ~7.5x faster at the default 21-char size.

nope-id is the one row that has all three at once: maximum entropy per character (126 bits), a real CSPRNG, and top-tier speed. That is the whole design goal, fast without ever spending randomness to get there. (For a plain v4 UUID, native crypto.randomUUID() is roughly tied with nope-id's uuid() at 122 bits in C++; reach for the stdlib if a v4 UUID is all you need and you do not want a dependency.)

Extra Features Performance

These features are exclusive to nope-id (nanoid doesn't have them):

Feature Performance
sortableId() ~6.4M ops/sec
prefixedId() ~28.9M ops/sec
uuid() ~25.6M ops/sec
slugId() ~6.6M ops/sec
shortId() ~14.1M ops/sec
isValid() ~7.1M ops/sec
uuidv7() ~5.1M ops/sec
ulid() ~2.8M ops/sec
monotonicFactory() ~8.5M ops/sec
snowflake (factory) ~4.1M ops/sec
objectId() ~7.2M ops/sec
sqids.encode() ~212K ops/sec

Disclaimer

While nope-id uses cryptographically secure random number generators (crypto.getRandomValues) and implements security best practices, no software can guarantee 100% randomness or absolute security. The quality of randomness ultimately depends on the underlying operating system's entropy source.

For extremely high-security applications (e.g., cryptographic keys, long-term secrets), consider using dedicated cryptographic libraries that have undergone formal security audits.

nope-id is provided "as is" without warranty of any kind. Always evaluate whether it meets your specific security requirements.


License

MIT

About

Generate Random ID with Time and Crypto

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors