Skip to content

Toolkit-F/Snowflake-Id

Repository files navigation

Snowflake ID Logo

npm version npm downloads license TypeScript

A Snowflake ID generator for Node.js • No dependencies • TypeScript support

Snowflake-ID

Snowflake IDs are 64-bit unique identifiers. Sortable by time and can be generated across multiple servers without coordination and collision.

| 41 bits - Timestamp | 10 bits - Machine ID | 12 bits - Sequence |

Features

  • Generates unique IDs across distributed systems
  • Works with CommonJS and ESM
  • Full TypeScript support
  • Parse IDs to extract timestamp, machine ID, and sequence

Why Snowflake-ID?

Feature Snowflake ID UUID v7 UUID v4 Auto Increment
Sortable Yes (Time) Yes (Time) No Yes
Unique Distributed Global Global Single DB
DB Size 8 bytes (BigInt) 16 bytes (Bytes) 16 bytes 4/8 bytes
Index Fast (B-Tree) Fast (B-Tree) Slow (Fragmented) Fast
Performance ~3M ops/sec ~2M ops/sec ~5M ops/sec Database Limit
Coordination None None None Centralized

Perf Check: This library generates ~3 million IDs per second on a standard laptop (M1 Air).


Installation

npm

npm install @toolkit-f/snowflake-id

Yarn

yarn add @toolkit-f/snowflake-id

pnpm

pnpm add @toolkit-f/snowflake-id

Bun

bun add @toolkit-f/snowflake-id

Quick Start

Generate IDs

import { SnowflakeGenerator } from '@toolkit-f/snowflake-id';

// Create a generator with a unique machine ID (0-1023)
const generator = new SnowflakeGenerator({ machineId: 1 });

// Generate as BigInt
const id = generator.nextId();
console.log(id);  // 136941813297541120n

// Generate as String (recommended for JSON/APIs)
const idString = generator.nextIdString();
console.log(idString);  // "136941813297541121"

Parse Existing IDs

import { parseSnowflake } from '@toolkit-f/snowflake-id';

const parts = parseSnowflake('136941813297545217');
console.log(parts);
// {
//   id: 136941813297545217n,
//   timestamp: 2024-01-16T09:09:25.000Z,
//   machineId: 1,
//   sequence: 1
// }

CommonJS Support

const { SnowflakeGenerator } = require('@toolkit-f/snowflake-id');

const generator = new SnowflakeGenerator({ machineId: 1 });
console.log(generator.nextIdString());

Production Guide

1. Database Storage & BigInt Warning

⚠️ Important: Always use .nextIdString() for databases and APIs. JavaScript's Number type cannot safely hold 64-bit integers. IDs will lose precision and become incorrect if you cast them to Number

// BAD - Precision loss guarantees bugs
const id = generator.nextId();
const numericId = Number(id); 

// GOOD - Always treat as string
const stringId = generator.nextIdString();

PostgreSQL

Use BIGINT to store IDs efficiently.

CREATE TABLE users (
  id BIGINT PRIMARY KEY,
  username TEXT
);
// Correct: Pass as string, driver handles BIGINT conversion
const id = generator.nextIdString();
await db.query('INSERT INTO users (id, username) VALUES ($1, $2)', [id, 'alice']);

MySQL

Use BIGINT (64-bit integer).

CREATE TABLE orders (
  id BIGINT PRIMARY KEY,
  amount DECIMAL(10, 2)
);

MongoDB

Store as String to ensure compatibility with all clients.

const id = generator.nextIdString();
await collection.insertOne({ _id: id, ... });

Common Mistakes

Mistake Consequence Fix
Number(id) Data Corruption Use String(id) or BigInt
machineId: 0 everywhere ID Collisions Unique ID per instance
Math.random() for ID No Sorting Use Snowflake

Framework Integrations

NestJS

// snowflake.provider.ts
import { Provider } from '@nestjs/common';
import { SnowflakeGenerator } from '@toolkit-f/snowflake-id';

export const SNOWFLAKE_PROVIDER = 'SNOWFLAKE_GENERATOR';

export const snowflakeProvider: Provider = {
  provide: SNOWFLAKE_PROVIDER,
  useFactory: () => {
    const machineId = parseInt(process.env.MACHINE_ID || '0', 10);
    return new SnowflakeGenerator({ machineId });
  },
};

Prisma

// schema.prisma
model User {
  id        BigInt   @id
  username  String
}

