Skip to content
  • English Deutsch

benjamin-abt.com

Performance is a feature | Cloud - AI - Azure - .NET - Sustainability

  • Home
  • Blog
  • Portfolio
    • Overview
    • Workshops & Training
    • Publications
  • Contact
  • Home
  • Blog

Zero-Code REST API Mocking with json-server for Angular and React

Zero-Code REST API Mocking with json-server for Angular and React
May 21, 2026 10 min read BEN ABT
Angular React Frontend Tooling Mock REST

Frontend development often hits a practical wall early: the backend API does not exist yet, is unreachable in local environments or is shared between multiple teams and cannot be safely modified during development. The typical workarounds - hardcoded response stubs, in-memory interceptors or hand-rolled Express servers - all impose overhead that does not belong in the development workflow.

json-server offers a different approach. Given a plain JSON file that describes data, it automatically generates a fully functional REST API with filtering, sorting, pagination and relational embedding, served over HTTP, with zero application code required. For Angular and React projects in early development stages, that combination removes a significant amount of friction.

What json-server provides

json-server reads a db.json file and exposes each top-level key as a REST resource. A posts array becomes a full CRUD endpoint at /posts. A profile object becomes a singular resource at /profile. The server handles GET, POST, PUT, PATCH and DELETE requests without any additional configuration.

Version 1 (currently in beta as 1.0.0-beta.x) introduces a few behavioral changes compared to the older v0 series. IDs are now always strings and are auto-generated if missing. The pagination parameter changed from _limit to _per_page. Relationship expansion uses _embed instead of _expand. These differences are relevant when migrating from v0, but for new setups v1 is the correct starting point.

Installation

json-server is available as an npm package. Installing it as a development dependency keeps it out of production bundles:

Bash
npm install --save-dev json-server

It can also be run without a local installation using npx:

Bash
npx json-server db.json

For persistent project use, a local install with a script entry in package.json is the more reliable approach.

Designing the data file

The db.json file acts as both the schema definition and the persisted state of the mock. json-server supports both db.json and db.json5 formats. The JSON Schema reference can be included for editor validation:

JSON
 1{
 2  "$schema": "./node_modules/json-server/schema.json",
 3  "users": [
 4    { "id": "1", "name": "Alice Müller", "role": "admin", "active": true },
 5    { "id": "2", "name": "Bob Schneider", "role": "editor", "active": true },
 6    { "id": "3", "name": "Clara Fischer", "role": "viewer", "active": false }
 7  ],
 8  "posts": [
 9    { "id": "1", "title": "Getting Started with Angular", "authorId": "1", "views": 320 },
10    { "id": "2", "title": "React Patterns in 2026", "authorId": "2", "views": 180 },
11    { "id": "3", "title": "TypeScript Deep Dive", "authorId": "1", "views": 450 }
12  ],
13  "comments": [
14    { "id": "1", "text": "Great introduction!", "postId": "1" },
15    { "id": "2", "text": "Very helpful, thanks.", "postId": "1" },
16    { "id": "3", "text": "Looking forward to part 2.", "postId": "2" }
17  ],
18  "settings": {
19    "theme": "dark",
20    "language": "en"
21  }
22}

There is one important design constraint: json-server mutates the db.json file when write requests are handled. This means test writes persist between server restarts unless the file is reset. Committing a clean baseline version of db.json under version control and resetting it before test runs prevents accumulated state from interfering with expected results.

Starting the server

The default command starts the server on port 3000:

Bash
npx json-server db.json

A custom port is available via the --port flag:

Bash
npx json-server db.json --port 3001

The output on startup lists all available resources:

1JSON Server started on PORT :3001
2http://localhost:3001/
3
4Resources
5  http://localhost:3001/users
6  http://localhost:3001/posts
7  http://localhost:3001/comments
8  http://localhost:3001/settings

Available routes

For array resources, json-server generates a complete set of CRUD routes:

1GET    /posts          # list all posts
2GET    /posts/:id      # fetch a single post
3POST   /posts          # create a post (id auto-generated if missing)
4PUT    /posts/:id      # replace a post entirely
5PATCH  /posts/:id      # partial update
6DELETE /posts/:id      # remove a post

For object resources like settings, only read and write operations apply:

1GET   /settings
2PUT   /settings
3PATCH /settings

Querying the API

json-server provides a range of query parameters that cover common frontend scenarios without requiring any custom middleware.

Filtering with conditions

Field-level comparisons use the field:operator=value syntax:

1GET /posts?views:gt=200
2GET /posts?title:contains=angular
3GET /posts?authorId:eq=1
4GET /posts?views:gte=300&authorId:in=1,2

