How to Handle CORS When Using json-server

3 min read.

CORS errors are one of the first things that bite you when you spin up json-server as a local mock API. Here's the fix.

Why CORS Happens with json-server

Browsers enforce the same-origin policy. When your frontend runs on http://localhost:3000 and json-server runs on http://localhost:3001, the browser treats them as different origins because the port is different. Any fetch or XHR request from your frontend to json-server gets blocked unless the server responds with the right CORS headers.

The error looks something like this:

Access to fetch at 'http://localhost:3001/users' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.

The fix is to run json-server programmatically so you can plug in your own middleware.

The Fix: Custom server.js

Instead of using the CLI, create a server.js file at your project root:

const jsonServer = require('json-server');
const server = jsonServer.create();
const router = jsonServer.router('db.json');
const middlewares = jsonServer.defaults();

server.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Headers', '*');
  next();
});

server.use(middlewares);
server.use(router);

server.listen(3001, () => {
  console.log('JSON Server is running on port 3001');
});

Run it with:

node server.js

The middleware runs before json-server's router, so every response gets those headers attached. That's it for basic CORS.

Handling Preflight OPTIONS Requests

For non-simple requests - anything with a JSON body, a custom header like Authorization, or methods like PUT, PATCH, DELETE - the browser sends a preflight OPTIONS request first to check if the server allows it. Without handling that, your DELETE or PATCH calls will still fail.

Add an OPTIONS check to the same middleware:

server.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Headers', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');

  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }

  next();
});

When the browser sends that preflight check, your server responds immediately with 200 and the right headers. The actual request then goes through cleanly.

Wildcard vs Specific Origin

Access-Control-Allow-Origin: * is fine for local dev. It allows requests from any origin, which is exactly what you want when mocking an API.

The one case where it breaks down: if you need to send cookies or use credentials: 'include' in your fetch calls, the browser will reject a wildcard origin. For that you need to specify the exact origin and set the credentials header:

res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
res.setHeader('Access-Control-Allow-Credentials', 'true');

For a local mock server you almost never need this, but it's worth knowing before you spend 20 minutes debugging why credentialed requests aren't working.

Conclusion

CORS in json-server is fixed by ditching the CLI and writing a small server.js with middleware that sets Access-Control-Allow-Origin and Access-Control-Allow-Headers. If you're making PUT, PATCH, DELETE, or any request with custom headers, add the OPTIONS handler too. Wildcard origin covers everything for local dev unless you need credentials.

Latest Posts