JSON IDs : générer et gérer les identifiants

🏷️ Back-end 📅 12/04/2026 16:00:00 👤 Mezgani Said
Json Id Uuid Nanoid Database
JSON IDs : générer et gérer les identifiants

Maîtriser les IDs en JSON : UUID, nanoid, autoincrement. Bonnes pratiques, sécurité et patterns réutilisables.

Introduction et importance

Un ID (identifiant) est une valeur unique qui représente un objet dans une base de données ou une API. C'est un élément fondamental de toute application web moderne.

À retenir : Les IDs = identité numérique des ressources. Bien les choisir impacte performance, sécurité et scalabilité.

Critères d'un bon ID :

  • ✅ Unique (jamais de doublon)
  • ✅ Immuable (une fois créé, ne change pas)
  • ✅ Court (optimise la base de données)
  • ✅ Non-prédictible (sécurité)
  • ✅ Distribué (scalable, pas de bottleneck central)
  • ✅ Performant à générer et indexer

Impact sur votre application :

  • ⚡ Performance des requêtes
  • 🔒 Sécurité (IDs prédictibles = énumération)
  • 📈 Scalabilité (croissance de la base)
  • 💾 Taille du stockage

Types d'IDs

Type Format Avantages Inconvénients
Autoincrement 1, 2, 3... Simple, petit, indexé rapide Prédictible, pas distribué
UUID v4 550e8400-e29b-41d4-a716-446655440000 Unique globally, distribué Long (36 caractères), performance BD
UUID v6 1ef4c7a9-3e7c-6000-9000-000000000000 Sortable, performant index Moins connu, adoption lente
Nanoid V1StGXR_Z5j Compact (21 chars), rapide, URL-safe Moins d'adoption que UUID
Snowflake 1234567890123456789 Sortable, distribué (Twitter) Complexe à mettre en place

Autoincrement

Autoincrement = compteur qui s'incrémente automatiquement (1, 2, 3, etc.)

Avantages :

  • ✅ Très rapide à générer
  • ✅ Petit en taille (4-8 bytes)
  • ✅ Excellente performance d'indexation
  • ✅ Sortable par défaut (ordre chronologique)

Inconvénients :

  • ❌ Prédictible (/users/1, /users/2 — énumération)
  • ❌ Pas distribué (génération centralisée)
  • ❌ Expose données sensibles (nombre total d'users)
  • ❌ Problèmes avec multi-DB (collisions possibles)

Exemple SQL :

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100)
);

INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
-- ID généré automatiquement = 1

Cas d'usage appropriés :

  • ✅ Applications internes
  • ✅ Prototypage/MVP
  • ✅ IDs non exposés publiquement
  • ✅ Single database (pas distribué)

UUID v4 et v6

UUID = Universally Unique Identifier

UUID v4 (Random) :

550e8400-e29b-41d4-a716-446655440000
^       ^  ^  ^  ^    ^
2^122 combinaisons possibles (pratiquement infini)

Génération UUID v4 :

import crypto from 'crypto';

const id = crypto.randomUUID();
console.info(id); // "550e8400-e29b-41d4-a716-446655440000"

UUID v6 (Sortable) - Nouveau !

UUID v6 améliore v4 : commence par timestamp, donc sortable et meilleure perf d'indexation.

import { v6 as uuidv6 } from 'uuid';

const id = uuidv6();
// 1ef4c7a9-3e7c-6000-9000-000000000000 (timestamp au début)

Avantages UUID :

  • ✅ Unique globalement (distribué)
  • ✅ Pas d'énumération possible
  • ✅ Standard officiel (RFC 4122)
  • ✅ Compatible partout

Inconvénients UUID :

  • ❌ Très long (36 caractères avec tirets)
  • ❌ Stockage : 16 bytes vs 4 bytes pour int
  • ❌ Index plus lent (v4 est random)
  • ❌ Pas sortable (v4 seulement, v6 améliore ça)

Comparaison v4 vs v6 :

// UUID v4 (random) - mauvais pour indexation
550e8400-e29b-41d4-a716-446655440000
6ba7b810-9dad-11d1-80b4-00c04fd430c8
936da01f-9abd-4d9d-80c7-02af85c822a8

// UUID v6 (sortable) - meilleur pour indexation
1ef4c7a9-0000-6000-9000-000000000000
1ef4c7a9-0001-6000-9000-000000000001
1ef4c7a9-0002-6000-9000-000000000002

Nanoid : compact et performant

