How to Secure JSON Server in a Production Environment: 7 Essential Hardening Steps

To secure JSON Server in production, disable the development file watcher by setting NODE_ENV=production, restrict CORS to specific origins in src/app.ts, mount authentication middleware before routes, disable or guard mutating HTTP verbs, and deploy behind a TLS-terminating reverse proxy.

JSON Server from typicode/json-server provides a full fake REST API for rapid prototyping, but its default configuration is deliberately permissive to accelerate development. When you need to expose this service to real traffic, you must harden the tinyhttp-based application against unauthorized access and data corruption. This guide shows you exactly how to secure JSON Server in a production environment by extending the core source files with industry-standard middleware and deployment patterns.

1. Understand the Default Security Posture

Before hardening, recognize that JSON Server ships with open defaults intended for local development. According to the source code in src/app.ts, the server enables CORS for any origin and exposes unrestricted CRUD endpoints powered by lowdb. Meanwhile, src/bin.ts activates a file watcher that rewrites db.json on every change, which is dangerous under production load. The core architecture uses tinyhttp, making it straightforward to inject Express-compatible middleware for authentication, logging, and access control.

2. Enable Production Mode and Disable File Watching

The codebase checks process.env['NODE_ENV'] to toggle development behaviors. In src/app.ts, this flag controls view caching:

// src/app.ts
const isProduction = process.env['NODE_ENV'] === 'production';
const eta = new Eta({
  views: join(__dirname, '../views'),
  cache: isProduction,          // enables caching only in prod
});

Setting NODE_ENV=production also disables the file watcher defined in src/bin.ts, preventing the server from reloading the database file on every write during high-traffic scenarios.

Action: Start the server with the production flag:

NODE_ENV=production npx json-server db.json

3. Lock Down CORS to Trusted Origins

By default, src/app.ts applies app.use(cors()) without restrictions, allowing any domain to interact with your API. Replace this with a whitelist before route definitions so the restriction inherits to all endpoints:

import { cors } from '@tinyhttp/cors';

// src/app.ts – replace existing CORS block
app.use(
  cors({
    origin: ['https://example.com', 'https://api.example.com'],
    allowedHeaders: (req) =>
      req.headers['access-control-request-headers']?.split(',').map((h) => h.trim()),
  })
);

Because the CORS middleware mounts early in the chain, it guards every subsequent route including those generated dynamically from your JSON file.

4. Implement Authentication Middleware

JSON Server does not provide authentication out of the box, but you can mount any tinyhttp-compatible guard. Insert this before the body parser and route definitions in src/app.ts to reject unauthorized requests early:

import { Request, Response, NextFunction } from '@tinyhttp/app';

// Simple API‑key guard
function apiKeyGuard(req: Request, res: Response, next: NextFunction) {
  const key = req.headers['x-api-key'];
  if (key === process.env['JSON_SERVER_API_KEY']) {
    return next();
  }
  res.status(401).json({ error: 'Invalid API key' });
}

// src/app.ts – after CORS, before routes
app.use(apiKeyGuard);

Store the secret in an environment variable (JSON_SERVER_API_KEY) and never commit it to version control.

5. Restrict or Remove Mutating Endpoints

If your production use case is read‑only, eliminate the risk of data corruption by disabling write operations. In src/app.ts, comment out or conditionally mount the mutating routes that bind to methods in src/service.ts:

// src/app.ts – comment out write routes or protect with middleware
// app.post('/:name', withBody(service.create.bind(service)));
// app.put('/:name', withBody(service.update.bind(service)));
// app.patch('/:name', withBody(service.patch.bind(service)));
// app.delete('/:name/:id', async (req, res, next) => { ... });

Alternatively, wrap these routes with the same apiKeyGuard so only administrative clients can modify data while public visitors retain read access.

6. Deploy Behind a Reverse Proxy with TLS

JSON Server does not manage TLS certificates. Deploy it behind nginx, Traefik, or a cloud load balancer that terminates HTTPS and provides additional firewall rules. A minimal nginx configuration:

server {
    listen 443 ssl;
    server_name api.example.com;

    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

This setup shields the Node.js process from direct internet exposure and centralizes certificate management.

7. Harden File Permissions and Enable Structured Logging

Protect the underlying lowdb database file and gain observability with these final steps:

File Security:

  • Ensure db.json is owned by the runtime user and not world‑writable (chmod 600 db.json).
  • Periodically backup the file to a secure location.
  • The Observer class already serializes writes, but you can configure lowdb to use atomic file replacement via fs.rename for stronger consistency guarantees.

Logging: Production logging defaults to off (createApp(db, { logger: false })). Enable a structured logger like pino in src/app.ts:

import pino from 'pino';
const logger = pino({ level: 'info' });

app.use((req, res, next) => {
  logger.info({ method: req.method, url: req.url, ip: req.ip });
  next();
});

Ship these logs to a central monitoring system and combine with process managers like PM2 or systemd to restart the service on crashes.

Summary

  • Set NODE_ENV=production to disable the file watcher and enable view caching in src/app.ts.
  • Whitelist CORS origins instead of allowing all domains.
  • Mount authentication middleware (API keys or JWT) before route definitions to block anonymous requests.
  • Disable mutating routes (POST/PUT/PATCH/DELETE) or wrap them with authorization guards when read‑only mode is required.
  • Use a reverse proxy (nginx) for TLS termination and IP filtering.
  • Restrict file permissions on db.json and enable structured logging for audit trails.

Frequently Asked Questions

Can JSON Server handle HTTPS natively?

No. The tinyhttp core in src/app.ts does not include TLS termination capabilities. You must place JSON Server behind a reverse proxy such as nginx or a cloud load balancer that manages SSL certificates and terminates HTTPS traffic before forwarding plain HTTP to the Node.js application.

How do I prevent the database file from being corrupted during concurrent writes?

The Observer class in the source code serializes write operations to db.json, but you should also ensure the file has restrictive Unix permissions (not world‑writable) and is owned by the service account. For stronger guarantees, configure the underlying lowdb adapter to write to a temporary file and use fs.rename for atomic replacement.

Is it safe to use JSON Server for production data?

Only if you implement the hardening steps above. The default configuration exposes all CRUD operations to any origin without authentication, which is unsafe for production. You must add authentication middleware, restrict CORS, and consider read‑only mode or IP allowlisting before exposing real data.

How do I disable specific HTTP methods like DELETE or POST?

In src/app.ts, locate the route definitions that use withBody(service.create.bind(service)), service.update, service.patch, or the async handler for DELETE. Comment out these lines or conditionally mount them only when an admin API key is present, effectively removing public write access while preserving GET endpoints.

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 →