@toolkit-f/snowflake-id
v1.0.3
Published
Unique ID Generator For NodeJS
Maintainers
Readme
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-idYarn
yarn add @toolkit-f/snowflake-idpnpm
pnpm add @toolkit-f/snowflake-idBun
bun add @toolkit-f/snowflake-idQuick 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:
- Coordination Service: Use Redis/etcd to lease machineIds
- Hash-based: Hash instance ID to 0-1023 range (collision possible)
- 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());
// 136941813297545216nnextIdString(): 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());
// 42getEpoch(): number
Returns the configured epoch timestamp.
const generator = new SnowflakeGenerator({ machineId: 1 });
console.log(generator.getEpoch());
// 1704067200000getSequence(): 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());
// 1getLastTimestamp(): number
Returns the timestamp of the last generated ID.
const generator = new SnowflakeGenerator({ machineId: 1 });
generator.nextId();
console.log(generator.getLastTimestamp());
// 1737017365000Factory Function: createGenerator
createGenerator(config: SnowflakeConfig): SnowflakeGeneratorAlternative way to create a generator instance.
import { createGenerator } from '@toolkit-f/snowflake-id';
const generator = createGenerator({ machineId: 1 });
console.log(generator.nextId());
// 136941813297541120nUtility 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.000ZgetMachineId(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));
// 1getSequence(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));
// 1isValidSnowflake(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));
// truesnowflakeToString(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 <= 60010106326016000Exported 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);
// 22nTypeScript 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.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
