How JSON Server Generates and Handles Resource IDs: A Complete Technical Guide
JSON Server generates stable, unique 4-character hexadecimal string IDs for every resource using crypto.randomBytes(2), assigns them during POST requests or when normalizing raw JSON data, and preserves these identifiers immutably through all update operations.
JSON Server is a popular open-source mock REST API that automatically generates unique identifiers for resources. Understanding how JSON Server generates and handles IDs for resources is essential for building reliable front-end prototypes and managing data persistence. This guide examines the source code implementation to reveal exactly how identifiers are created, assigned, and maintained throughout the resource lifecycle.
How JSON Server Generates Unique Resource IDs
JSON Server relies on a centralized helper function to produce cryptographically random identifiers that avoid collisions across the mock database.
The randomId() Generator in src/random-id.ts
The core ID generation logic resides in [src/random-id.ts](https://github.com/typicode/json-server/blob/main/src/random-id.ts). The randomId() function uses Node.js crypto utilities to generate unpredictable hexadecimal strings:
export function randomId(): string {
return randomBytes(2).toString('hex') // 4‑character hex string
}
This implementation calls crypto.randomBytes(2), which retrieves two random bytes from the system entropy pool. Converting these bytes to a hexadecimal string produces a 4-character lowercase string (for example, "a3f9" or "e4a1"). This approach ensures that IDs are short, URL-safe, and sufficiently unique for mock API scenarios.
When and How IDs Are Assigned to Resources
JSON Server assigns identifiers at three distinct points in the data lifecycle, ensuring every resource possesses a stable string ID before it becomes accessible via the REST interface.
1. Creating Resources via POST Requests
When you submit a POST request to a resource endpoint, the Service.create() method in [src/service.ts](https://github.com/typicode/json-server/blob/main/src/service.ts) (lines 45-50) automatically injects a fresh identifier:
// Simplified excerpt from Service.create()
const newItem = {
...body,
id: randomId(), // New 4-char hex ID generated here
}
this.data.push(newItem)
The server constructs the new object by spreading the request body and overriding any client-provided ID with a server-generated one, preventing ID collisions or manipulation.
2. Normalizing Raw JSON Files Without IDs
When JSON Server loads a database file that lacks identifiers, the NormalizedAdapter.read() method in [src/adapters/normalized-adapter.ts](https://github.com/typicode/json-server/blob/main/src/adapters/normalized-adapter.ts) (lines 30-36) iterates through arrays and assigns IDs to orphaned items:
// From normalized-adapter.ts
if (!('id' in item)) {
item['id'] = randomId() // Inject missing ID
}
This normalization process ensures backward compatibility with plain JSON datasets while guaranteeing that every item conforms to the string-ID contract before the server begins accepting requests.
3. Coercing Numeric IDs to Strings
If your source data contains numeric IDs (for example, "id": 1), the same normalization adapter coerces them to strings to maintain type consistency across the API:
// Lines 30-33 in normalized-adapter.ts
if (typeof item['id'] === 'number') {
item['id'] = item['id'].toString()
}
This coercion prevents type-mismatch errors during ID comparisons and ensures that foreign key relationships remain stable regardless of how the original data was formatted.
ID Stability During Updates and Patches
JSON Server treats resource identifiers as immutable properties that persist throughout the entire lifecycle of an object, protecting against accidental ID changes during modification operations.
Preserving IDs in PUT and PATCH Operations
When you update a resource via PUT or PATCH, the #updateOrPatchById() private method in [src/service.ts](https://github.com/typicode/json-server/blob/main/src/service.ts) (lines 72-84) explicitly preserves the original identifier:
// Simplified logic from lines 72-84
const index = this.data.findIndex(item => item['id'] === id)
const item = this.data[index]
// For PUT: replace entirely but keep original id
const newItem = { ...body, id: item['id'] }
// For PATCH: merge changes but keep original id
const patchedItem = { ...item, ...body, id: item['id'] }
this.data[index] = newItem // or patchedItem
By spreading the request body and then explicitly setting the id property to the existing value, the server ensures that client payloads cannot override the stable identifier, even if the request body contains a conflicting id field.
ID Lookup and Referential Integrity
All database operations rely on strict string equality checks against the id field, with additional mechanisms to maintain data consistency when resources are removed.
String-Based ID Comparisons
Every lookup operation—whether findById, destroyById, or internal foreign-key resolution—compares the stored id string directly:
// Representative pattern from src/service.ts
const item = this.data.find(item => item['id'] === id)
Because the normalization process guarantees that all IDs are strings, these comparisons are type-safe and avoid the subtle bugs that can occur when comparing numbers and strings in JavaScript.
Foreign Key Nullification on Deletion
When a resource is deleted, the nullifyForeignKey() utility ensures referential integrity by scanning other collections for properties that reference the removed ID and setting them to null. This process respects the stable ID system by ensuring that remaining resources retain their original identifiers while relationships are safely severed.
Practical Examples of JSON Server ID Handling
The following examples demonstrate how JSON Server manages identifiers in real-world scenarios.
Creating a New Resource with Auto-Generated ID
When you POST to a collection endpoint, the server generates a fresh 4-character hexadecimal ID:
// Client request
await fetch('http://localhost:3000/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Hello JSON Server' })
})
// Server response
// { "id": "e4a1", "title": "Hello JSON Server" }
The randomId() function in src/random-id.ts produced the "e4a1" identifier during the Service.create() execution.
Normalizing Legacy Data Without IDs
If you provide a raw JSON file lacking identifiers, JSON Server automatically injects them during startup:
// db.json input
{
"posts": [{ "title": "First" }, { "title": "Second" }]
}
After NormalizedAdapter.read() processes the file, the in-memory database contains:
// Result after normalization
[
{ "id": "b2c7", "title": "First" },
{ "id": "9f3d", "title": "Second" }
]
Updating While Preserving the Original ID
Even if your PUT request includes an id field, the server protects the original identifier:
// Attempting to change the ID during update
await fetch('http://localhost:3000/posts/9f3d', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: '9999', title: 'Edited' })
})
// Response shows original ID preserved:
// { "id": "9f3d", "title": "Edited" }
The #updateOrPatchById() method in src/service.ts explicitly reconstructs the object using the existing id value, ignoring any client-provided identifier.
Summary
- JSON Server generates 4-character hexadecimal string IDs using
crypto.randomBytes(2)insrc/random-id.ts, ensuring short, unique, URL-safe identifiers. - IDs are assigned automatically during POST requests via
Service.create()and during startup normalization viaNormalizedAdapter.read(), which also coerces existing numeric IDs to strings. - Resource IDs are immutable; the
#updateOrPatchById()method insrc/service.tspreserves the original identifier during PUT and PATCH operations by explicitly settingidafter spreading the request body. - String-based lookups ensure type-safe comparisons across all CRUD operations, while
nullifyForeignKey()maintains referential integrity by clearing foreign key references when resources are deleted.
Frequently Asked Questions
What format does JSON Server use for resource IDs?
JSON Server uses 4-character hexadecimal strings (for example, "a3f9" or "e4a1"). The randomId() function in src/random-id.ts generates these by converting two random bytes from crypto.randomBytes(2) into a hex string. This format ensures IDs are short, URL-safe, and compatible with all HTTP clients.
Does JSON Server preserve IDs when updating resources?
Yes, JSON Server treats resource IDs as immutable properties. When you send a PUT or PATCH request, the #updateOrPatchById() method in src/service.ts explicitly reconstructs the object using the original ID, ignoring any id field in your request body. This guarantees that the identifier assigned at creation remains stable for the lifetime of the resource.
How does JSON Server handle existing numeric IDs in source data?
JSON Server normalizes all IDs to strings during startup. The NormalizedAdapter.read() method in src/adapters/normalized-adapter.ts checks each item; if it finds a numeric ID, it converts it to a string using item['id'].toString(). This coercion ensures type consistency across the API and prevents comparison errors during database lookups.
What happens to foreign key references when a resource is deleted?
When you delete a resource, JSON Server maintains referential integrity by invoking nullifyForeignKey(). This utility scans other collections for properties that reference the deleted ID and sets those foreign key fields to null. The operation respects the stable ID system by ensuring remaining resources retain their original identifiers while safely severing relationships to the removed item.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →