An HTTP mock server for simulating APIs with minimal setup — ideal for triggering difficult to reproduce backend states.
With Mockaton, you don’t need to write code for wiring up your mocks. Instead, a given directory is scanned for filenames following a convention similar to the URLs.
For example, for /api/user/1234
the filename would be:
my-mocks-dir/api/user/[user-id].GET.200.json
On the dashboard you can select a mock variant for a particular route, delaying responses,
or triggering an autogenerated 500
(Internal Server Error), among other features.
Nonetheless, there’s a programmatic API, which is handy for setting up tests (see Commander API section).

Each route can have different mocks. There are two options for doing that:
Comments are anything within parentheses, including them.
api/login(locked out user).POST.423.json api/login(invalid login attempt).POST.401.json
For instance, you can use a 4xx
or 5xx
status code for triggering error
responses, or a 2xx
such as 204
for testing empty collections.
api/videos(empty list).GET.204.json # No Content api/videos.GET.403.json # Forbidden api/videos.GET.500.txt # Internal Server Error
No need to mock everything. Mockaton can forward requests to your backend for routes you don’t have mocks for, or routes that have the ☁️ Cloud Checkbox checked.
If you check Save Mocks, Mockaton will collect the responses that hit your backend.
They will be saved in your config.mocksDir
following the filename convention.
Mockaton is a Node.js program with no build or runtime NPM dependencies.
tsx
is only needed if you want to write mocks in TypeScript.
npm install mockaton tsx --save-dev
Create a my-mockaton.js
file
import { resolve } from 'node:path'
import { Mockaton } from 'mockaton'
// See the Config section for more options
Mockaton({
mocksDir: resolve('my-mocks-dir'), // must exist
port: 2345
})
node --import=tsx my-mockaton.js
This is a minimal React + Vite + Mockaton app. It’s a list of colors containing all of their possible states. For example, permutations for out-of-stock, new-arrival, and discontinued.
git clone https://github.com/ericfortis/mockaton.git
cd mockaton/demo-app-vite
npm install
npm run mockaton
npm run start
# BTW, that directory has scripts for running Mockaton and Vite
# with one command in two terminals.
The app looks like this:
- Empty responses
- Spinners by delaying responses
- Errors such as Bad Request and Internal Server Error
- Setting up UI tests
- Mocking third-party APIs
- Polled resources (for triggering their different states)
- alerts
- notifications
- slow to build resources
If you commit the mocks to your repo, it’s straightforward to bisect bugs and check out long-lived branches, so you don’t have to downgrade backends to old API contracts or databases.
Sometimes, the ideal flow you need is too difficult to reproduce from your actual backend.
For this, you can Bulk Select mocks by comments to simulate the complete states
you want. For example, by adding (demo-part1)
, (demo-part2)
to the filenames.
Similarly, you can deploy a Standalone Demo Server by compiling the frontend app and
putting its built assets in config.staticDir
. And simulate the flow by Bulk Selecting mocks.
- Avoids spinning up and updating hefty backends when developing UIs.
- Allows for a deterministic, comprehensive, and consistent backend state. For example, having a collection with all the possible state variants helps for spotting inadvertent bugs.
- Sometimes frontend progress is blocked by waiting for backend APIs.
For example, api/foo.GET.200.js
Option A: An Object, Array, or String is sent as JSON.
export default [{ foo: 'bar' }]
Option B: Function
Return a string | Buffer | Uint8Array
, but don’t call response.end()
export default (request, response) =>
JSON.stringify({ foo: 'bar' })
Think of these functions as HTTP handlers that allow you to intercept requests. For example, for writing to a database.
See Intercepting Requests Examples
Imagine you have an initial list of colors, and you want to concatenate newly added colors.
api/colors.POST.201.js
import { parseJSON } from 'mockaton'
export default async function insertColor(request, response) {
const color = await parseJSON(request)
globalThis.newColorsDatabase ??= []
globalThis.newColorsDatabase.push(color)
// These two lines are not needed but you can change their values
// response.statusCode = 201 // default derived from filename
// response.setHeader('Content-Type', 'application/json') // unconditional default
return JSON.stringify({ msg: 'CREATED' })
}
api/colors(assorted)(default).GET.200.ts
import colorsFixture from './colors.json' with { type: 'json' }
export default function listColors() {
return JSON.stringify([
...colorsFixture,
...(globalThis.newColorsDatabase || [])
])
}
What if I need to serve a static .js?
Put it in your config.staticDir
without the mock filename convention.
The last three dots are reserved for the HTTP Method, Response Status Code, and File Extension.
api/user.GET.200.json
You can also use .empty
or .unknown
if you don’t
want a Content-Type
header in the response.
Supported Methods
From require('node:http').METHODS
ACL, BIND, CHECKOUT, CONNECT, COPY, DELETE, GET, HEAD, LINK, LOCK, M-SEARCH, MERGE, MKACTIVITY, MKCALENDAR, MKCOL, MOVE, NOTIFY, OPTIONS, PATCH, POST, PROPFIND, PROPPATCH, PURGE, PUT, QUERY, REBIND, REPORT, SEARCH, SOURCE, SUBSCRIBE, TRACE, UNBIND, UNLINK, UNLOCK, UNSUBSCRIBE
Anything within square brackets is always matched. For example, for this route:
/api/company/1234/user/5678
api/company/[id]/user/[uid].GET.200.json
Comments are anything within parentheses, including them.
They are ignored for routing purposes, so they have no effect
on the URL mask. For example, these two are for /api/foo
api/foo(my comment).GET.200.json api/foo.GET.200.json
A filename can have many comments.
You can add the comment: (default)
.
Otherwise, the first file in alphabetical order wins.
api/user(default).GET.200.json
The query string is ignored for routing purposes. In other words, it’s only used for documenting the URL contract.
api/video?limit=[limit].GET.200.json
On Windows filenames containing "?" are not permitted, but since that’s part of the query string it’s ignored anyway.
If you have api/foo
and api/foo/bar
, you have two options:
Option A. Standard naming:
api/foo.GET.200.json
api/foo/bar.GET.200.json
Option B. Omit the URL on the filename:
api/foo/.GET.200.json
api/foo/bar.GET.200.json
This is the only required field. The directory must exist.
- Use Case 1: If you have a bunch of static assets you don’t want to add
.GET.200.ext
- Use Case 2: For a standalone demo server. For example, build your frontend bundle, and serve it from Mockaton.
Files under config.staticDir
don’t use the filename convention, and
they take precedence over corresponding GET
mocks in config.mocksDir
.
For example, if you have two files for GET /foo/bar.jpg
my-static-dir/foo/bar.jpg my-mocks-dir/foo/bar.jpg.GET.200.jpg // Unreachable
Defaults to /(\.DS_Store|~)$/
Defaults to 'localhost'
Defaults to 0
, which means auto-assigned
Defaults to 1200
milliseconds. Although routes can individually be delayed
with the 🕓 Checkbox, the amount is globally configurable with this option.
For example, config.proxyFallback = 'http://example.com'
Lets you specify a target server for serving routes you don’t have mocks for, or that you manually picked with the ☁️ Cloud Checkbox.
Defaults to false
. With this flag you can save mocks that hit
your proxy fallback to config.mocksDir
. If there are UUIDv4 in the
URL, the filename will have [id]
in their place. For example,
/api/user/d14e09c8-d970-4b07-be42-b2f4ee22f0a6/likes =>
my-mocks-dir/api/user/[id]/likes.GET.200.json
Your existing mocks won’t be overwritten. That is, the routes you manually selected for using your backend with the ☁️ Cloud Checkbox, will have a unique filename comment.
Extension details
An .empty
extension means the Content-Type
header was not sent by your backend.
An .unknown
extension means the Content-Type
is not in
Mockaton’s predefined list. For that, you can add it to config.extraMimes
Defaults to true
. Saves the mock with the formatting output
of JSON.stringify(data, null, ' ')
(two spaces indentation).
import { jwtCookie } from 'mockaton'
config.cookies = {
'My Admin User': 'my-cookie=1;Path=/;SameSite=strict',
'My Normal User': 'my-cookie=0;Path=/;SameSite=strict',
'My JWT': jwtCookie('my-cookie', {
name: 'John Doe',
picture: 'https://cdn.auth0.com/avatars/jd.png'
})
}
The selected cookie, which is the first one by default, is sent in every
response in a Set-Cookie
header.
If you need to send more cookies, you can either inject them globally
in config.extraHeaders
, or in function .js
or .ts
mock.
By the way, the jwtCookie
helper has a hardcoded header and signature.
In other words, it’s useful only if you care about its payload.
Note: it’s a one-dimensional array. The header name goes at even indices.
config.extraHeaders = [
'Server', 'Mockaton',
'Set-Cookie', 'foo=FOO;Path=/;SameSite=strict',
'Set-Cookie', 'bar=BAR;Path=/;SameSite=strict'
]
config.extraMimes = {
jpe: 'application/jpeg'
}
Those extra media types take precedence over the built-in utils/mime.js, so you can override them.
type Plugin = (
filePath: string,
request: IncomingMessage,
response: OutgoingMessage
) => Promise<{
mime: string,
body: string | Uint8Array
}>
Plugins are for processing mocks before sending them. If no regex matches the filename, the fallback plugin will read the file from disk and compute the MIME from the extension.
Note: don’t call response.end()
on any plugin.
See plugin examples
npm install yaml
import { parse } from 'yaml'
import { readFileSync } from 'node:js'
import { jsToJsonPlugin } from 'mockaton'
config.plugins = [
// Although `jsToJsonPlugin` is set by default, you need to add it to your list if you need it.
// In other words, your plugins array overwrites the default list. This way you can remove it.
[/\.(js|ts)$/, jsToJsonPlugin],
[/\.yml$/, yamlToJsonPlugin],
[/foo\.GET\.200\.txt$/, capitalizePlugin], // e.g. GET /api/foo would be capitalized
]
function yamlToJsonPlugin(filePath) {
return {
mime: 'application/json',
body: JSON.stringify(parse(readFileSync(filePath, 'utf8')))
}
}
function capitalizePlugin(filePath) {
return {
mime: 'application/text',
body: readFileSync(filePath, 'utf8').toUpperCase()
}
}
Defaults to true
. When true
, these are the default options:
config.corsOrigins = ['*']
config.corsMethods = require('node:http').METHODS
config.corsHeaders = ['content-type']
config.corsCredentials = true
config.corsMaxAge = 0 // seconds to cache the preflight req
config.corsExposedHeaders = [] // headers you need to access in client-side JS
By default, it will open the dashboard in your default browser on macOS and
Windows. But for a more cross-platform utility you could npm install open
and
Mockaton will use that implementation instead.
If you don’t want to open a browser, pass a noop:
config.onReady = () => {}
At any rate, you can trigger any command besides opening a browser.
Commander
is a client for Mockaton’s HTTP API.
All of its methods return their fetch
response promise.
import { Commander } from 'mockaton'
const myMockatonAddr = 'http://localhost:2345'
const mockaton = new Commander(myMockatonAddr)
await mockaton.select('api/foo.200.GET.json')
await mockaton.bulkSelectByComment('(demo-a)')
Parentheses are optional, so you can pass a partial match.
For example, passing 'demo-'
(without the final a
), selects the
first mock in alphabetical order that matches.
await mockaton.setRouteIsDelayed('GET', '/api/foo', true)
await mockaton.setRouteIsProxied('GET', '/api/foo', true)
In config.cookies
, each key is the label used for selecting it.
await mockaton.selectCookie('My Normal User')
await mockaton.setProxyFallback('http://example.com')
Pass an empty string to disable it.
await mockaton.setCollectProxied(true)
Re-initialize the collection. The selected mocks, cookies, and delays go back to
default, but the proxyFallback
, colledProxied
, and corsAllowed
are not affected.
await mockaton.reset()
These are similar to Mockaton in the sense that you can modify the mock response without loosing or risking your frontend code state. For example, if you are polling, and you want to test the state change.
- Chrome DevTools allows for overriding responses
- Reverse Proxies such as Burp are also handy for overriding responses
In contrast to Mockaton, which is an HTTP Server, these programs
mock the client (e.g., fetch
) in Node.js and browsers.
- MSW (Mock Server Worker)
- Nock
- Fetch Mock
- Mentoss Has a server side too