Supported operators include eq, ne, lt, lte, gt, gte, in, contains, startsWith and endsWith. String comparisons are case-insensitive.

Sorting

1GET /posts?_sort=views           # ascending
2GET /posts?_sort=-views          # descending
3GET /posts?_sort=authorId,-views # compound sort

Pagination

GET /posts?_page=1&_per_page=10

The response wraps the data in a pagination envelope:

JSON
1{
2  "first": 1,
3  "prev": null,
4  "next": 2,
5  "last": 3,
6  "pages": 3,
7  "items": 25,
8  "data": [ ]
9}

The _per_page parameter defaults to 10 when not specified. Invalid values are normalized automatically.

Embedding related resources

1GET /posts?_embed=comments
2GET /comments?_embed=post

Embedding works on the postId foreign key convention. A comments entry with postId: "1" is automatically associated with the posts entry whose id is "1".

Complex filtering

For compound conditions that go beyond simple field equality, _where accepts a JSON object:

GET /posts?_where={"or":[{"views":{"gt":300}},{"authorId":{"eq":"2"}}]}

Integrating with Angular

In an Angular project, HttpClient connects to json-server the same way it would connect to any REST API. The recommended pattern is to define the base URL in the environment files so switching between the mock and the real API requires changing only one value.

src/environments/environment.ts (development):

TypeScript
1export const environment = {
2  production: false,
3  apiUrl: 'http://localhost:3001/'
4};

src/environments/environment.prod.ts (production):

TypeScript
1export const environment = {
2  production: true,
3  apiUrl: 'https://api.example.com/'
4};

A service that communicates with the mock server:

TypeScript
 1import { Injectable } from '@angular/core';
 2import { HttpClient, HttpParams } from '@angular/common/http';
 3import { Observable } from 'rxjs';
 4import { environment } from '../environments/environment';
 5
 6export interface Post {
 7  id: string;
 8  title: string;
 9  authorId: string;
10  views: number;
11}
12
13@Injectable({ providedIn: 'root' })
14export class PostService {
15  private readonly baseUrl: string = `${environment.apiUrl}/posts`;
16
17  constructor(private readonly http: HttpClient) {}
18
19  getAll(): Observable<Post[]> {
20    return this.http.get<Post[]>(this.baseUrl);
21  }
22
23  getById(id: string): Observable<Post> {
24    return this.http.get<Post>(`${this.baseUrl}/${id}`);
25  }
26
27  getFiltered(minViews: number, page: number): Observable<Post[]> {
28    const params: HttpParams = new HttpParams()
29      .set('views:gte', minViews.toString())
30      .set('_page', page.toString())
31      .set('_per_page', '10');
32
33    return this.http.get<Post[]>(this.baseUrl, { params });
34  }
35
36  create(post: Omit<Post, 'id'>): Observable<Post> {
37    return this.http.post<Post>(this.baseUrl, post);
38  }
39
40  update(id: string, patch: Partial<Post>): Observable<Post> {
41    return this.http.patch<Post>(`${this.baseUrl}/${id}`, patch);
42  }
43
44  remove(id: string): Observable<void> {
45    return this.http.delete<void>(`${this.baseUrl}/${id}`);
46  }
47}

Because the Angular dev server and json-server run on different ports, requests go directly from the browser to localhost:3001. CORS is not an issue in this configuration. However, proxying through Angular’s dev server can be useful when the API base URL must match the frontend origin - for example, when cookie-based sessions are required.

A proxy configuration at proxy.conf.json:

JSON
1{
2  "/api": {
3    "target": "http://localhost:3001/",
4    "secure": false,
5    "pathRewrite": { "^/api": "" }
6  }
7}

Register the proxy file in angular.json under projects.<name>.architect.serve.options.proxyConfig.

Integrating with React

React does not prescribe a specific HTTP client. The examples below use the native fetch API, but the same patterns apply with axios or any other library.

Centralizing the API base URL using a Vite environment variable:

TypeScript
1// src/config/api.ts
2export const API_BASE_URL: string =
3  import.meta.env.VITE_API_URL ?? 'http://localhost:3001/';

A typed service module for posts:

TypeScript
 1// src/services/postService.ts
 2import { API_BASE_URL } from '../config/api';
 3
 4export interface Post {
 5  id: string;
 6  title: string;
 7  authorId: string;
 8  views: number;
 9}
