Skip to main content

Implementing UUIDs in TypeScript

UUIDs (Universally Unique Identifiers) are essential for creating unique identifiers in applications. They're 128-bit numbers that help maintain data integrity by providing globally unique "ID cards" for your data.

How to Generate UUIDs in TypeScript

The most common way to generate UUIDs in TypeScript is by using the uuid library. Here's a straightforward implementation:

import { v4 as uuidv4 } from 'uuid';

const uuid = uuidv4();
console.log(uuid); // e.g. "123e4567-e89b-12d3-a456-426614174000"

This code imports the v4 function from the uuid package (which implements UUID version 4) and generates a random UUID. You'll need to install the package first using:

npm install uuid
npm install @types/uuid --save-dev

The @types/uuid package provides TypeScript types for better type checking and autocompletion when working with UUIDs.

If you prefer a more lightweight approach, you can also use the Web Crypto API, which is built into modern browsers:

function generateUuidWithCrypto(): string {
return ([1e7] as any + -1e3 + -4e3 + -8e3 + -1e11).replace(
/[018]/g,
(c: string) =>
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4)).toString(16)
);
}

const cryptoUuid = generateUuidWithCrypto();
console.log(cryptoUuid);

When working with TypeScript function types, you can create strongly-typed UUID generator functions that ensure consistent return values.

For server-side applications using Convex, you can integrate UUID generation directly with your backend services. Check out Convex's TypeScript best practices for more information on implementing this pattern effectively.

Ensuring UUIDs are Unique and Valid

UUIDs are designed to be globally unique, but it's still important to validate them in your applications. Here are key approaches for ensuring UUID integrity:

Validating UUID Format

You can validate UUIDs using regular expressions or dedicated libraries:

// Using regex validation
function isValidUuid(uuid: string): boolean {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(uuid);
}

// Using uuid-validate library
import { validate } from 'uuid-validate';

const isValid = validate('123e4567-e89b-12d3-a456-426614174000'); // returns true
const isInvalid = validate('not-a-valid-uuid'); // returns false

Preventing Collisions in Distributed Systems

In distributed systems, you might want additional safeguards:

class UuidService {
private generatedUuids: Set<string> = new Set();

generateUnique(): string {
let uuid: string;
do {
uuid = uuidv4();
} while (this.generatedUuids.has(uuid));

this.generatedUuids.add(uuid);
return uuid;
}
}

For more reliable collision prevention at scale, consider using Convex's backend services which can handle unique ID generation and validation across distributed systems.

Type-Safe UUID Handling

When working with TypeScript type assertion, you can create branded types for extra type safety:

type UUID = string & { readonly __brand: unique symbol };

function createUUID(value: string): UUID {
if (!isValidUuid(value)) {
throw new Error('Invalid UUID format');
}
return value as UUID;
}

This approach leverages TypeScript utility types to create nominal types that prevent accidental assignment of plain strings to UUID variables.

For production applications using Convex, review their best practices documentation for guidance on handling unique identifiers in your backend systems.

Using UUIDs in TypeScript Applications

In a TypeScript application, UUIDs can serve as unique identifiers for entities like users or products.

Database Integration

interface User {
id: string;
name: string;
email: string;
}

interface Product {
id: string;
name: string;
price: number;
}

// Generate UUIDs for new entities
const createUser = (name: string, email: string): User => ({
id: uuidv4(),
name,
email
});

const createProduct = (name: string, price: number): Product => ({
id: uuidv4(),
name,
price
});

UUID-Based Entity Management

When building applications with TypeScript generics, you can create flexible entity management systems:

class EntityManager<T extends { id: string }> {
private entities: Map<string, T> = new Map();

add(entity: T): void {
this.entities.set(entity.id, entity);
}

get(id: string): T | undefined {
return this.entities.get(id);
}

remove(id: string): boolean {
return this.entities.delete(id);
}
}

// Usage
const userManager = new EntityManager<User>();
const newUser = createUser('Jane Doe', 'jane@example.com');
userManager.add(newUser);

API Integration

When designing APIs, UUIDs provide consistent identifiers across frontend and backend:

