# How JSON Server Generates and Handles Resource IDs: A Complete Technical Guide

> Discover how JSON Server generates stable, unique 4-character hexadecimal IDs for resources. Learn about ID assignment in POST requests and immutable updates.

- Repository: [typicode/json-server](https://github.com/typicode/json-server)
- Tags: deep-dive
- Published: 2026-03-01

---

**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)](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:

```typescript
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)](https://github.com/typicode/json-server/blob/main/src/service.ts)** (lines 45-50) automatically injects a fresh identifier:

```typescript
// 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)](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:

```typescript
// 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:

```typescript
// 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)](https://github.com/typicode/json-server/blob/main/src/service.ts)** (lines 72-84) explicitly preserves the original identifier:

```typescript
// 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:

```typescript
// 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:

```javascript
// 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`](https://github.com/typicode/json-server/blob/main/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:

```json
// db.json input
{
  "posts": [{ "title": "First" }, { "title": "Second" }]
}

```

After `NormalizedAdapter.read()` processes the file, the in-memory database contains:

```javascript
// 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:

```javascript
// 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`](https://github.com/typicode/json-server/blob/main/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)` in [`src/random-id.ts`](https://github.com/typicode/json-server/blob/main/src/random-id.ts), ensuring short, unique, URL-safe identifiers.
- **IDs are assigned automatically** during POST requests via `Service.create()` and during startup normalization via `NormalizedAdapter.read()`, which also coerces existing numeric IDs to strings.
- **Resource IDs are immutable**; the `#updateOrPatchById()` method in [`src/service.ts`](https://github.com/typicode/json-server/blob/main/src/service.ts) preserves the original identifier during PUT and PATCH operations by explicitly setting `id` after 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`](https://github.com/typicode/json-server/blob/main/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`](https://github.com/typicode/json-server/blob/main/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`](https://github.com/typicode/json-server/blob/main/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.