CASE STUDY

REST APIs for JSON Document Storage

3 min read·450 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, and handling large documents.

Staff / Principal

Discuss versioning of documents, storage choices (document DB vs object storage), permissions, and API evolution.


0) Problem Restatement

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.


1) 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.


2) Concurrency Control (the important part)

Two users open version 7 and both save changes. Without protection, the second save silently overwrites the first (lost update).

  • Every document has a version. GET returns it as an ETag header.
  • Updates must send If-Match: "v7". The server does a conditional write: UPDATE ... SET content = ?, version = 8 WHERE id = ? AND version = 7.
  • If someone already saved v8, the condition fails → 412 Precondition Failed. The client re-fetches, merges or shows a conflict, then retries.
  • PATCH lets clients change only certain fields (smaller payloads), with the same ETag check.


3) Backend

Architecture Diagram

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.


4) 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.


5) Wrap-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

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with our AI interviewer.

Start System Design Interview →