// API request interface
interface UserRequest {
id: string;
operation: 'create' | 'update' | 'delete';
data?: Partial<User>;
}

// API handler
async function handleUserRequest(request: UserRequest): Promise<void> {
switch (request.operation) {
case 'create':
// Create user with UUID
break;
case 'update':
// Update user by UUID
break;
case 'delete':
// Delete user by UUID
break;
}
}

For backend services using Convex, refer to their modules documentation for implementing UUID-based operations with their database system.

Typing UUIDs in TypeScript

TypeScript offers multiple approaches for typing UUIDs, from simple strings to custom branded types. Here's how to implement type-safe UUID handling:

Basic UUID Types

// Simple string alias
type UUID = string;

// Interface with UUID
interface Entity {
id: UUID;
createdAt: Date;
updatedAt: Date;
}

Branded Types for Enhanced Type Safety

For stricter type checking, use branded types to prevent accidental string assignments:

// Branded type definition
type UUID = string & { readonly __uuid: unique symbol };

// Factory function to create UUID type
function createUUID(value: string): UUID {
// Validation logic here
return value as UUID;
}

// Usage with interfaces
interface User {
id: UUID;
name: string;
email: string;
}

// Type-safe function
function getUser(id: UUID): User | null {
// Implementation
return null;
}

// This will cause a type error
// const userId: UUID = "regular-string"; // ❌ Type error

// Correct usage
const validUUID = createUUID("123e4567-e89b-12d3-a456-426614174000");
const user = getUser(validUUID); // ✅ Works

Using Type Guards

Create type guards to validate UUID strings at runtime:

function isUUID(value: unknown): value is UUID {
if (typeof value !== 'string') return false;

const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidPattern.test(value);
}

// Usage in functions
function processEntity(value: unknown) {
if (isUUID(value)) {
// TypeScript knows this is a UUID
const userId: UUID = value;
// Process the UUID
}
}

Integration with Generics

Combine UUID types with TypeScript generics for reusable components:

interface Identifiable<T extends string = UUID> {
id: T;
}

class Repository<T extends Identifiable> {
private items: Map<T['id'], T> = new Map();

save(item: T): void {
this.items.set(item.id, item);
}

findById(id: T['id']): T | undefined {
return this.items.get(id);
}
}

For more advanced type manipulations with UUIDs, explore TypeScript utility types. When working with backend systems, review Convex's TypeScript best practices for typing strategies in production applications.

Using UUIDs with Convex's Backend Service

When integrating UUIDs with Convex, generate them client-side and pass them to your backend mutations:

Client-Side UUID Generation

import { v4 as uuidv4 } from 'uuid';
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function CreateUser() {
const createUser = useMutation(api.users.create);

const handleCreate = async (name: string, email: string) => {
const userId = uuidv4();
await createUser({ id: userId, name, email });
};

return /* UI component here */;
}

Backend Validation and Storage

Implement server-side validation in your Convex mutations:

// convex/users.ts
import { v } from 'convex/values';
import { mutation } from './_generated/server';

// UUID validation schema
const uuidSchema = v.string();

export const create = mutation({
args: {
id: uuidSchema,
name: v.string(),
email: v.string(),
},
handler: async (ctx, args) => {
// Validate UUID format
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (!uuidRegex.test(args.id)) {
throw new Error('Invalid UUID format');
}

// Check for existing ID
const existing = await ctx.db.query('users')
.filter(q => q.eq(q.field('id'), args.id))
.first();

if (existing) {
throw new Error('UUID already exists');
}

// Insert user
return ctx.db.insert('users', {
id: args.id,
name: args.name,
email: args.email,
createdAt: Date.now(),
});
},
});

UUID-Based Queries

Create query functions that leverage UUIDs for efficient data retrieval:

// convex/users.ts
export const getById = query({
args: { id: v.string() },
handler: async (ctx, args) => {
return ctx.db.query('users')
.filter(q => q.eq(q.field('id'), args.id))
.first();
},
});

For optimal performance with UUID lookups, consider adding database indexes. Check Convex's documentation on modules for indexing strategies.

Type-Safe Patterns

Define shared types for consistency across your application:

// types.ts
export type UUID = string;