Nanoid est une alternative moderne à UUID, créée par Vercel (Next.js).

Exemple :

V1StGXR_Z5j
NQlCUeP7u8q
9ZyFfxJK2m_

Installation et utilisation :

npm install nanoid

import { nanoid } from 'nanoid';

const id = nanoid();
console.info(id);        // "V1StGXR_Z5j" (21 caractères par défaut)

const shortId = nanoid(10);
console.info(shortId);   // "Z5j_V1StGX" (10 caractères)

const customAlphabet = nanoid(12);
console.info(customAlphabet); // IDs de 12 caractères

Avantages Nanoid :

  • ✅ Très compact (21 chars vs 36 pour UUID)
  • ✅ URL-safe (pas de chars spéciaux)
  • ✅ Rapide à générer
  • ✅ Bonne entropie (216 bits)
  • ✅ Pas de tirets (meilleur pour URLs)

Inconvénients Nanoid :

  • ❌ Moins standardisé que UUID
  • ❌ Adopté principalement chez Vercel/Node community
  • ❌ Non sortable par défaut

Comparaison Tail :

Autoincrement     : 1 byte
UUID              : 36 chars (16 bytes)
Nanoid            : 21 chars (13 bytes)

Génération en JavaScript/Node.js

Autoincrement (Prisma) :

// schema.prisma
model User {
    id    Int     @id @default(autoincrement())
    name  String
    email String  @unique
}

UUID avec Prisma :

// schema.prisma
import { v4 } from 'uuid';

model User {
    id    String  @id @default(cuid())  // or uuid()
    name  String
    email String  @unique
}

Nanoid dans une API Express :

import express from 'express';
import { nanoid } from 'nanoid';

const app = express();
app.use(express.json());

const users = [];

app.post('/users', (req, res) => {
    const user = {
        id: nanoid(),  // Générer un Nanoid unique
        name: req.body.name,
        email: req.body.email,
        createdAt: new Date()
    };
    users.push(user);
    res.status(201).json(user);
});

app.get('/users/:id', (req, res) => {
    const user = users.find(u => u.id === req.params.id);
    res.json(user);
});

app.listen(3000);

Générer des IDs en batch :

import { nanoid } from 'nanoid';

// Générer 1000 IDs uniques
const ids = Array.from({ length: 1000 }, () => nanoid());

console.info(ids[0]);    // "V1StGXR_Z5j"
console.info(ids[999]);  // "9ZyFfxJK2m_"

Stockage et indexation

PostgreSQL avec UUID :

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(100),
    email VARCHAR(100)
);

INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
-- UUID généré automatiquement

MongoDB avec Nanoid :

import { MongoClient } from 'mongodb';
import { nanoid } from 'nanoid';

const client = new MongoClient(uri);
const db = client.db('myapp');
const users = db.collection('users');

await users.insertOne({
    _id: nanoid(),  // Utiliser Nanoid comme _id
    name: 'Alice',
    email: 'alice@example.com'
});

// Requête
const user = await users.findOne({ _id: 'V1StGXR_Z5j' });

Index performance :

-- Index sur ID (toujours créer un index primaire)
CREATE INDEX idx_user_id ON users(id);

-- Requête rapide (O(1) avec index)
SELECT * FROM users WHERE id = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';

-- Recherche lente sans index (O(n))
SELECT * FROM users WHERE email = 'alice@example.com';
-- Solution : ajouter index aussi
CREATE INDEX idx_user_email ON users(email);

Taille stockage comparée :

Type Taille Pour 1M documents
INT (Autoincrement) 4 bytes 4 MB
UUID (36 chars) 36 bytes 36 MB
Nanoid (21 chars) 21 bytes 21 MB

Sécurité et prédictibilité

❌ Danger : Autoincrement prévisible

GET /api/users/1
GET /api/users/2
GET /api/users/3
-- Attaquant peut énumérer tous les users !

✅ Solution : UUID ou Nanoid non-prédictible

GET /api/users/V1StGXR_Z5j
GET /api/users/NQlCUeP7u8q
-- Impossible d'énumérer sans connaître le format

Entropie des IDs :

Autoincrement  : 0 bits d'entropie (100% prédictible)
UUID v4        : 122 bits d'entropie (cryptographiquement sûr)
Nanoid         : 216 bits d'entropie (réellement aléatoire)

Bonnes pratiques sécurité :

  • ✅ Toujours utiliser UUID/Nanoid pour les ressources publiques
  • ✅ Vérifier authorization au-delà de l'ID (ce n'est pas authentification)
  • ✅ Ajouter rate limiting pour éviter énumération
  • ✅ Hasher/chiffrer les IDs sensibles

