Mockaton is an HTTP mock server with the goal of making your frontend development and testing easierâand a lot more fun.
With Mockaton you donât need to write code for wiring your mocks. Instead, just place your mocks in a directory and it will be scanned for filenames following a convention similar to the URLs.
For example, for /api/user/1234
the mock filename would be:
my-mocks-dir/api/user/[user-id].GET.200.json
In the dashboard you can select a mock variant for a particular route, among
other features such as delaying responses, or triggering an autogenerated
500
(Internal Server Error). Nonetheless, thereâs a programmatic API,
which is handy for setting up tests (see Commander API below).

Mockaton can fallback to your real backend on routes you donât have mocks for. For that, type your backend address in the Fallback Backend field.
Similarly, if you already have mocks for a route you can check the âď¸ Cloud checkbox and that route will be requested from your backend.
If you check Save Mocks, Mockaton will collect the responses that hit your backend.
Those mocks will be saved to your config.mocksDir
following the filename convention.
Want to mock a locked-out user or an invalid login attempt? Add a comment to the filename in parentheses. For example:
api/login(locked out user).POST.423.json
For instance, you can have mocks with a 4xx
or 5xx
status code for triggering
error responses. Or with a 204
(No Content) for testing empty collections.
api/videos.GET.401.json api/videos.GET.500.empty
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 mainly a list of colors, which contains all of their possible states. For example, permutations for out-of-stock, new-arrival, and discontinued.
Also, if you select the Admin User from the Mockaton dashboard, the color cards will have a Delete button.
git clone https://github.com/ericfortis/mockaton.git
cd mockaton/demo-app-vite
npm install
npm run mockaton
npm run start
By the way, that directory has scripts for opening Mockaton and Vite in one command.
The app looks like this:
- Empty responses
- Spinners by delaying responses
- Errors such as Bad Request and Internal Server Error
- Setting up UI tests
- Polled resources (for triggering their different states)
- alerts
- notifications
- slow to build resources
- Mocking third-party APIs
If you commit the mocks to your repo, itâs straightforward to bisect bugs and checking out long-lived branches. In other words, you donât have to downgrade backends to old API contracts or databases.
Perhaps you need to demo your app, but the ideal flow is too complex to
simulate from the actual backend. In this case, compile your frontend app and
put its built assets in config.staticDir
. Then, on the dashboard
Bulk Select mocks to simulate the complete states you want to demo.
For bulk-selecting, you just need to add a comment to the mock
filename, such as (demo-part1)
, (demo-part2)
.
- Avoids spinning up and maintaining hefty backends when developing UIs.
- 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 waiting for some backend API. Similarly, itâs often delayed due to missing data or inconvenient contracts. Therefore, many meetings can be saved by prototyping frontend features with mocks, and then showing those contracts to the backend team.
- Chrome DevTools allows for overriding responses
- Reverse Proxies such as Burp are also handy for overriding responses.
- Mock Server Worker
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, so you can 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 URL 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
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 when routing to it. In other words, itâs only used for documenting the URL contract.
api/video?limit=[limit].GET.200.json
Speaking of which, 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:
api/foo.GET.200.json
api/foo/bar.GET.200.json
Option B: Omit the filename.
api/foo/.GET.200.json
api/foo/bar.GET.200.json
This is the only required field. The directory must exist.
Defaults to 'localhost'
Defaults to 0
, which means auto-assigned
Defaults to /(\.DS_Store|~)$/
Defaults to 1200
milliseconds.
Although routes can individually be delayed with the đ checkbox, the delay amount is globally configurable.
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
- 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.
They take precedence over the 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 // Unreacheable
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 unidimensional 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 pass it.
import open from 'open'
config.onReady = open
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()