Using UUIDs in API Design
Why Use UUID in APIs
In REST API design, UUID as resource identifiers has clear advantages over auto-increment integer IDs: higher security โ does not expose database record count and order, preventing users from enumerating resources via /users/1, /users/2; supports client-side ID generation (idempotent creation) โ clients can generate UUIDs before sending POST requests, making creation operations idempotent (retries won't create duplicate records); distribution-friendly โ multiple services and nodes can generate IDs independently without conflicts; clearer URL semantics โ UUID in a URL clearly identifies a specific resource, not a potentially changing sequence number.
REST API Path Design Conventions
# ๆจ่็ UUID ่ทฏๅพๆ ผๅผ๏ผๅฐๅ๏ผๅธฆ่ฟๅญ็ฌฆ๏ผ
GET /api/v1/users/550e8400-e29b-41d4-a716-446655440000
PUT /api/v1/users/550e8400-e29b-41d4-a716-446655440000
DELETE /api/v1/users/550e8400-e29b-41d4-a716-446655440000
# ๅตๅฅ่ตๆบ
GET /api/v1/users/550e8400-e29b-41d4-a716-446655440000/orders
GET /api/v1/orders/9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
# ๅน็ญๅๅปบ๏ผๅฎขๆท็ซฏๆไพ UUID๏ผ
PUT /api/v1/resources/client-generated-uuid
# ๆๅก็ซฏๅฆๆๅทฒๅญๅจ่ฏฅ UUID๏ผๅฏไปฅ่ฟๅ 200 ๆ 409
# API ๅๅบๆ ผๅผ่ง่๏ผ็ปไธไฝฟ็จๅญ็ฌฆไธฒ๏ผ
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Example User",
"createdAt": "2025-01-01T00:00:00Z"
}
Format Normalization Recommendations
Recommendations for UUID format in APIs: accept input loosely (accept both upper/lowercase, with or without braces), output strictly (always return in lowercase without braces format); in path parameters, validate format and return 400 Bad Request for invalid format rather than 404 Not Found (tells callers it's a format issue, not "resource not found"); in JSON body, transmit UUID as string (don't attempt numeric representation, which loses precision); in API documentation, clearly specify using UUID v4 and provide format examples.
Error Handling Best Practices
# FastAPI ็คบไพ๏ผUUID ๅๆฐ้ช่ฏๅ้่ฏฏๅค็
from uuid import UUID
from fastapi import FastAPI, HTTPException, Path
from fastapi.responses import JSONResponse
app = FastAPI()
@app.exception_handler(ValueError)
async def uuid_exception_handler(request, exc):
return JSONResponse(
status_code=400,
content={
"error": "invalid_uuid",
"message": "The provided ID is not a valid UUID format",
"expected": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
)
@app.get("/api/users/{user_id}")
async def get_user(
user_id: UUID = Path(..., description="User UUID in RFC 4122 format")
):
user = db.find_user(user_id)
if not user:
raise HTTPException(
status_code=404,
detail={
"error": "not_found",
"message": f"User {user_id} not found"
}
)
return user
Idempotency and Retry Safety
UUID is very useful in API idempotency design. Standard pattern: the client generates a UUID as the Idempotency-Key request header before the first request; the server caches this UUID with the request result (typically in Redis for 24 hours); if the same Idempotency-Key is requested again, directly return the cached response without re-executing the operation; this is particularly important for operations that cannot be repeated, like payments and order creation. Payment platforms like Stripe and Adyen use this pattern, allowing clients to safely retry failed requests without side effects.
# ๅน็ญ API ่ฐ็จ็คบไพ
import uuid
import httpx
def create_order_idempotent(order_data, max_retries=3):
idempotency_key = str(uuid.uuid4()) # ๆฌๆฌก่ฐ็จๅ
จๅฑๅฏไธ
for attempt in range(max_retries):
try:
response = httpx.post(
'/api/orders',
json=order_data,
headers={'Idempotency-Key': idempotency_key}
)
if response.status_code in (200, 201):
return response.json()
elif response.status_code == 409:
return response.json() # ๅน็ญ๏ผ่ฟๅๅทฒๅญๅจ็็ปๆ
except httpx.NetworkError:
if attempt == max_retries - 1:
raise
# ไฝฟ็จ็ธๅ็ idempotency_key ้่ฏ
UUID Usage in GraphQL
# GraphQL schema ็คบไพ
scalar UUID
type User {
id: UUID! # ้็ฉบ UUID ็ฑปๅ
name: String!
email: String!
posts: [Post!]!
}
type Query {
user(id: UUID!): User
users: [User!]!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: UUID!, input: UpdateUserInput!): User!
deleteUser(id: UUID!): Boolean!
}
# ๆฅ่ฏข็คบไพ
query {
user(id: "550e8400-e29b-41d4-a716-446655440000") {
id
name
email
}
}
Try the free tool now
Use Free Tool โ