An HTTP mock server for simulating APIs with minimal setup — ideal for testing difficult to reproduce states.
Motivation
No API state should be too hard to test. With Mockaton, developers can achieve correctness and speed.
Correctness
- Enables testing of complex scenarios that would otherwise be skipped. e.g.,
- Triggering errors on third-party APIs.
- Triggering errors on your project’s backend (if you are a frontend developer).
- Allows for deterministic, comprehensive, and consistent state.
- Spot inadvertent regressions during development.
- Use it to set up screenshot tests, e.g., with pixaton.
Speed
- Works around unstable dev backends while developing UIs.
- Spinning up development infrastructure.
- Syncing database states.
- Prevents progress from being blocked by waiting for APIs.
- Time travel. If you commit the mocks to your repo,
you don’t have to downgrade backends for:
- checking out long-lived branches
- bisecting bugs
Overview
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/123, the filename could be:
my-mocks-dir/api/user/[user-id].GET.200.json
Dashboard
On the dashboard you can select a mock variant for a particular route, delaying responses,
or triggering an autogenerated 500
error, among other features.
Nonetheless, there’s a programmatic API, which is handy for setting up tests (see Commander API section below).
<picture> <source media="(prefers-color-scheme: light)" srcset="pixaton-tests/macos/pic-for-readme.vp781x772.light.gold.png"> <source media="(prefers-color-scheme: dark)" srcset="pixaton-tests/macos/pic-for-readme.vp781x772.dark.gold.png">
Multiple Mock Variants
Each route can have different mocks. There are two options for doing that, and they can be combined.
Adding comments to the filename
Comments are anything within parentheses, including them.
api/login(locked out user).POST.423.json api/login(invalid login attempt).POST.401.json
Different response status code
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.GET.204.empty # No Content api/videos.GET.403.json # Forbidden api/videos.GET.500.txt # Internal Server Error
Scraping Mocks from your Backend
Option 1: Browser Extension
The companion Chrome devtools extension lets you download all the HTTP responses, and they get saved following Mockaton’s filename convention.
Option 2: Fallback to Your Backend
This option could be a bit elaborate if your backend uses third-party authentication,
because you’d have to manually inject cookies or sessionStorage
tokens.
On the other hand, proxying to your backend is straightforward if your backend handles the session cookie, or if you can develop without auth.
Either way you can forward requests to your backend for routes you don’t have
mocks for, or routes that have the ☁️ Cloud Checkbox checked. In addition, by
checking ✅ Save Mocks, you can collect the responses that hit your backend.
They will be saved in your config.mocksDir
following the filename convention.
Privacy and Security
- Zero dependencies (no runtime and no build packages).
- Does not write to disk. Except when you select ✅ Save Mocks for scraping mocks from a backend.
- Does not initiate network connections (no logs, no telemetry).
- Does not hijack your HTTP client.
- Auditable. Organized and small — under 4 KLoC (half is UI and tests).
Basic Usage
Install Node.js, which comes with
npm
andnpx
Create a sample mock in the default mocks directory (
./mockaton-mocks
)mkdir -p mockaton-mocks/api echo "[1,2,3]" > mockaton-mocks/api/foo.GET.200.json
Run Mockaton (
npx
installs it if needed)npx mockaton --port 2345
Test it
curl localhost:2345/api/foo
Alternative Installations
npm install mockaton --save-dev
Then, add the script to your package.json
:
{
"scripts": {
"mockaton": "mockaton --port 2345"
}
}
Since Mockaton has no dependencies, you can create an executable
by linking to src/cli.js
.
git clone https://github.com/ericfortis/mockaton.git --depth 1
ln -s `realpath mockaton/src/cli.js` ~/bin/mockaton # some dir in your $PATH
CLI Options
The CLI options override their counterparts in mockaton.config.js
-c, --config <file> (default: ./mockaton.config.js)
-m, --mocks-dir <dir> (default: ./mockaton-mocks/)
-s, --static-dir <dir> (default: ./mockaton-static-mocks/)
-H, --host <host> (default: 127.0.0.1)
-p, --port <port> (default: 0) which means auto-assigned
-q, --quiet Errors only
--no-open Don’t open dashboard in a browser (noops onReady callback)
-h, --help Show this help
-v, --version Show version
mockaton.config.js (Optional)
As an overview, these are the defaults:
import {
defineConfig,
jsToJsonPlugin,
openInBrowser,
SUPPORTED_METHODS
} from 'mockaton'
export default defineConfig({
mocksDir: 'mockaton-mocks',
staticDir: 'mockaton-static-mocks',
ignore: /(\.DS_Store|~)$/,
host: '127.0.0.1',
port: 0,
logLevel: 'normal',
delay: 1200,
delayJitter: 0,
proxyFallback: '',
collectProxied: false,
formatCollectedJSON: true,
cookies: {},
extraHeaders: [],
extraMimes: {},
corsAllowed: true,
corsOrigins: ['*'],
corsMethods: SUPPORTED_METHODS,
corsHeaders: ['content-type', 'authorization'],
corsExposedHeaders: [],
corsCredentials: true,
corsMaxAge: 0,
plugins: [
[/\.(js|ts)$/, jsToJsonPlugin]
],
onReady: await openInBrowser,
})
mocksDir?: string
Defaults to 'mockaton-mocks'
.
staticDir?: string
Defaults to 'mockaton-static-mocks'
.
This option is not needed besides serving partial content (e.g., videos). But
it’s convenient for serving 200 GET requests without having to add the filename
extension convention. For example, for using Mockaton as a standalone demo server,
as explained above in the Use Cases section.
Files under config.staticDir
take precedence over corresponding
GET
mocks in config.mocksDir
(regardless of status code).
For example, if you have two files for GET
/foo/bar.jpg such as:
my-static-dir/foo/bar.jpg // Wins my-mocks-dir/foo/bar.jpg.GET.200.jpg // Unreachable
ignore?: RegExp
Defaults to /(\.DS_Store|~)$/
. The regex rule is
tested against the basename (filename without directory path).
host?: string
Defaults to '127.0.0.1'
port?: number
Defaults to 0
, which means auto-assigned
delay?: number
Defaults to 1200
milliseconds. Although routes can individually be delayed
with the 🕓 Checkbox, the amount is globally configurable with this option.
delayJitter?: number
Defaults to 0
. Range: [0.0, 3.0]
. Maximum percentage of the delay to add.
For example, 0.5
will add at most 600ms
to the default delay.
proxyFallback?: string
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.
collectProxied?: boolean
Defaults to false
. With this flag you can save mocks that hit
your proxy fallback to config.mocksDir
. If the URL has v4 UUIDs,
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. In other words, responses of routes with the ☁️ Cloud Checkbox selected will be saved with unique filename-comments.
An .empty
extension means the Content-Type
header was not sent by your backend.
An .unknown
extension means the Content-Type
is not in
the predefined list. For that, you can add it to config.extraMimes
formatCollectedJSON?: boolean
Defaults to true
. Saves the mock with two spaces indentation —
the formatting output of JSON.stringify(data, null, ' ')
cookies?: { [label: string]: string }
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'
}),
'None': ''
}
The selected cookie, which is the first one by default, is sent in every response in a
Set-Cookie
header (as long as its value is not an empty string). The object key is just
a label for UI display purposes, and also for selecting a cookie via the Commander API.
If you need to send more than one cookie, you can inject them globally
in config.extraHeaders
, or individually in a 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.
extraHeaders?: string[]
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'
]
extraMimes?: { [fileExt: string]: string }
config.extraMimes = {
jpe: 'application/jpeg',
html: 'text/html; charset=utf-8' // overrides built-in
}
Those extra media types take precedence over the built-in utils/mime.js, so you can override them.
plugins?: [filenameTester: RegExp, plugin: Plugin][]
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.
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 include it if you need it.
// IOW, 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()
}
}
corsAllowed?: boolean
Defaults to true
. When true
, these are the default options:
config.corsOrigins = ['*']
config.corsMethods = require('node:http').METHODS
config.corsHeaders = ['content-type', 'authorization']
config.corsCredentials = true
config.corsMaxAge = 0 // seconds to cache the preflight req
config.corsExposedHeaders = [] // headers you need to access in client-side JS
onReady?: (dashboardUrl: string) => void
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
that implementation will be automatically used 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.
logLevel?: 'quiet' | 'normal' | 'verbose'
Defaults to 'normal'
.
quiet
: only errors (stderr)normal
: info, mock access, warnings, and errorsverbose
: normal + API access
import { Mockaton } from 'mockaton'
import config from './mockaton.config.js'
const server = await Mockaton(
config // Not required, but it’s not read by default.
)
Demo App (Vite + React)
git clone https://github.com/ericfortis/mockaton.git --depth 1
cd mockaton/demo-app-vite
npm install
npm run mockaton
npm run start # in another terminal
The demo app has a list of colors containing all of their possible states. For example, permutations for out-of-stock, new-arrival, and discontinued.

