Last Updated: 3/9/2026
Migration Guide
Guide for migrating to Nano ID from other ID generators or upgrading between versions.
Migrating from UUID
Why Migrate?
Advantages of Nano ID over UUID:
- ✅ Shorter: 21 chars vs 36 chars
- ✅ URL-safe: No encoding needed
- ✅ Smaller package: 118 bytes vs 423 bytes
- ✅ Similar collision resistance
Disadvantages:
- ❌ Not RFC 4122 compliant
- ❌ Not sortable by time (unless using ULID variant)
- ❌ Different format (may affect existing systems)
Side-by-Side Comparison
import { v4 as uuidv4 } from 'uuid'
import { nanoid } from 'nanoid'
// UUID v4
const uuid = uuidv4()
// "f47ac10b-58cc-4372-a567-0e02b2c3d479" (36 chars)
// Nano ID
const id = nanoid()
// "V1StGXR8_Z5jdHi6B-myT" (21 chars)Migration Strategy
Option 1: Dual Column (Gradual)
-- Add new column
ALTER TABLE users ADD COLUMN nanoid VARCHAR(21);
-- Generate Nano IDs for existing rows
UPDATE users SET nanoid = generate_nanoid() WHERE nanoid IS NULL;
-- Make unique
ALTER TABLE users ADD UNIQUE (nanoid);
-- Update application to use nanoid
-- Gradually migrate foreign keys
-- Eventually drop uuid columnOption 2: New Table (Clean Break)
-- Create new table
CREATE TABLE users_new (
id VARCHAR(21) PRIMARY KEY,
uuid VARCHAR(36) UNIQUE, -- Keep for reference
email VARCHAR(255),
created_at TIMESTAMP
);
-- Copy data
INSERT INTO users_new (id, uuid, email, created_at)
SELECT generate_nanoid(), uuid, email, created_at FROM users;
-- Update foreign keys in other tables
-- Rename tables
-- Drop old tableOption 3: New Records Only
import { nanoid } from 'nanoid'
import { v4 as uuidv4 } from 'uuid'
// Keep UUID for existing records
// Use Nano ID for new records
const createUser = (email) => {
return {
id: nanoid(), // New format for new users
email,
createdAt: new Date()
}
}
// Handle both formats in queries
const getUser = (id) => {
// Check format
const isUuid = id.includes('-')
const column = isUuid ? 'uuid' : 'id'
return db.query(`SELECT * FROM users WHERE ${column} = ?`, [id])
}Code Changes
// Before (UUID)
import { v4 as uuidv4 } from 'uuid'
const user = {
id: uuidv4(),
email: 'user@example.com'
}
// After (Nano ID)
import { nanoid } from 'nanoid'
const user = {
id: nanoid(),
email: 'user@example.com'
}Migrating from shortid
Why Migrate?
shortid is deprecated and has security issues:
- ❌ Predictable IDs
- ❌ No longer maintained
- ❌ Security vulnerabilities
Nano ID advantages:
- ✅ Cryptographically secure
- ✅ Actively maintained
- ✅ Better performance
- ✅ Smaller package
Code Changes
// Before (shortid)
const shortid = require('shortid')
const id = shortid.generate()
// "rJv2ZvZ8W" (9 chars, variable length)
// After (Nano ID)
import { nanoid } from 'nanoid'
const id = nanoid(10) // Match length if needed
// "V1StGXR8_Z" (10 chars, fixed length)Migration Checklist
-
Install Nano ID:
npm install nanoid npm uninstall shortid -
Replace imports:
// Find: const shortid = require('shortid') // Replace: import { nanoid } from 'nanoid' -
Replace generate calls:
// Find: shortid.generate() // Replace: nanoid() -
Update database schema (if needed):
-- shortid: VARCHAR(14) (max length) -- Nano ID: VARCHAR(21) (default) ALTER TABLE users MODIFY COLUMN id VARCHAR(21); -
Test thoroughly: IDs will be different format
Upgrading Nano ID Versions
From v3 to v5
Major changes:
- ❌ CommonJS support removed (ESM only)
- ✅ Smaller package size
- ✅ Better TypeScript types
Breaking Changes
CommonJS require() no longer works:
// v3 (works)
const { nanoid } = require('nanoid')
// v5 (error)
const { nanoid } = require('nanoid')
// Error: require() of ES Module not supportedSolutions:
-
Use Node.js 22.12+:
const { nanoid } = require('nanoid') // Now works -
Use dynamic import:
const { nanoid } = await import('nanoid') -
Convert to ESM:
// package.json { "type": "module" }import { nanoid } from 'nanoid' -
Stay on v3:
npm install nanoid@3
API Changes
No API changes - same functions, same signatures:
// v3
import { nanoid, customAlphabet } from 'nanoid'
const id = nanoid()
// v5 (identical)
import { nanoid, customAlphabet } from 'nanoid'
const id = nanoid()From v2 to v3
Major changes:
- ✅ ESM support added
- ✅ Better browser support
- ✅ TypeScript improvements
No breaking changes for ESM users.
Migrating from Auto-Increment
Why Migrate?
Auto-increment limitations:
- ❌ Requires database coordination
- ❌ Not suitable for distributed systems
- ❌ Reveals business metrics (e.g., user count)
- ❌ Sequential = predictable
Nano ID advantages:
- ✅ Generate client-side
- ✅ No coordination needed
- ✅ Works in distributed systems
- ✅ Doesn’t reveal metrics
Migration Strategy
Option 1: Keep Both
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
nanoid VARCHAR(21) UNIQUE NOT NULL,
email VARCHAR(255)
);import { nanoid } from 'nanoid'
const user = {
nanoid: nanoid(), // External ID (in URLs)
email: 'user@example.com'
}
// Use nanoid in URLs
app.get('/users/:nanoid', async (req, res) => {
const user = await db.users.findOne({ nanoid: req.params.nanoid })
res.json(user)
})Option 2: Replace Completely
-- Before
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255)
);
-- After
CREATE TABLE users (
id VARCHAR(21) PRIMARY KEY,
email VARCHAR(255)
);// Before
const user = {
email: 'user@example.com'
// id generated by database
}
await db.users.insert(user)
// After
import { nanoid } from 'nanoid'
const user = {
id: nanoid(),
email: 'user@example.com'
}
await db.users.insert(user)Testing Migration
Validation Script
import { nanoid } from 'nanoid'
// Test ID generation
const ids = new Set()
for (let i = 0; i < 10000; i++) {
const id = nanoid()
// Check length
if (id.length !== 21) {
throw new Error(`Wrong length: ${id.length}`)
}
// Check uniqueness
if (ids.has(id)) {
throw new Error(`Duplicate ID: ${id}`)
}
ids.add(id)
}
console.log('✅ Generated 10,000 unique IDs')Performance Test
import { nanoid } from 'nanoid'
const iterations = 100000
const start = Date.now()
for (let i = 0; i < iterations; i++) {
nanoid()
}
const elapsed = Date.now() - start
const opsPerSec = (iterations / elapsed) * 1000
console.log(`Performance: ${opsPerSec.toFixed(0)} ops/sec`)
// Should be >1M ops/secRollback Plan
If migration fails:
-
Keep old ID column:
-- Don't drop old column immediately ALTER TABLE users ADD COLUMN nanoid VARCHAR(21); -- Test first, drop later -
Dual write:
// Write to both old and new ID columns const user = { id: autoIncrementId, nanoid: nanoid(), email: 'user@example.com' } -
Feature flag:
const USE_NANOID = process.env.USE_NANOID === 'true' const createUser = (email) => { const id = USE_NANOID ? nanoid() : null // Let DB generate return { id, email } }