10
11export interface PagedResponse<T> {
12  first: number;
13  prev: number | null;
14  next: number | null;
15  last: number;
16  pages: number;
17  items: number;
18  data: T[];
19}
20
21export async function fetchPosts(): Promise<Post[]> {
22  const response: Response = await fetch(`${API_BASE_URL}/posts`);
23  if (!response.ok) {
24    throw new Error(`Failed to fetch posts: ${response.status}`);
25  }
26  return response.json() as Promise<Post[]>;
27}
28
29export async function fetchPostsPaged(
30  page: number,
31  perPage: number = 10
32): Promise<PagedResponse<Post>> {
33  const url: URL = new URL(`${API_BASE_URL}/posts`);
34  url.searchParams.set('_page', page.toString());
35  url.searchParams.set('_per_page', perPage.toString());
36
37  const response: Response = await fetch(url.toString());
38  if (!response.ok) {
39    throw new Error(`Failed to fetch posts: ${response.status}`);
40  }
41  return response.json() as Promise<PagedResponse<Post>>;
42}
43
44export async function createPost(post: Omit<Post, 'id'>): Promise<Post> {
45  const response: Response = await fetch(`${API_BASE_URL}/posts`, {
46    method: 'POST',
47    headers: { 'Content-Type': 'application/json' },
48    body: JSON.stringify(post),
49  });
50  if (!response.ok) {
51    throw new Error(`Failed to create post: ${response.status}`);
52  }
53  return response.json() as Promise<Post>;
54}

Using the service in a component:

TypeScript
 1// src/components/PostList.tsx
 2import { useEffect, useState } from 'react';
 3import { fetchPosts, type Post } from '../services/postService';
 4
 5export function PostList() {
 6  const [posts, setPosts] = useState<Post[]>([]);
 7  const [loading, setLoading] = useState<boolean>(true);
 8  const [error, setError] = useState<string | null>(null);
 9
10  useEffect(() => {
11    let cancelled: boolean = false;
12
13    fetchPosts()
14      .then((data: Post[]) => {
15        if (!cancelled) {
16          setPosts(data);
17        }
18      })
19      .catch((err: Error) => {
20        if (!cancelled) {
21          setError(err.message);
22        }
23      })
24      .finally(() => {
25        if (!cancelled) {
26          setLoading(false);
27        }
28      });
29
30    return () => {
31      cancelled = true;
32    };
33  }, []);
34
35  if (loading) return <p>Loading...</p>;
36  if (error) return <p>Error: {error}</p>;
37
38  return (
39    <ul>
40      {posts.map((post: Post) => (
41        <li key={post.id}>
42          {post.title} ({post.views} views)
43        </li>
44      ))}
45    </ul>
46  );
47}

The cancelled flag prevents state updates after the component has unmounted, which avoids a common source of memory leak warnings in React.

For React projects using Vite, CORS can be avoided entirely by configuring a dev server proxy in vite.config.ts:

TypeScript
 1import { defineConfig } from 'vite';
 2import react from '@vitejs/plugin-react';
 3
 4export default defineConfig({
 5  plugins: [react()],
 6  server: {
 7    proxy: {
 8      '/api': {
 9        target: 'http://localhost:3001/',
10        changeOrigin: true,
11        rewrite: (path: string) => path.replace(/^\/api/, ''),
12      },
13    },
14  },
15});

With this configuration, the React app calls /api/posts and Vite routes the request to http://localhost:3001/posts. The environment variable VITE_API_URL can then be set to /api for local development.

Running both servers in parallel

A typical development workflow requires two processes: the mock server and the framework dev server. The concurrently package handles this cleanly:

Bash
npm install --save-dev concurrently

An example package.json scripts section for a React project:

JSON
1{
2  "scripts": {
3    "mock": "json-server --port 3001 db.json",
4    "dev": "vite",
5    "start": "concurrently \"npm run mock\" \"npm run dev\""
6  }
7}

concurrently prefixes output from each process and terminates all processes when one exits, which avoids orphaned json-server instances after stopping the dev session.

For Angular, the equivalent would replace vite with ng serve:

JSON
1{
2  "scripts": {
3    "mock": "json-server --port 3001 db.json",
4    "start": "concurrently \"npm run mock\" \"ng serve\""
5  }
6}

Limitations and when json-server is not enough

json-server is well-suited for prototyping, onboarding and early-phase feature development, but it has hard constraints that become relevant as projects grow.

No authentication or authorization. json-server serves all data without any access control. It is not a replacement for a secured staging API and should never be exposed beyond localhost.

File-based persistence with mutation risk. Because state is stored directly in db.json, concurrent writes during parallel test execution can corrupt the file. Resetting db.json to a clean baseline before each test run is the only reliable mitigation.

No business logic. Derived fields, validation rules, state transitions and side effects are not supported. Any scenario requiring more than pure data storage calls for a different tool - a custom Express or Fastify server or a contract-testing approach with tools like Pact.