Vérification d'authorization :

app.get('/api/posts/:id', authenticateUser, async (req, res) => {
    const post = await Post.findById(req.params.id);

    // ❌ Mauvais : juste vérifier que l'ID existe
    if (!post) return res.status(404).send('Not found');

    // ✅ Bon : vérifier que c'est l'utilisateur propriétaire
    if (post.userId !== req.user.id) {
        return res.status(403).send('Unauthorized');
    }

    res.json(post);
});

Patterns réels

Pattern 1 : User ID + Ressource ID

// Meilleur pour permission, immutenabilité
POST /api/users/V1StGXR_Z5j/posts
{
  "id": "NQlCUeP7u8q",
  "title": "...",
  "userId": "V1StGXR_Z5j"
}

Pattern 2 : Verifies + UUID pour partage

// Document partageable, ID non-séquentiel
POST /api/documents
{
  "id": "3fa85f64-5717-4562-b3fc",  // UUID
  "shareLink": "https://app.com/share/abc123"  // Token unique
}

Pattern 3 : Composite ID

// Useful pour relationner efficacement
{
  "id": "org-123|proj-456|task-789",
  "organizationId": "org-123",
  "projectId": "proj-456",
  "taskId": "task-789"
}

Erreurs courantes

❌ Erreur 1 : Autoincrement pour données publiques

❌ Mauvais :
GET /api/public-profiles/1
GET /api/public-profiles/2
-- Énumération facile

✅ Bon :
GET /api/public-profiles/V1StGXR_Z5j
-- Impossible sans connaître l'ID exact

❌ Erreur 2 : Générer UUID via Date

❌ Mauvais :
const fakeId = Math.random().toString(36).substr(2, 9);
// Pas assez d'entropie, collisions possibles

✅ Bon :
import { nanoid } from 'nanoid';
const id = nanoid();
// 216 bits d'entropie, zéro collisions

❌ Erreur 3 : ID trop long dans URL

❌ Mauvais :
/api/users/550e8400-e29b-41d4-a716-446655440000
/api/users/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
-- URLs longues et inesthétiques

✅ Bon :
/api/users/V1StGXR_Z5j
-- Compact et lisible

❌ Erreur 4 : Pas d'index sur l'ID

❌ Mauvais :
SELECT * FROM users WHERE id = 123;
-- Si pas d'index, scan complet table (O(n))

✅ Bon :
CREATE INDEX idx_users_id ON users(id);
-- Recherche O(1) avec index B-tree

❌ Erreur 5 : Confondre ID et authentification

❌ Mauvais :
app.get('/api/profile/:userId', (req, res) => {
    const user = Users.findById(req.params.userId);
    // DANGER : n'importe quel ID accède au profil
    res.json(user);
});

✅ Bon :
app.get('/api/profile/:userId', authenticateUser, (req, res) => {
    // Vérifier que userId === req.user.id
    if (req.params.userId !== req.user.id) {
        return res.status(403).send('Forbidden');
    }
    res.json(req.user);
});

Bonnes pratiques

Checklist pour choisir un ID :

  • ✅ IDs publiques = UUID/Nanoid (jamais autoincrement)
  • ✅ Single database = autoincrement si privé OK
  • ✅ Distributed system = UUID v6 ou Nanoid
  • ✅ APIs = Nanoid (compact et URL-safe)
  • ✅ Mobile apps = Nanoid (taille réduite)

Recommandations par cas :

Cas d'usage Recommandation Raison
API REST publique Nanoid Compact, sûr, URL-safe
App interne Autoincrement Performance, stockage minimal
Microservices UUID v6 Distribué, sortable
MongoDB Nanoid dans _id Compact, compatible

Configuration recommandée :

// Node.js API moderne
import { nanoid } from 'nanoid';

app.post('/api/resource', (req, res) => {
    const resource = {
        id: nanoid(),           // ID unique
        ...req.body,
        createdAt: new Date(),  // Timestamp
        updatedAt: new Date(),
        version: 1              // Versioning
    };

    db.create(resource);
    res.status(201).json(resource);
});
Conclusion : Choisir le bon type d'ID est crucial pour la sécurité, performance et scalabilité. Préférez Nanoid pour les APIs modernes (compact + sûr), UUID pour les systèmes distribués, et gardez autoincrement pour les applications internes. N'oubliez jamais : l'ID n'est PAS l'authentification !