Note: You must generate the ID in code before creating the record.

TypeORM

@Entity()
export class User {
  @PrimaryColumn('bigint')
  id: string; // TypeORM handles BigInt as string
}

2. Distributed Deployment (machineId)

To prevent ID collisions, every running instance must have a unique machineId (0-1023).

Kubernetes (StatefulSet)

Use the collection ordinal from the hostname (e.g., web-0, web-1).

const podName = process.env.HOSTNAME || 'web-0';
const machineId = parseInt(podName.split('-').pop() || '0', 10);
const generator = new SnowflakeGenerator({ machineId });

Docker Swarm / Replicas

Inject via environment variables:

services:
  api:
    environment:
      - MACHINE_ID={{.Task.Slot}}
const machineId = parseInt(process.env.MACHINE_ID || '0', 10);
const generator = new SnowflakeGenerator({ machineId });

Auto-scaling Groups

Options:

  1. Coordination Service: Use Redis/etcd to lease machineIds
  2. Hash-based: Hash instance ID to 0-1023 range (collision possible)
  3. Time-based: Use startup timestamp modulo 1024 (not recommended)

Multi-Region

Partition machineId ranges by region:

Region machineId Range
us-east 0-255
us-west 256-511
eu-west 512-767
ap-south 768-1023

3. Security Considerations

⚠️ Snowflake IDs allow public timestamp decoding. Anyone with the ID can calculate exactly when a record was created.

const { parseSnowflake } = require('@toolkit-f/snowflake-id');
console.log(parseSnowflake('136941813297545217').timestamp);
// 2024-01-16T09:09:25.000Z
  • Do not use Snowflake IDs if the creation time must remain secret.
  • Do not trust the timestamp for security verification (it can be spoofed by the client if they generate IDs).
  • Consider using a separate random slug (e.g., UUID or NanoID) for public-facing URLs if business metrics (like order volume) need to be hidden.

API Reference

Core Class: SnowflakeGenerator

Constructor

new SnowflakeGenerator(config: SnowflakeConfig)
Parameter Type Required Default Description
machineId number Yes - Unique ID for this machine/worker (0-1023)
epoch number No 1704067200000 (Jan 1, 2024) Custom epoch in milliseconds
clockMoveBackAction 'throw' | 'wait' No 'throw' Behavior when system clock drifts backwards

Example:

import { SnowflakeGenerator } from '@toolkit-f/snowflake-id';

const generator = new SnowflakeGenerator({
  machineId: 5,
  epoch: 1609459200000,        // Jan 1, 2021
  clockMoveBackAction: 'wait'  // Wait instead of throwing error
});

nextId(): bigint

Generates the next unique ID as a bigint.

const generator = new SnowflakeGenerator({ machineId: 1 });

console.log(generator.nextId());
// 136941813297541120n

console.log(generator.nextId());
// 136941813297541121n

console.log(generator.nextId());
// 136941813297545216n

nextIdString(): string

Generates the next unique ID as a string.

const generator = new SnowflakeGenerator({ machineId: 1 });

console.log(generator.nextIdString());
// "136941813297541120"

console.log(generator.nextIdString());
// "136941813297541121"

getMachineId(): number

Returns the configured machine ID.

const generator = new SnowflakeGenerator({ machineId: 42 });

console.log(generator.getMachineId());
// 42

getEpoch(): number

Returns the configured epoch timestamp.

const generator = new SnowflakeGenerator({ machineId: 1 });

console.log(generator.getEpoch());
// 1704067200000

getSequence(): number

Returns the current sequence number.

const generator = new SnowflakeGenerator({ machineId: 1 });

generator.nextId();
console.log(generator.getSequence());
// 0

generator.nextId();
console.log(generator.getSequence());
// 1

getLastTimestamp(): number

Returns the timestamp of the last generated ID.

const generator = new SnowflakeGenerator({ machineId: 1 });

generator.nextId();
console.log(generator.getLastTimestamp());
// 1737017365000

Factory Function: createGenerator

createGenerator(config: SnowflakeConfig): SnowflakeGenerator

Alternative way to create a generator instance.

import { createGenerator } from '@toolkit-f/snowflake-id';

const generator = createGenerator({ machineId: 1 });

console.log(generator.nextId());
// 136941813297541120n

Utility Functions

parseSnowflake(id, epoch?): SnowflakeParts