export interface BaseEntity {
id: UUID;
createdAt: number;
updatedAt?: number;
}

export interface User extends BaseEntity {
name: string;
email: string;
}

When implementing authentication or complex queries with UUIDs, refer to Convex's TypeScript best practices for production-ready patterns.

Comparing and Validating UUIDs

UUID comparison and validation are critical operations in TypeScript applications. Here's how to implement them correctly:

String Comparison

:

function compareUUIDs(uuid1: string, uuid2: string): boolean {
return uuid1 === uuid2;
}

// Case-insensitive comparison
function compareUUIDsIgnoreCase(uuid1: string, uuid2: string): boolean {
return uuid1.toLowerCase() === uuid2.toLowerCase();
}

Validation Methods

// Basic regex validation
function validateUuidFormat(uuid: string): boolean {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(uuid);
}

// Version-specific validation
function validateUuidVersion(uuid: string, version: number): boolean {
if (!validateUuidFormat(uuid)) return false;

const versionChar = uuid.charAt(14);
return parseInt(versionChar, 16) === version;
}

// Using uuid-validate library
import { validate, version } from 'uuid-validate';

const isValid = validate('123e4567-e89b-12d3-a456-426614174000');
const uuidVersion = version('123e4567-e89b-12d3-a456-426614174000'); // returns 4

Type-Safe Comparison

Use TypeScript type assertion for safer comparisons:

type UUID = string & { readonly __uuid: unique symbol };

function assertUUID(value: string): asserts value is UUID {
if (!validateUuidFormat(value)) {
throw new Error('Invalid UUID format');
}
}

function safeCompare(uuid1: string, uuid2: string): boolean {
assertUUID(uuid1);
assertUUID(uuid2);
return uuid1 === uuid2;
}

Batch Validation

When validating multiple UUIDs, optimize with batch operations:

function validateUUIDBatch(uuids: string[]): Record<string, boolean> {
return uuids.reduce((result, uuid) => {
result[uuid] = validateUuidFormat(uuid);
return result;
}, {} as Record<string, boolean>);
}

// Find invalid UUIDs in a collection
function findInvalidUUIDs(uuids: string[]): string[] {
return uuids.filter(uuid => !validateUuidFormat(uuid));
}

For applications using Convex, implement these validation checks in your mutations before database operations. Review Convex's other recommendations for data validation patterns.

Creating Your Own UUID Generator

While the uuid library is recommended for production, understanding how to build your own generator helps you grasp UUID mechanics:

Basic UUID Generator

function generateSimpleUUID(): string {
// Version 4 UUID template
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}

Cryptographically Secure Generator

function generateSecureUUID(): string {
const crypto = window.crypto || (window as any).msCrypto;

return ([1e7] as any +-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, (c: string) =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}

Timestamp-Based Generator

function generateTimestampUUID(): string {
const timestamp = Date.now();
const hexTimestamp = timestamp.toString(16).padStart(12, '0');

// Simplified version 1 UUID
return `${hexTimestamp.slice(0, 8)}-${hexTimestamp.slice(8, 12)}-1xxx-yxxx-xxxxxxxxxxxx`
.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}

Custom Generator with Node ID

interface UUIDOptions {
version?: number;
node?: string;
}

function createCustomUUID(options: UUIDOptions = {}): string {
const { version = 4, node } = options;

let template = `xxxxxxxx-xxxx-${version}xxx-yxxx-xxxxxxxxxxxx`;

if (node) {
// Replace last 12 characters with node ID
template = template.slice(0, -12) + node.slice(0, 12);
}

return template.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}

For production applications, stick with established libraries like uuid. When implementing custom generators with TypeScript function types, ensure proper validation and error handling.

Final Thoughts on Using UUIDs in TypeScript

UUIDs are essential for creating unique identifiers in TypeScript applications. By following these best practices:

  • Use established libraries like uuid for production code
  • Implement proper validation to ensure UUID integrity
  • Apply type safety through branded types or interfaces
  • Consider performance implications when working with large datasets

For comprehensive TypeScript patterns, explore TypeScript generics and utility types to enhance your UUID implementations. When building with Convex, refer to their TypeScript best practices for production-ready solutions.