Use Cases
Testing Backend or Frontend
- Empty responses
- Errors such as Bad Request and Internal Server Error
- Mocking third-party APIs
- Polled resources (for triggering their different states)
- alerts
- notifications
- slow to build resources
Testing Frontend
- Spinners by delaying responses
- Setting up UI tests
Demoing complex backend states
Sometimes, the ideal flow you need is too difficult to reproduce from the 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.
The aot-fetch-demo repo has a working example.
You can write JSON mocks in JavaScript or TypeScript
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. For example, you can intercept requests to write to a database.
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.GET.200.js
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 or .ts?
Option A: Put it in your config.staticDir
without the .GET.200.js
extension.
Option B: Read it and return it. For example:
export default function (_, response) {
response.setHeader('Content-Type', 'application/javascript')
return readFileSync('./some-dir/foo.js', 'utf8')
}
Mock Filename Convention
Extension
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.
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
Dynamic parameters
Anything within square brackets is always matched.
For example, for /api/company/123/user/789, the filename could be:
api/company/[id]/user/[uid].GET.200.json
Comments
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.
Default mock for a route
You can add the comment: (default)
.
Otherwise, the first file in alphabetical order wins.
api/user(default).GET.200.json
Query string params
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.
Index-like routes
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
Commander API
Commander
is a JavaScript 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)
Select a mock file for a route
await mockaton.select('api/foo.200.GET.json')
Toggle 500
Either selects the first found 500, which could be the autogenerated one, or selects the default file.
await mockaton.toggle500('GET', '/api/foo')
Select all mocks that have a particular comment
await mockaton.bulkSelectByComment('(demo-a)')
Parentheses are optional, so you can pass a partial match. For example,
passing 'demo-'
(without the final a
) works too. On routes
with many partial matches, their first mock in alphabetical order wins.
Set route is delayed flag
await mockaton.setRouteIsDelayed('GET', '/api/foo', true)
// or
await mockaton.setStaticRouteIsDelayed('/api/foo', true)
Set static route status
await mockaton.setStaticRouteStatus('/api/foo', 404)
Set route is proxied flag
await mockaton.setRouteIsProxied('GET', '/api/foo', true)
Select a cookie
In config.cookies
, each key is the label used for selecting it.
await mockaton.selectCookie('My Normal User')
Set fallback proxy server address
await mockaton.setProxyFallback('http://example.com')
Pass an empty string to disable it.
Set save proxied responses as mocks flag
await mockaton.setCollectProxied(true)
Set global delay value
await mockaton.setGlobalDelsy(1200) // ms
Set CORS allowed
await mockaton.setCorsAllowed(true)
Reset
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()
Alternatives worth learning as well
Proxy-like
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
Client Side
In contrast to Mockaton, which is an HTTP Server, these programs hijack the HTTP client in Node.js and browsers.
- Mock Server Worker (MSW)
- Nock
- Fetch Mock
- Mentoss Has a server side too