Deconstructs a Snowflake ID into its components.

Parameter Type Required Default Description
id bigint | string Yes - The Snowflake ID to parse
epoch number No 1704067200000 Custom epoch used during generation

Returns: SnowflakeParts

import { parseSnowflake } from '@toolkit-f/snowflake-id';

const parts = parseSnowflake('136941813297545217');
console.log(parts);
// {
//   id: 136941813297545217n,
//   timestamp: 2024-01-16T09:09:25.000Z,
//   timestampMs: 1737017365000,
//   machineId: 1,
//   sequence: 1
// }

const parts2 = parseSnowflake(136941813297545217n);
console.log(parts2);
// {
//   id: 136941813297545217n,
//   timestamp: 2024-01-16T09:09:25.000Z,
//   timestampMs: 1737017365000,
//   machineId: 1,
//   sequence: 1
// }

stringifySnowflakeParts(parts): SnowflakePartsJSON

Converts SnowflakeParts to a JSON-serializable format.

import { parseSnowflake, stringifySnowflakeParts } from '@toolkit-f/snowflake-id';

const parts = parseSnowflake('136941813297545217');
const json = stringifySnowflakeParts(parts);

console.log(json);
// {
//   id: "136941813297545217",
//   timestamp: "2024-01-16T09:09:25.000Z",
//   timestampMs: 1737017365000,
//   machineId: 1,
//   sequence: 1
// }

console.log(JSON.stringify(json));
// '{"id":"136941813297545217","timestamp":"2024-01-16T09:09:25.000Z","timestampMs":1737017365000,"machineId":1,"sequence":1}'

getTimestamp(id, epoch?): Date

Extracts the Date object from a Snowflake ID.

Parameter Type Required Default Description
id bigint | string Yes - The Snowflake ID
epoch number No 1704067200000 Custom epoch
import { getTimestamp } from '@toolkit-f/snowflake-id';

console.log(getTimestamp('136941813297545217'));
// 2024-01-16T09:09:25.000Z

console.log(getTimestamp(136941813297545217n));
// 2024-01-16T09:09:25.000Z

getMachineId(id): number

Extracts the machine ID from a Snowflake ID.

Parameter Type Required Description
id bigint | string Yes The Snowflake ID
import { getMachineId } from '@toolkit-f/snowflake-id';

console.log(getMachineId('136941813297545217'));
// 1

console.log(getMachineId(136941813297545217n));
// 1

getSequence(id): number

Extracts the sequence number from a Snowflake ID.

Parameter Type Required Description
id bigint | string Yes The Snowflake ID
import { getSequence } from '@toolkit-f/snowflake-id';

console.log(getSequence('136941813297545217'));
// 1

console.log(getSequence(136941813297545217n));
// 1

isValidSnowflake(id, epoch?, relaxed?): boolean

Validates if a value is a valid Snowflake ID.

Parameter Type Required Default Description
id unknown Yes - Value to validate
epoch number No 1704067200000 Custom epoch
relaxed boolean No false Skip timestamp range check
import { isValidSnowflake } from '@toolkit-f/snowflake-id';

console.log(isValidSnowflake('136941813297545217'));
// true

console.log(isValidSnowflake(136941813297545217n));
// true

console.log(isValidSnowflake('invalid'));
// false

console.log(isValidSnowflake('abc123'));
// false

console.log(isValidSnowflake(-1n));
// false

// Relaxed mode (skip timestamp validation)
console.log(isValidSnowflake('999999999999999999999', undefined, true));
// true

snowflakeToString(id): string

Converts a bigint Snowflake ID to string.

Parameter Type Required Description
id bigint Yes The Snowflake ID
import { snowflakeToString } from '@toolkit-f/snowflake-id';

console.log(snowflakeToString(136941813297545217n));
// "136941813297545217"

stringToSnowflake(str): bigint

Converts a string Snowflake ID to bigint.

Parameter Type Required Description
str string Yes The Snowflake ID string
import { stringToSnowflake } from '@toolkit-f/snowflake-id';

console.log(stringToSnowflake('136941813297545217'));
// 136941813297545217n

// Throws error for invalid input
stringToSnowflake('invalid');
// Error: Invalid Snowflake ID string: "invalid"

compareSnowflakes(a, b): -1 | 0 | 1

Compares two Snowflake IDs. Useful for sorting.

Parameter Type Required Description
a bigint | string Yes First Snowflake ID
b bigint | string Yes Second Snowflake ID

