Performance Considerations for JSON Server with Large JSON Databases

JSON Server loads the entire JSON file into memory via lowdb, resulting in linear O(N) query scans and full-file rewrites on every mutation, which causes significant memory pressure and write latency as databases grow beyond tens of thousands of records.

When prototyping REST APIs with typicode/json-server, developers often start with small JSON files but eventually face slowdowns as data scales. Understanding the performance considerations for JSON Server with large JSON databases is critical before deploying to production or stress-testing your mock API.

How JSON Server Handles Large Databases In-Memory

Memory Architecture and the Lowdb Adapter

In src/bin.ts (lines 28-38), JSON Server initializes a Low instance from lowdb with a NormalizedAdapter wrapping the file observer. This design loads the entire parsed JSON object into the Node.js heap immediately upon startup. The db.data object (referenced in src/service.ts line 81) holds all collections in memory for the process lifetime.

For a 200 MB JSON file containing approximately 2 million records, this consumes roughly 400 MB of heap space. When the file size approaches or exceeds the default Node.js memory limit (1.5 GB), the process risks HEAP OUT OF MEMORY crashes during garbage collection cycles.

Write Amplification on Every Mutation

Every POST, PUT, PATCH, or DELETE operation triggers a complete file rewrite. In src/adapters/normalized-adapter.ts (lines 44-46), the adapter's write method serializes the entire in-memory database back to disk, including the $schema metadata.

This write amplification means that modifying a single 1 KB record in a 100 MB database requires rewriting the entire 100 MB file. As database size grows, write latency increases linearly, potentially blocking the event loop for seconds during large file serializations.

Query Performance Bottlenecks in Large Collections

Linear Scanning with matchesWhere()

The Service.find() method in src/service.ts (lines 20-42) implements filtering by calling matchesWhere() from src/matches-where.ts (lines 24-88). This function performs a linear O(N) scan over every item in the collection, checking each record against the query predicates.

Complex _where clauses involving operators like _gt, _lt, or _contains execute multiple comparisons per record. Without database indexes, querying a collection of 100,000 items requires evaluating 100,000 predicate checks, consuming significant CPU cycles per request.

The Cost of Embedding and Sorting

The _embed parameter triggers nested iteration across related collections. For each result item, the embed logic walks every related collection to find matches, creating O(N × M) complexity where N is the result set size and M is the related collection size.

Sorting compounds the overhead. The sortOn() utility (invoked in src/service.ts) sorts the entire filtered result set in memory using JavaScript's native sort algorithm. Sorting millions of objects creates substantial memory pressure and CPU load, particularly when sorting on non-indexed string fields.

Pagination Overhead

While pagination in src/paginate.ts (lines 11-38) limits the response payload using Array.prototype.slice(), the preceding filter and sort operations must process the entire collection first. Pagination reduces network overhead but does not mitigate the linear scan costs incurred before the slice operation executes.

Benchmarking JSON Server with Large Files

The following examples demonstrate real-world performance characteristics when JSON Server handles large datasets.

Loading a 200 MB JSON file containing approximately 2 million records:

import { readFileSync } from 'fs';
import { Low } from 'lowdb';
import { NormalizedAdapter } from './src/adapters/normalized-adapter.js';
import { JSONFile } from 'lowdb/node';

const adapter = new JSONFile('big-db.json');
const db = new Low(new NormalizedAdapter(adapter));

console.time('load');
await db.read();
console.timeEnd('load');
// Output: load: 2345.123ms
// Heap usage: ~400 MB

Querying with pagination against the full dataset:


# Request 10 items from a collection of 2M records

curl "http://localhost:3000/posts?_page=1&_per_page=10"
// Server-side processing in src/service.ts
// matchesWhere() iterates 2M items → 300ms-1s latency

Write operation latency:


# POST to a 200MB database

curl -X POST -H "Content-Type: application/json" \
  -d '{"title":"new post"}' http://localhost:3000/posts

Observed behavior: The request blocks for approximately 1-2 seconds while NormalizedAdapter.write serializes the entire 200 MB file back to disk.

Mitigation Strategies for Large Databases

When working with large datasets in JSON Server, apply these optimization techniques to minimize performance degradation.

Limit Result Size with Pagination

Always use _page and _per_page parameters to reduce response payload. While this does not eliminate the linear scan cost, it minimizes network overhead and client-side processing.

Avoid Heavy Embedding

Replace _embed parameters with separate requests. Fetch primary resources first, then retrieve related data by ID to avoid O(N × M) nested iteration costs.

Simplify Where Clauses

Use simple equality checks (field=value) rather than complex operators (_gt, _lt, _contains). The matchesWhere() function in src/matches-where.ts performs fewer comparisons per record with simple predicates.

Split the Database

Store independent collections in separate JSON files and run multiple JSON Server instances. This reduces per-process memory footprint and write amplification costs.

Increase Node Heap Size

As a temporary measure, increase the memory limit:

node --max-old-space-size=4096 ./node_modules/.bin/json-server --watch db.json

This delays out-of-memory errors but does not address linear scan inefficiency.

Switch to a Real Database

For production workloads exceeding tens of thousands of records, migrate to SQLite, PostgreSQL, or MongoDB. Projects like json-server-typeorm provide JSON Server-compatible APIs with database-backed indexing and query optimization.

Summary

  • JSON Server loads the entire JSON file into memory via lowdb, creating a hard dependency on available Node.js heap space.
  • All queries execute linear O(N) scans through matchesWhere() in src/service.ts, making large collections CPU-intensive to filter.
  • Every mutation triggers a full file rewrite through NormalizedAdapter.write(), causing write latency to scale linearly with file size.
  • Embedding and sorting compound overhead by introducing O(N × M) complexity and full-array memory operations.
  • Mitigation requires architectural changes: pagination limits payload but not scan cost, while true scalability requires database migration or sharding across multiple instances.

Frequently Asked Questions

What is the maximum file size JSON Server can handle?

JSON Server does not enforce a hard file size limit, but practical constraints emerge around 50-100 MB depending on available system memory. The entire file must fit within the Node.js heap (default 1.5 GB), and large files risk HEAP OUT OF MEMORY errors during garbage collection or when loading complex nested objects.

Why does JSON Server slow down as my database grows?

Performance degradation stems from three architectural characteristics: the entire database resides in memory requiring linear scans for every query via matchesWhere() in src/matches-where.ts, every write operation rewrites the complete file through NormalizedAdapter.write(), and complex operations like _embed or sorting process full arrays in memory. These O(N) and O(N × M) operations become measurably slower as N grows.

Can I use JSON Server in production with large datasets?

JSON Server is designed for prototyping and mocking APIs rather than production workloads with large datasets. While you can mitigate issues using pagination, avoiding embeds, and splitting databases across instances, the fundamental limitations of in-memory storage and linear scanning make it unsuitable for high-traffic or large-volume production scenarios. For production, migrate to indexed databases like SQLite or PostgreSQL.

How can I improve query speed without switching databases?

To improve query performance within JSON Server's constraints: implement strict pagination using _page and _per_page to minimize final payload size, eliminate _embed parameters to avoid nested iteration, simplify _where clauses to use equality checks rather than operators like _gt or _contains, and shard your data across multiple JSON Server instances to reduce per-process memory pressure and scan scope. Note that these strategies reduce overhead but cannot eliminate the linear scan requirement inherent to the architecture.

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 →