It's straightforward enough to generate a unique ID in a closed system, but every now and again one may need to generate a unique ID that stays unique across different systems, regardless if they're in a connected state or not. Had to cook up this shiny routine to handle just that and well here it is in case anyone else wants to save a few keystrokes.
Introduction
Typically when someone wants to create a unique identifier that works across distributed/disconnected systems a UUID (GUID in Microsoft parlance) is leveraged. In the past, for JavaScript/TypeScript in Node, we had to use a UUID library for this or write one ourselves. These days, we have native a implementation of version 4 of the UUID specification using Crypto.randomUUID however. As such, we should take advantage of that.
Now, UUIDs are great, but they're also long, 36 characters
in fact. In order to help with that, here is a routine and associated helper routine to generate a UUID but shorten it into a 24 character
case sensitive string first. It also adds a teeny, tiny bit of extra entropy in the process. And while it's a straightforward process; hopefully, it'll help save you a few keystrokes should you need this functionality.
Also, given the fact that Crypto.randomUUID still isn't ubiquitously supported on older browsers, this code should be considered server-side. Be aware it's only available in Node 19 or higher. But, using the native implantation also means this code runs fast enough to generate 100,000 IDs in 340ms in a WSL environment.
Profiled via:
console.time('createUniqueId');
for (let x = 0; x < 100000; x++) createUniqueId();
console.timeEnd('createUniqueId');
The Goodies
This exposes two functions, one for the ID generation and one to convert a number into base62
to keep it short as possible. This follows in-line with sites such as YouTube or URL shorteners where otherwise longer IDs are kept short. JavaScript natively supports up to base36, but not 62. So, we have to roll that one ourselves.
It's implemented as an ESM module that's intended to run on the server via Node. In theory this code could be used on the client side as well after transpilation into JavaScript; however, at the time of this writing Crypto.randomUUID
isn't still widely supported in older browsers. Using a polyfill would defeat the purpose since there are already uuid
libraries out there and the goal here is speed. So, server-side for the win given the native implementation should run faster.
import crypto from 'node:crypto';
const CHARSET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const MAX_COUNT = 0xFFFF;
export function createUniqueId(): string | null {
const self = createUniqueId as any;
const count: number = (self.count && (self.count < MAX_COUNT)) ? self.count : 0;
let result = '';
const uuid = crypto.randomUUID();
const parts = uuid.split('-');
if (parts.length === 5) {
const one = toBase62(parseInt(parts[0], 16) || 0).padStart(6, '0');
const two = toBase62((parseInt(parts[1], 16) || 0) + count).padStart(3, '0');
const three = toBase62(parseInt(parts[2], 16) || 0).padStart(3, '0');
const four = toBase62(parseInt(parts[3], 16) || 0).padStart(3, '0');
const five = toBase62(parseInt(parts[4], 16) || 0).padStart(9, '0');
result = `${one}${two}${three}${four}${five}`;
}
self.count = count + 1;
return result || null;
}
export function toBase62(number: number): string {
let n = Math.floor(number);
if (n === 0) return CHARSET[0];
let result = '';
while (n > 0) {
result = CHARSET[n % 62] + result;
n = Math.floor(n / 62);
}
return result;
}
Using the code
Using the code is pretty straightforward, as one routine takes zero parameters and the other just takes one.
createUniqueId()
toBase62(30456)
Credits
The algorithm for the base62 conversation came from base62.js. However, it was modified to leverage ES6 string indexing to avoid the array cast and adds an additional sanity check.
History
2024-04-19: Initial release.
2024-04-25: Updated introduction.