A Snowflake ID generator for Node.js • No dependencies • TypeScript support
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 |
- Generates unique IDs across distributed systems
- Works with CommonJS and ESM
- Full TypeScript support
- Parse IDs to extract timestamp, machine ID, and sequence
| 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).
npm install @toolkit-f/snowflake-idyarn add @toolkit-f/snowflake-idpnpm add @toolkit-f/snowflake-idbun add @toolkit-f/snowflake-idimport { 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"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
// }const { SnowflakeGenerator } = require('@toolkit-f/snowflake-id');
const generator = new SnowflakeGenerator({ machineId: 1 });
console.log(generator.nextIdString());.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();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']);Use BIGINT (64-bit integer).
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
amount DECIMAL(10, 2)
);Store as String to ensure compatibility with all clients.
const id = generator.nextIdString();
await collection.insertOne({ _id: id, ... });| 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 |
// 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 });
},
};// schema.prisma
model User {
id BigInt @id
username String
}Note: You must generate the ID in code before creating the record.
@Entity()
export class User {
@PrimaryColumn('bigint')
id: string; // TypeORM handles BigInt as string
}To prevent ID collisions, every running instance must have a unique machineId (0-1023).
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 });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 });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)
Partition machineId ranges by region:
| Region | machineId Range |
|---|---|
| us-east | 0-255 |
| us-west | 256-511 |
| eu-west | 512-767 |
| ap-south | 768-1023 |
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.
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
});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());
// 136941813297545216nGenerates the next unique ID as a string.
const generator = new SnowflakeGenerator({ machineId: 1 });
console.log(generator.nextIdString());
// "136941813297541120"
console.log(generator.nextIdString());
// "136941813297541121"Returns the configured machine ID.
const generator = new SnowflakeGenerator({ machineId: 42 });
console.log(generator.getMachineId());
// 42Returns the configured epoch timestamp.
const generator = new SnowflakeGenerator({ machineId: 1 });
console.log(generator.getEpoch());
// 1704067200000Returns the current sequence number.
const generator = new SnowflakeGenerator({ machineId: 1 });
generator.nextId();
console.log(generator.getSequence());
// 0
generator.nextId();
console.log(generator.getSequence());
// 1Returns the timestamp of the last generated ID.
const generator = new SnowflakeGenerator({ machineId: 1 });
generator.nextId();
console.log(generator.getLastTimestamp());
// 1737017365000createGenerator(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());
// 136941813297541120nDeconstructs 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
// }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}'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.000ZExtracts 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));
// 1Extracts 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));
// 1Validates 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));
// trueConverts 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"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"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']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 <= 60010106326016000import {
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);
// 22nimport 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;
}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"'
}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