Returns: -1 if a < b, 0 if a === b, 1 if a > b

import { compareSnowflakes } from '@toolkit-f/snowflake-id';

console.log(compareSnowflakes('136941813297545216', '136941813297545217'));
// -1

console.log(compareSnowflakes('136941813297545217', '136941813297545217'));
// 0

console.log(compareSnowflakes('136941813297545218', '136941813297545217'));
// 1

// Sorting example
const ids = ['136941813297545218', '136941813297545216', '136941813297545217'];
ids.sort(compareSnowflakes);
console.log(ids);
// ['136941813297545216', '136941813297545217', '136941813297545218']

snowflakeFromTimestamp(date, machineId?, sequence?, epoch?): bigint

Creates a Snowflake ID from a specific timestamp. Useful for database range queries.

Parameter Type Required Default Description
date Date | number Yes - Timestamp to create ID from
machineId number No 0 Machine ID (0-1023)
sequence number No 0 Sequence number (0-4095)
epoch number No 1704067200000 Custom epoch
import { snowflakeFromTimestamp } from '@toolkit-f/snowflake-id';

// From Date object
const id1 = snowflakeFromTimestamp(new Date('2024-06-15T12:00:00.000Z'));
console.log(id1);
// 59918327808000000n

// From timestamp number
const id2 = snowflakeFromTimestamp(1718452800000);
console.log(id2);
// 59918327808000000n

// With machine ID and sequence
const id3 = snowflakeFromTimestamp(new Date('2024-06-15T12:00:00.000Z'), 5, 10);
console.log(id3);
// 59918327808020490n

// Database range query example
const startOfDay = snowflakeFromTimestamp(new Date('2024-06-15T00:00:00.000Z'));
const endOfDay = snowflakeFromTimestamp(new Date('2024-06-15T23:59:59.999Z'));
console.log(`SELECT * FROM items WHERE id >= ${startOfDay} AND id <= ${endOfDay}`);
// SELECT * FROM items WHERE id >= 59914215014400000 AND id <= 60010106326016000

Exported Constants

import {
  DEFAULT_EPOCH,
  MAX_MACHINE_ID,
  MAX_SEQUENCE,
  MACHINE_ID_SHIFT,
  TIMESTAMP_SHIFT
} from '@toolkit-f/snowflake-id';

console.log(DEFAULT_EPOCH);
// 1704067200000 (Jan 1, 2024 00:00:00 UTC)

console.log(MAX_MACHINE_ID);
// 1023

console.log(MAX_SEQUENCE);
// 4095n

console.log(MACHINE_ID_SHIFT);
// 12n

console.log(TIMESTAMP_SHIFT);
// 22n

TypeScript Types

import type {
  SnowflakeConfig,
  SnowflakeParts,
  SnowflakePartsJSON
} from '@toolkit-f/snowflake-id';

// SnowflakeConfig
interface SnowflakeConfig {
  machineId: number;              // 0-1023
  epoch?: number;                 // Custom epoch (ms)
  clockMoveBackAction?: 'throw' | 'wait';
}

// SnowflakeParts
interface SnowflakeParts {
  id: bigint;
  timestamp: Date;
  timestampMs: number;
  machineId: number;
  sequence: number;
}

// SnowflakePartsJSON
interface SnowflakePartsJSON {
  id: string;
  timestamp: string;
  timestampMs: number;
  machineId: number;
  sequence: number;
}

Error Handling

import { SnowflakeGenerator, stringToSnowflake, parseSnowflake } from '@toolkit-f/snowflake-id';

// Invalid machine ID
try {
  new SnowflakeGenerator({ machineId: 2000 });
} catch (e) {
  console.log(e.message);
  // "machineId must be integer 0-1023, got 2000"
}

// Future epoch
try {
  new SnowflakeGenerator({ machineId: 1, epoch: Date.now() + 100000 });
} catch (e) {
  console.log(e.message);
  // "epoch cannot be in the future"
}

// Invalid string ID
try {
  stringToSnowflake('not-a-number');
} catch (e) {
  console.log(e.message);
  // 'Invalid Snowflake ID string: "not-a-number"'
}

// Invalid parse input
try {
  parseSnowflake('abc123');
} catch (e) {
  console.log(e.message);
  // 'Invalid Snowflake ID: "abc123"'
}

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

🪪 License

MIT © Toolkit-F