UUID generation can happen in three places: the browser or mobile client, the application server, or the database. Each has distinct trade-offs that interact with your consistency model, network architecture, and deployment topology. RFC 9562 defines UUID v4 and v7 but is silent on where generation should occur — that choice is yours. The Wikipedia UUID generation section summarizes the historical approaches; this guide focuses on the modern production trade-offs.

Strategy 1: Database-generated UUIDs

The database generates the UUID as a column default at insert time.

-- PostgreSQL 18+
CREATE TABLE events (
    id   uuid DEFAULT uuidv7() PRIMARY KEY,
    data jsonb NOT NULL
);

-- Insert without specifying id
INSERT INTO events (data) VALUES ('{"type": "click"}');
-- The database assigns id; retrieve with RETURNING id
INSERT INTO events (data) VALUES ('{"type": "view"}') RETURNING id;

Advantages:

Disadvantages:

Best for: Server-rendered applications, audit logs, event tables, any context where the application can always reach the database before needing the ID.

Strategy 2: Application-server-generated UUIDs

The application server generates the UUID before issuing the INSERT.

// Node.js — UUID v7 from uuid package
import { v7 as uuidv7 } from 'uuid';

async function createOrder(userId, totalCents) {
    const id = uuidv7();   // generated before the INSERT

    await db.query(
        'INSERT INTO orders (id, user_id, total_cents) VALUES ($1, $2, $3)',
        [id, userId, totalCents]
    );

    return id;   // known before database round trip completes
}
# Python 3.13+ — UUID v7 from standard library
import uuid

def create_order(user_id: str, total_cents: int) -> str:
    order_id = str(uuid.uuid7())
    db.execute(
        "INSERT INTO orders (id, user_id, total_cents) VALUES (%s, %s, %s)",
        (order_id, user_id, total_cents)
    )
    return order_id

Advantages:

Disadvantages:

Best for: Microservices, event-driven systems, any application where the ID is needed before the database write.

Strategy 3: Client-generated UUIDs

The browser, mobile app, or CLI generates the UUID before sending a request to the server.

// Browser — UUID v4 (built-in, no install)
const id = crypto.randomUUID();

// Browser — UUID v7 (uuid package, sortable)
import { v7 as uuidv7 } from 'uuid';
const id = uuidv7();

// Mobile — iOS (Foundation)
import Foundation
let id = UUID().uuidString   // UUID v4 — Foundation built-in

Advantages:

Disadvantages:

Best for: Offline-first apps, collaborative editors, local-first sync databases, and any client that must create records without a server round trip.

Comparing clock sources for UUID v7

UUID v7’s timestamp quality depends on the source of Date.now() or its equivalent:

PlatformClock resolutionNotes
Node.js / Deno / Bun1 msHigh-resolution, system monotonic clock
Browser (Date.now())1–100 ms (jitter added)Reduced resolution for fingerprinting protection — still sufficient for UUID v7 ordering
PostgreSQL uuidv7()1 msDatabase wall clock — may differ from application server clock
iOS Date()Sub-millisecondDate.now in Swift; NTP-synced by the OS
Android System.currentTimeMillis()1 msNTP-synced by the OS

Clock skew between nodes is addressed in UUID Clock Skew. For most applications, millisecond precision with a monotonic counter is sufficient — only high-frequency trading or strict event-ordering systems require sub-millisecond resolution.

Hybrid: client generates, server validates

Many production systems use a hybrid: the client generates a UUID and sends it with the request; the server validates it before using it:

// Server (Node.js / Express) — validate client UUID before INSERT
import { validate as uuidValidate, version as uuidVersion } from 'uuid';

app.post('/orders', async (req, res) => {
    const { id, totalCents } = req.body;

    // Validate format and version
    if (!uuidValidate(id) || ![4, 7].includes(uuidVersion(id))) {
        return res.status(400).json({ error: 'Invalid UUID' });
    }

    // Check for duplicates before INSERT
    const existing = await db.query('SELECT id FROM orders WHERE id = $1', [id]);
    if (existing.rows.length > 0) {
        return res.status(409).json({ error: 'Duplicate ID' });
    }

    await db.query(
        'INSERT INTO orders (id, total_cents) VALUES ($1, $2)',
        [id, totalCents]
    );

    res.status(201).json({ id });
});

This gives clients the latency benefit of offline ID creation while the server maintains control over what enters the database.

External references

Frequently asked questions

Should UUIDs be generated on the client or the server?

It depends on whether you need offline-first capability and whether you trust client-generated IDs. Clients can generate UUID v4 with crypto.randomUUID() or UUID v7 with the uuid npm package. Server generation is safer for audit trails and sequential ordering; database generation (e.g., PostgreSQL 18's uuidv7()) guarantees the ID exists before your application code sees it.

What is the benefit of generating UUIDs at the database layer?

The database becomes the single source of truth for ID generation. IDs are guaranteed to exist before any application code processes them, there is no risk of a client submitting a malformed or colliding UUID, and the generation function (e.g., uuidv7() in PostgreSQL 18) can include a database-level monotonic counter for strict per-millisecond ordering. The downside is that you cannot know the ID before the INSERT completes.

Can I generate UUID v7 on the client in a browser?

Yes. Use the uuid npm package's v7() function in any browser that supports Date.now() and crypto.getRandomValues() — which is all modern browsers. The uuid package uses a per-process monotonic counter for within-millisecond ordering. crypto.randomUUID() is built-in but only generates v4.