# How to Write Tests for JSON Server Customizations: A Complete Guide

> Learn to write tests for JSON Server customizations using Node.js native test runner and LowDB. Test live endpoints with fetch and assert against HTTP instances.

- Repository: [typicode/json-server](https://github.com/typicode/json-server)
- Tags: how-to-guide
- Published: 2026-03-01

---

**Use the Node.js native test runner with LowDB's Memory adapter to spin up real HTTP instances of your customized JSON Server and assert against live endpoints using `fetch`.**

Testing customizations in `typicode/json-server` ensures your extended routes, middleware, and service-layer modifications remain stable across updates. Because JSON Server is built on **TinyHTTP** and exposes an Express-compatible `App` instance, you can import `createApp` directly into your test files, inject an in-memory LowDB database, and execute real HTTP requests against your custom logic.

This guide demonstrates the exact patterns used in the official repository to test static directories, custom endpoints, and service extensions.

## Setting Up the Test Environment with In-Memory LowDB

Isolating your tests from the filesystem prevents flaky assertions and side effects. JSON Server uses **LowDB** for data persistence, and the `Memory` adapter provides a clean slate for every test run.

### Installing Development Dependencies

You need utilities to acquire free ports and manage temporary files:

```bash
npm install --save-dev get-port tempy

```

- **`get-port`** → Resolves an available TCP port to avoid collisions.
- **`tempy`** → Creates disposable directories for testing static file serving.

### Creating an Isolated In-Memory Database

Import the `Memory` adapter and instantiate `Low` with your data type:

```typescript
import { Low, Memory } from 'lowdb';
import type { Data } from './service.ts';

const db = new Low<Data>(new Memory<Data>(), { posts: [] });

```

This `db` instance is passed directly into `createApp`, ensuring no files are written to disk during test execution.

## Testing JSON Server Custom Routes and Middleware

Custom routes must be registered **before** the generic `/:name` CRUD handlers. The test suite in [`src/app.test.ts`](https://github.com/typicode/json-server/blob/main/src/app.test.ts) demonstrates how to verify these extensions using the native Node.js test runner.

### Spinning Up the App with Custom Static Directories

When testing static file serving, inject a temporary directory into the `static` option:

```typescript
import { createApp } from './app.ts';
import { join } from 'node:path';
import { temporaryDirectory } from 'tempy';
import { writeFileSync } from 'node:fs';

// Create a temp folder with a test asset
const staticDir = temporaryDirectory();
writeFileSync(join(staticDir, 'hello.html'), '<h1>Hello</h1>');

// Initialize the server with custom static configuration
const app = createApp(db, { static: [staticDir] });

```

### Writing Integration Tests with node:test and fetch

Use `node:test` and `node:assert` to execute real HTTP requests against the running server:

```typescript
import test from 'node:test';
import assert from 'node:assert';
import getPort from 'get-port';

const port = await getPort();
const server = app.listen(port, () => {});

// Clean up after tests complete
test.after(() => server.close());

// Test custom static file serving
await test('serves custom static file', async () => {
  const response = await fetch(`http://localhost:${port}/hello.html`);
  assert.equal(response.status, 200);
  const body = await response.text();
  assert.equal(body, '<h1>Hello</h1>');
});

// Test built-in CRUD functionality still works
await test('GET /posts returns 200', async () => {
  const response = await fetch(`http://localhost:${port}/posts`);
  assert.equal(response.status, 200);
});

```

This pattern validates that your customizations do not interfere with the default JSON Server behavior.

## Testing Custom Service Layer Extensions

For business logic extensions—such as adding a `bulkDelete` method to the `Service` class—write unit tests that bypass HTTP entirely. The existing [`src/service.test.ts`](https://github.com/typicode/json-server/blob/main/src/service.test.ts) provides the template.

### Unit Testing Service Methods Without HTTP

Import the `Service` class and invoke methods directly against the in-memory database:

```typescript
import { Service } from './service.ts';
import { Low, Memory } from 'lowdb';
import type { Data } from './service.ts';

const db = new Low<Data>(new Memory<Data>(), { posts: [] });
const service = new Service(db);

await test('bulkDelete removes all posts', async () => {
  // Seed the database
  db.data!.posts = [{ id: '1', title: 'First' }, { id: '2', title: 'Second' }];
  await db.write();

  // Execute custom service method
  await service.bulkDelete('posts');

  // Assert state
  assert.equal(db.data!.posts.length, 0);
});

```

This approach is faster than HTTP integration tests and isolates logic errors from routing issues.

## Running the Full Test Suite

Execute all tests using the Node.js native runner:

```bash
npm test

```

This command runs both [`src/app.test.ts`](https://github.com/typicode/json-server/blob/main/src/app.test.ts) and [`src/service.test.ts`](https://github.com/typicode/json-server/blob/main/src/service.test.ts), validating HTTP endpoints and service-layer logic simultaneously.

## Summary

- **Isolate state** using LowDB's `Memory` adapter to prevent filesystem dependencies in your test suite.
- **Use `createApp`** to instantiate the TinyHTTP server with custom static directories or middleware configurations.
- **Execute real HTTP requests** via `fetch` inside `node:test` blocks to verify routing and middleware behavior.
- **Test service extensions** by instantiating the `Service` class directly against an in-memory database for fast, isolated unit tests.
- **Reference [`src/app.test.ts`](https://github.com/typicode/json-server/blob/main/src/app.test.ts) and [`src/service.test.ts`](https://github.com/typicode/json-server/blob/main/src/service.test.ts)** as canonical examples of integration and unit testing patterns in the JSON Server codebase.

## Frequently Asked Questions

### How do I test custom routes in JSON Server?

Register your custom routes inside the `createApp` function before the generic `/:name` CRUD handlers, then start the server on a random port using `get-port` and assert against the endpoint with `fetch`. The [`src/app.test.ts`](https://github.com/typicode/json-server/blob/main/src/app.test.ts) file demonstrates this pattern for static files and query parameters.

### What testing framework does JSON Server use?

JSON Server uses the **Node.js native test runner** (`node:test` and `node:assert`), requiring no external testing frameworks like Jest or Mocha. This choice keeps the dependency tree minimal and leverages built-in Node.js features available in version 18 and later.

### How do I mock the database for JSON Server tests?

Use LowDB's `Memory` adapter instead of the file-based JSON adapter. Create a new `Low<Data>(new Memory<Data>(), defaultData)` instance and pass it to `createApp`. This provides a fresh, isolated database for every test without writing to the filesystem.

### Can I test static file serving in JSON Server?

Yes. Use the `tempy` package to create a temporary directory, write test files to it, and pass the directory path in the `static` option when calling `createApp`. Then start the server and request the file via `fetch` to verify it returns the correct content and HTTP 200 status.