Memory-bounded performance. The entire db.json file is loaded into memory on each write operation. Performance degrades noticeably with large files.

For projects that reach these limits, a lightweight custom server provides more control while remaining close to json-server’s simplicity in setup cost.

Summary

json-server provides a practical, low-friction starting point for frontend API mocking. A plain JSON file is enough to expose a REST API with full CRUD support, filtering, sorting, pagination and embedded relationships - no code required. For Angular, the integration fits naturally into the HttpClient and environment file pattern. For React, a typed service module combined with a Vite proxy produces clean component code without CORS concerns.

The tool works best at the beginning of a project or during isolated feature development. Understanding its limitations from the start avoids the common mistake of relying on it beyond its appropriate scope.

Related articles

Stop Using nginx as Your Default Static File Server in Docker

Oct 24, 2026 - 15 min read

Stop Using nginx as Your Default Static File Server in Docker

In the week of October 21, 2026, government portals in the Netherlands, Finland and Germany, hospital websites and several large companies …

GitHub Copilot - Custom Agents for Full-Stack Teams: A Practical Operating Model for .NET, React and Azure

Mar 17, 2026 - 15 min read

GitHub Copilot - Custom Agents for Full-Stack Teams: A Practical Operating Model for .NET, React and Azure

GitHub Copilot custom agents allow teams to define specialized AI assistants, each with its own role, tool access and behavioral boundaries. …

Serve SPAs correctly on Cloudflare Workers

Jan 5, 2026 - 2 min read

Serve SPAs correctly on Cloudflare Workers

Single Page Applications like React expect the server to always return index.html and let the client-side router take over. On Cloudflare …


BEN ABT

BEN ABT

Ben is Chief Engineering Officer and Principal Software Engineer at Medialesson.de, with a strong focus on .NET and Microsoft Azure. In his professional role, he designs and builds highly scalable platforms for hybrid and cloud-based scenarios. He also advises companies and C-levels on migrating to the sovereign cloud.

He runs myCSharp.de, the largest and most active German-speaking C# community forum, actively contributes to both his own and external open-source projects and regularly shares his knowledge through his blog, at community events, conferences and as a writer for technical magazines.

Let's Work Together

Looking for an experienced Platform Architect or Engineer for your next project? Whether it's cloud migration, platform modernization or building new solutions from scratch - I'm here to help you succeed.

New Platforms

Modernization

Training & Consulting

Contact Me
X Facebook LinkedIn WhatsApp
Back Next Blog Post

Open for work!

Contact Me
BEN ABT BEN ABT Software Engineer · Microsoft MVP Passionate about .NET, Azure, performance optimization and sustainable software engineering.

Medialesson GmbH Logo

Microsoft MVP Logo

Recent posts

View all
Stop Using nginx as Your Default Static File Server in Docker

Stop Using nginx as Your Default Static File Server in …

Oct 24, 2026
Containerizing .NET in 2026 Without a Dockerfile

Containerizing .NET in 2026 Without a Dockerfile

Oct 20, 2026
Problem Details in ASP.NET Core and .NET 11

Problem Details in ASP.NET Core and .NET 11

Oct 5, 2026
Resilience in .NET with Microsoft.Extensions.Resilience

Resilience in .NET with Microsoft.Extensions.Resilience

Sep 21, 2026
What Matters in ASP.NET Core 11

What Matters in ASP.NET Core 11

Sep 14, 2026

Tags

.NET (192) Csharp (96) ASP.NET Core (48) Azure (41) DevOps (17) Docker (14) Microsoft (14) EF Core (11) Microsoft MVP (11) AI (10) GitHub (10) GitHub Copilot (10) ASP.NET (9) Security (9) Synology (9) Architecture (8) Raspberry (8) Azure Functions (7) Cloudflare (7) Bicep (6)
benjamin-abt.com

About BEN

Benjamin Abt is a Software Engineer, Platform Architect and Microsoft MVP specializing in .NET, Azure, cloud architecture, performance optimization and sustainable software development.

Connect

  • Follow me on GitHub
  • Follow me on LinkedIn
  • Follow me on Mastodon
  • Microsoft MVP profile
  • Follow SchwabenCode on GitHub
  • Follow me on X

Quick Links

  • Portfolio
  • Blog
  • Workshops & Training
  • Publications
  • Get in Touch
  • Sitemap
Open for Work

© Copyright Benjamin Abt & SchwabenCode | 2011 - 2026

Content licensed under CC BY 4.0 unless otherwise noted.

  • Impressum
  • Privacy
  • Contact