•CASE STUDY

REST APIs for JSON Document Storage

4 min read·652 words·Beginner

Asked at

1 candidate report in Jan 2026

How to use this case study

SDE-2 / Mid

  • Design resources and endpoints for creating
  • Reading
  • Updating (full and partial) and deleting JSON documents in folders
  • With proper status codes

SDE-3 / Senior

  • Add optimistic concurrency (ETags / If-Match)
  • Pagination and filtering
  • Validation against schemas
  • Handling large documents

Staff / Principal

  • Discuss versioning of documents
  • Storage choices (document DB vs object storage)
  • Permissions
  • API evolution

Problem RestatementProblem

Microsoft asked: design REST APIs (and the backend) for a service that stores JSON documents (small files), organized in folders. Clients create, read, update, partially update, delete and list documents. Key topics: resource design, metadata, concurrency control when two clients edit the same document, and good error handling.

Resources and Endpoints

POST   /v1/folders                          { name, parent_id? }           → 201 folder
GET    /v1/folders/{fid}/documents?cursor=&limit=50&sort=updated_at        → 200 { items, next_cursor }
POST   /v1/folders/{fid}/documents          { name, content: {...} }       → 201, Location, ETag
GET    /v1/documents/{id}                                                   → 200 body = JSON, ETag: "v7"
PUT    /v1/documents/{id}                   If-Match: "v7"  { content }    → 200, ETag: "v8"  | 412
PATCH  /v1/documents/{id}                   If-Match: "v7"  (JSON Merge Patch or JSON Patch)  → 200 | 412
DELETE /v1/documents/{id}                   If-Match: "v8"                  → 204 | 412
GET    /v1/documents/{id}/metadata                                          → { name, size, owner, created_at, updated_at, version }
GET    /v1/documents/{id}/versions/{n}                                      → an older version (optional)

Status codes: 201 created, 200 ok, 204 no content, 400 invalid JSON or schema error, 401/403 auth, 404 not found, 409 name conflict in the folder, 412 precondition failed (stale ETag), 413 too large, 429 rate limited.

Deep Dive — Two people saving the same documentDeep dive

Two users open version 7 and both press save. Without protection the second save silently erases the first — the lost update, and the reason this question is asked.

Weak

PUT overwrites whatever is there

The client sends the full document and the server stores it.

Sequence diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
sequenceDiagram
  participant A as User A
  participant S as Server
  participant B as User B
  A->>S: GET doc - version 7
  B->>S: GET doc - version 7
  A->>S: PUT - adds a section
  B->>S: PUT - fixes a typo, based on v7
  Note over S: A's section is gone, and nobody is told

Both writes succeed and one person's work disappears without an error. The user who lost it usually discovers it much later, with no way to tell what happened.

Good

Compare a last-modified timestamp

Store last_modified and reject a write whose timestamp is older than the stored one.

It catches the obvious case and it is fragile for two reasons. Timestamp resolution: two saves inside the same millisecond compare equal and one is lost anyway. And clock source: if the client supplies the time, clock skew decides who wins, which is not a decision anyone chose.

Best

A version, exposed as an ETag, enforced by a conditional write

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
  G["GET /docs/42"] --> E["200 + ETag: \"7\""]
  E --> EDIT["Client edits"]
  EDIT --> P["PUT with If-Match: \"7\""]
  P --> COND["UPDATE ... SET version = 8 WHERE id = 42 AND version = 7"]
  COND -->|"1 row"| OK["200 - new ETag \"8\""]
  COND -->|"0 rows"| C412["412 Precondition Failed"]
  C412 --> REF["Client re-fetches, merges or shows a conflict, retries"]
  • The version is an integer the server owns, so there is no resolution problem and no clock involved.
  • The check and the write are one statement. WHERE version = 7 means the comparison happens inside the update, so two concurrent saves cannot both pass it.
  • 412 is the useful answer. It tells the client precisely what happened — someone else changed this — so it can re-fetch and merge rather than guessing from a generic error.
  • PATCH uses the same ETag check with a smaller payload, which matters when documents are large and the edit is one field.

Make If-Match required for updates rather than optional. An optional precondition is one the first client to skip it will skip, and then the guarantee only holds for the well-behaved callers — which is not a guarantee.

Backend

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
    C["Clients"] --> API["Documents API - auth, validation"]
    API --> META[("Metadata DB - folders, docs, versions, ACLs")]
    API --> STORE[("Content store - document DB or object storage")]
    API --> SCH[("JSON schemas (optional)")]
  • Metadata (name, folder, owner, size, version, timestamps, permissions) in a relational DB. There's a unique constraint on (folder_id, name), which gives the 409 on duplicate names.
  • Content: small documents (< 1 MB) in a document database (e.g., stored as JSONB), and large ones in object storage, referenced by key. Old versions are kept for history, with retention limits.
  • Validation: check that the body is valid JSON, and optionally validate against a JSON Schema registered for the folder.
  • Listing: cursor pagination (sorted by updated_at + id), with filters by name prefix or date.

Extras

  • Permissions: folder-level ACLs inherited by documents (owner, editor, viewer). Check them on every call.
  • Large documents: max body size (413). For big ones, support upload via a pre-signed URL.
  • Caching: GET with If-None-Match: "v8" → 304 Not Modified if unchanged, which saves bandwidth.
  • Soft delete + trash, so accidental deletes can be restored.

Wrap-UpWrap-up

Model folders and documents as REST resources with clear verbs and status codes, and return the document's version as an ETag. Require If-Match on PUT, PATCH and DELETE, with conditional writes that return 412 on conflicts (no lost updates). Keep metadata (with a unique folder/name constraint) in a relational DB and content in a document store or object storage with version history, validate JSON (optionally against schemas), paginate with cursors, and support 304 caching.

More Case Studies

Frequently Asked Questions

What is the REST APIs for JSON Document Storage system design question?

REST APIs for JSON Document Storage is a system design interview question asked at FAANG companies. It covers api design, storage, databases and tests your ability to design scalable, production-ready systems. InterviewSkool's breakdown walks you through requirements, API design, architecture, and trade-offs.

Which companies ask the REST APIs for JSON Document Storage question?

Microsoft have reportedly asked variations of this question in system design interviews. The exact wording may differ, but the core design challenges remain the same.

How should I prepare for the REST APIs for JSON Document Storage interview question?

Start with the problem statement and scale estimates, then design the high-level architecture. Focus on the core components, data model, and API design. InterviewSkool's breakdown covers the full solution with mermaid diagrams and trade-off analysis to help you prep efficiently.

What level is the REST APIs for JSON Document Storage question?

This question is suitable for SDE-2, SDE-3, and Staff engineer interviews. The level guidance on this page provides specific tips for each level — SDE-2 candidates should focus on core architecture, while Staff engineers should discuss trade-offs, monitoring, and incremental rollouts.

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with InterviewSkool's AI interviewer.

Start System Design Interview →