How JSON Server's service.ts Implements Core Business Logic: A Deep Dive

The Service class in src/service.ts acts as the central orchestration layer that transforms lowdb's raw JSON data into a complete REST API, handling CRUD operations, complex querying, and relationship management.

JSON Server's architecture separates the HTTP layer from data manipulation, placing the core business logic inside a single Service class. This class wraps a lowdb instance (a lightweight JSON file-based database) and exposes methods that power every REST endpoint. Understanding service.ts reveals how the project achieves its "zero-configuration" promise while supporting advanced features like embedding, filtering, and referential integrity.

Service Class Architecture

The Service class defined in src/service.ts maintains a private #db property that holds the Low<Data> instance. This encapsulation ensures all disk I/O happens through a controlled interface. The class exposes three primary responsibility areas: data access validation, query pipeline processing, and state-mutating CRUD operations.

Key helper methods include #get (lines 88-90) which retrieves a resource array by name, and has (lines 92-94) which validates resource existence before operations proceed. These guards prevent runtime errors when clients request non-existent collections.

Query Processing Pipeline

The find method (lines 110-144) implements the core query engine that transforms raw arrays into paginated, filtered, and sorted results. This method executes a four-stage pipeline:

  1. Embedding – The private embed helper (lines 23-47) handles relationship expansion via the _embed parameter. It detects singular relationships (e.g., author mapping to authors) by checking for foreign key patterns like authorId, and plural relationships by matching child items containing parent IDs (e.g., postId).

  2. Filtering – The matchesWhere function imported from src/matches-where.ts (line 33) evaluates complex where clauses against each item, supporting operators defined in src/where-operators.ts.

  3. Sorting – The sortOn utility (lines 35-36) arranges results based on the sort parameter, handling both ascending and descending orders.

  4. Pagination – The paginate function (lines 38-40) slices the final array according to page and perPage parameters, returning the subset along with total count metadata.

CRUD Operations and Data Mutation

The Service class provides atomic CRUD methods that modify the in-memory state and persist changes via await this.#db.write().

Create

The create method (lines 45-54) generates a unique identifier using random-id.ts, merges it into the provided data, pushes the item to the appropriate collection, and immediately writes to disk.

Update and Patch

Two private helpers handle modifications: #updateOrPatch and #updateOrPatchById (lines 56-84). These methods distinguish between full replacement (update) and partial merging (patch). They locate the target item by ID, apply the new data (either replacing or merging), and persist the change.

Delete with Referential Integrity

The destroyById method (lines 102-121) removes an item and maintains database integrity through two mechanisms:

  • nullifyForeignKey (lines 49-64): After deletion, this helper iterates every collection, identifying foreign keys that reference the deleted ID (e.g., userId) and setting them to null to prevent dangling pointers.

  • deleteDependents (lines 66-78): When called with a list of dependent resource names, this function removes child items whose foreign keys were nullified, effectively cascading deletes.

Implementation Example

The following TypeScript example demonstrates initializing the Service class and executing common operations:

import { Low } from 'lowdb'
import { JSONFile } from 'lowdb/node'
import { Service } from './service.ts'

// Initialize lowdb with a JSON file
const adapter = new JSONFile<Data>('db.json')
const db = new Low<Data>(adapter)
await db.read()
db.data ||= {}

// Instantiate the service layer
const service = new Service(db)

// Create a new resource
const newPost = await service.create('posts', { 
  title: 'Understanding service.ts', 
  authorId: '1' 
})

// Query with embedding, filtering, and pagination
const results = await service.find('posts', {
  where: { title: { contains: 'service' } },
  sort: 'title',
  page: 1,
  perPage: 10,
  embed: 'author'  // Expands authorId into full author object
})

// Partial update
await service.patchById('posts', newPost.id, { title: 'Updated Title' })

// Delete with cascade to remove orphaned comments
await service.destroyById('posts', newPost.id, ['comments'])

Summary

  • The Service class in src/service.ts encapsulates JSON Server's business logic, bridging lowdb's file-based storage with REST API semantics.
  • Query processing occurs through a pipeline in find (lines 110-144), handling embedding, filtering via matchesWhere, sorting, and pagination.
  • CRUD operations include create (lines 45-54), #updateOrPatch (lines 56-84), and destroyById (lines 102-121), all persisting via this.#db.write().
  • Referential integrity is maintained through nullifyForeignKey (lines 49-64) and optional cascading deletes via deleteDependents (lines 66-78).
  • Helper utilities ensureArray (lines 19-21) and isItem (lines 13-15) normalize inputs and validate data shapes.

Frequently Asked Questions

What is the role of the Service class in JSON Server?

The Service class acts as the core business logic layer that transforms lowdb's raw JSON data into a functional REST API. It handles all data access, query processing (filtering, sorting, pagination), CRUD operations, and relationship management, effectively decoupling the HTTP transport layer from the data manipulation logic.

How does JSON Server handle relationship embedding in service.ts?

Relationship embedding is handled by the private embed helper method (lines 23-47 in src/service.ts). When the find method receives an embed parameter, it detects whether the relationship is singular (e.g., author linked via authorId) or plural (e.g., comments linked via postId), then fetches and attaches the related objects to the response.

What happens when a resource is deleted in JSON Server?

When destroyById is called (lines 102-121), the service removes the item and maintains referential integrity through two mechanisms. First, nullifyForeignKey (lines 49-64) scans all collections and sets any foreign key referencing the deleted ID to null. Second, if dependent resource names are provided, deleteDependents (lines 66-78) removes child items whose foreign keys were nullified, effectively cascading the deletion.

How does the query pipeline in service.ts process filters and pagination?

The find method (lines 110-144) executes a sequential pipeline. It first applies relationship embedding, then passes the dataset to matchesWhere (imported from src/matches-where.ts) to evaluate where clauses. Next, sortOn arranges results by the specified field, and finally paginate slices the array according to page and perPage parameters before returning the final dataset.

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 →