How the JSON Server Routing System Works: From HTTP Request to CRUD Operation

JSON Server's routing system is built on the tinyhttp framework and maps RESTful HTTP endpoints to CRUD operations through a middleware pipeline defined in src/app.ts, delegating data operations to a central Service class while parsing complex queries via parseListParams and parseWhere.

The JSON Server routing system in the typicode/json-server repository provides a zero-configuration REST API layer over a JSON file. Built on tinyhttp rather than Express, it uses a concise middleware stack in src/app.ts to handle static files, CORS, body parsing, and route registration. Every HTTP method maps directly to specific service methods that perform filtering, embedding, and pagination against a lowdb-backed database.

Architecture and Middleware Pipeline

The Tinyhttp Foundation

Unlike earlier versions that used Express, the current implementation relies on tinyhttp, a lightweight Express-compatible framework. This architectural choice reduces dependencies while maintaining familiar middleware patterns and routing APIs.

Request Processing Stages

The createApp factory in src/app.ts assembles three critical middleware layers before reaching the route handlers:

  1. Static file serving: The sirv middleware serves content from the built-in public folder and additional directories specified in options.static (lines 98-101).
  2. CORS handling: A wrapper around @tinyhttp/cors dynamically sets Access-Control-Allow-Headers and registers a global OPTIONS * handler (lines 104-112).
  3. Body parsing: The milliparsec library's json() middleware parses JSON bodies for POST, PUT, and PATCH requests (lines 115-117).

RESTful Route Definitions

After the middleware pipeline, src/app.ts defines route handlers that map HTTP methods to Service class operations. All handlers store results in res.locals['data'] for final serialization by a catch-all middleware (lines 55-63).

Collection Routes

  • GET /:name: Returns filtered, sorted, and paginated collections. The parseListParams function (lines 30-66) extracts reserved query keys including _sort, _page, _per_page, _embed, and _where, converting them into a structured query object. The Service.find method in src/service.ts (lines 10-42) then applies these criteria using matchesWhere for filtering.
  • POST /:name: Creates resources. The withBody wrapper (lines 68-76) validates that the request body is an object before calling Service.create (lines 45-53), which generates a random ID and persists to the lowdb database.

Single Resource Routes

  • GET /:name/:id: Retrieves individual resources via Service.findById. The _embed query parameter triggers relational data inclusion through the embed helper (lines 96-104).
  • PUT /:name/:id: Full resource replacement using withIdAndBody middleware (lines 78-86), delegating to Service.updateById (lines 94-100).
  • PATCH /:name/:id: Partial updates handled by Service.patchById (lines 92-98).
  • DELETE /:name/:id: Resource removal via Service.destroyById, which performs referential integrity cleanup through nullifyForeignKey and deleteDependents (lines 49-78).

Query Processing and Filtering

The routing system's filtering capabilities rely on three specialized modules that transform URL queries into executable database operations.

Parameter Parsing

When handling GET requests to collection routes, parseListParams in src/app.ts (lines 30-66) segregates standard query parameters from reserved keywords. It constructs a where object by passing query strings to parseWhere from src/parse-where.ts (lines 6-28), which supports both modern field:op=value syntax and legacy _lt, _gt operators (lines 58-68).

Filter Evaluation

The matchesWhere function in src/matches-where.ts (lines 24-72) recursively evaluates objects against the parsed criteria. It handles logical or operators, comparison operators (lt, gt, eq), set membership (in), and string matching (contains, startsWith, endsWith).

Service Layer Integration

Within src/service.ts, the find method (lines 26-38) orchestrates query execution: it retrieves the full collection, applies relational embedding via embed, filters using matchesWhere, applies sorting, and finally paginates results using the paginate helper.

Response Handling

A catch-all middleware at the end of the pipeline inspects res.locals['data'] to determine the appropriate HTTP status code—returning 404 when no data exists, 201 for successful POST requests, or 200 for other operations—and serializes the payload as JSON.

Code Examples

Filtering, Sorting, and Pagination

// GET request to retrieve filtered and paginated posts
GET /posts?_where=title:contains=json&page=2&_per_page=5&_sort=createdAt

// Internal execution flow:
// 1. parseListParams extracts: where={title:{contains:"json"}}, page=2, perPage=5, sort="createdAt"
// 2. parseWhere builds the where object from the raw query string
// 3. Service.find filters posts with matchesWhere, sorts on createdAt, then paginates

Creating Resources

// POST request to create a new comment
POST /comments
{
  "postId": 3,
  "body": "Nice article!"
}

// Execution flow:
// withBody validates body → Service.create('comments', {postId: 3, body: "Nice article!"})
// Service generates random ID and writes to the lowdb JSON file

Summary

  • JSON Server's routing system uses tinyhttp as its underlying framework, providing Express-compatible middleware support with lighter dependencies than traditional Express.
  • The createApp factory in src/app.ts configures static file serving, CORS, and JSON body parsing before registering RESTful routes that handle both collections and individual resources.
  • Route handlers delegate all CRUD operations to the Service class in src/service.ts, which implements business logic for filtering, embedding, pagination, and referential integrity.
  • Query parsing occurs in two stages: parseListParams extracts pagination and sorting parameters while parseWhere converts query strings into structured filter objects supporting operators like contains, gt, and in.
  • The matchesWhere engine in src/matches-where.ts evaluates complex filter criteria including logical operators and string comparisons against the JSON dataset.
  • All responses flow through a final middleware that sets appropriate HTTP status codes (200, 201, 404) and serializes data from res.locals['data'] as JSON.

Frequently Asked Questions

What framework does JSON Server use for routing?

JSON Server uses tinyhttp, a lightweight Express-compatible framework, rather than Express itself. This implementation appears in src/app.ts where the createApp function assembles the middleware pipeline using tinyhttp-specific imports for CORS (@tinyhttp/cors) and body parsing (milliparsec).

How does JSON Server handle complex query parameters like filtering and sorting?

The routing system processes query strings through parseListParams in src/app.ts (lines 30-66), which identifies reserved keys like _sort, _page, and _where. For filtering, it delegates to parseWhere to convert syntax such as title:contains=json into structured objects, then uses matchesWhere in src/matches-where.ts to evaluate records against these criteria during Service.find execution.

Where are the RESTful route definitions located in the source code?

All HTTP endpoint definitions reside in src/app.ts within the createApp function. This file registers handlers for GET, POST, PUT, PATCH, and DELETE methods on both collection paths (/:name) and single resource paths (/:name/:id), wrapping business logic calls with validation middleware like withBody and withIdAndBody.

How does JSON Server map HTTP methods to database operations?

The routing layer delegates to the Service class defined in src/service.ts. GET requests trigger find (collections) or findById (single resources), POST uses create, PUT utilizes updateById, PATCH calls patchById, and DELETE invokes destroyById. These methods handle lowdb interactions, random ID generation via randomId, and relational cleanup through nullifyForeignKey and deleteDependents.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →