Mock middleware
Fully-functional mock implementations for every operation in your API, including data persistence, all with zero code! This is a great way to test-drive your API as you write it, or for quick demos and POCs. You can even extend the mock middleware with your own logic and data to fill in any gaps.
NOTE: The Mock middleware is not intended to be a 100% perfect implementation of every possible Swagger API. It makes intelligent guesses about the intended behavior of your API based on good RESTful API design principles, but sometimes those guesses can be incorrect. Don’t worry though, it’s really easy to enhance, alter, or even replace the default behavior with your own custom behavior.
Examples
For some examples (and explanations) of the Mock middleware in action, see the Sample 1 walkthrough and Sample 2 walkthrough.
const express = require('express');
const createMiddleware = require('@apidevtools/swagger-express-middleware');
let app = express();
createMiddleware('PetStore.yaml', app, function(err, middleware) {
app.use(
middleware.metadata(),
middleware.parseRequest(),
middleware.validateRequest(),
middleware.mock()
);
app.listen(8000, function() {
console.log('POST some data to http://localhost:8000/pets');
});
Options
middleware.mock(router, dataStore)
This is the function you call to create the Mock middleware.
-
router (optional) -
express.App
orexpress.Router
An Express Application or Router that will be used to determine settings (such as case-sensitivity and strict routing).
All Swagger Express Middleware modules accept this optional first parameter. Rather than passing it to each middleware, you can just pass it to the createMiddleware function (as shown in the example above) and all middleware will use it. -
dataStore (optional) -
DataStore object
By default, the Mock middleware creates a new MemoryDataStore instance, which stores mock data as an in-memory array. But this parameter allows you to specify your ownDataStore
object to use instead. Maybe you alrady have aMemoryDataStore
object that’s pre-populated with sample data. Or maybe you want to use a FileDataStore instead. Or perhaps you want to create your own custom class that inherits from the DataStore abstract class and saves data to a SQL database or third-party web service.
Dependencies
The Mock middleware requires the following middleware to come before it in the middleware pipeline:
Default Behavior
The Mock middleware’s behavior varies greatly depending on the HTTP request and the structure of your Swagger API. At a high level, the logic consists of three steps:
- Determine if it’s a collection or resource operation
- Perform the corresponding action for the HTTP method
- Send the response
1) Determine if it’s a collection or resource operation
Two fundamental concepts in RESTful API are resources and collections. Put simply, resources are the things in your API — the users, the products, the orders, etc. — and collections are groups of those things — all the products in your database, all the orders for a user, etc. Every REST operation is either operating on a resource or a collection of resources, so the first thing the Mock middleware does is determine which one.
To determine this, it compares the response schema of your GET
or HEAD
operation to the request schema of your POST
, PUT
, or PATCH
operation. If the response schema is an array
, or an object
with an array
property that matches your request schema, then the path is considered a collection path; otherwise, it’s considered a resource path. For example, the Swagger Pet Store API has five paths defined:
-
/pets
Has aget
operation with anarray
response schema, therefore it’s a collection path. You canget
multiple pets from the collection,delete
multiple pets from the collection, andpost
a new pet to the collection. -
/pets/{petName}
Has aget
operation with anobject
response schema, therefore it’s a resource path. You canget
a specific pet,delete
a specific pet, orpatch
(update) a specific pet. -
/pets/{petName}/photos
Has aget
operation with anarray
response schema, therefore it’s a collection path. You canget
all photos for a specific pet, orpost
a new photo for a specific pet. -
/pets/{petName}/photos/{id}
Has aget
operation with anfile
response schema, therefore it’s a resource path. You canget
a specific photo ordelete
a specific photo. -
/
This is the root URL of the API. It has aget
operation with anfile
response schema, therefore it’s a resource path. You canget
the homepage (which is the index.html file)
But what if your API has a path without a GET
operation? Or what if your GET
operation doesn’t have a response schema? In this case, the Mock middleware tries to guess whether it’s a collection or resource path based on the path parameters. If the final path segment contains a path parameter, then it’s assumed to be a resource path; otherwise, it’s a collection path. For example, if the Swagger Pet Store API didn’t have any get
operations for any of its paths, then they would be categorized like this, based on their path parameters:
-
Collections:
/pets
,/pets/{petName}/photos
, and/
(the root URL) would all be considered collection paths becasue they do not end with parameters. -
Resources:
/pets/{petName}
and/pets/{petName}/photos/{id}
would both be considered resource paths becasue they end with parameters.
NOTE: This algorithm may be enhanced with additional logic over time. If you have any ideas for ways to improve the algorithm, please let me know.
2) Perform the corresponding action for the HTTP method
This is where the CRUD happens. Each HTTP method corresponds to a CRUD action, though the action varies depending on whether this is a resource operation or a collection operation.
Resources
HTTP Method | CRUD action |
---|---|
GET |
Returns the resource. If no data exists, then an HTTP 404 (Not Found) error is sent. |
HEAD |
The same as GET , except that only the HTTP headers are sent. No body content is sent. |
POST |
Creates or updates a resource. |
PATCH |
The same as POST . |
PUT |
The same as PATCH , except that when updating an existing resource, the old data is completely overwritten with the new data, rather than merging the data. |
DELETE |
Delete the resource |
OPTIONS |
OPTIONS is usually reserved for CORS preflight requests. If you’re not using the CORS middleware, then OPTIONS is treated the same as GET |
Collections
HTTP Method | CRUD action |
---|---|
GET |
Returns all resources in the collection. If your API has query parameters, they can be used to filter the results (e.g. /pets?age=4&type=dog) |
HEAD |
The same as GET , except that only the HTTP headers are sent. No body content is sent. |
POST |
Adds new resources to the collection. The URL of the new resource is determined by its primary key. For example, a POST request to “/pets” with the data {name: 'Fido', type: 'dog'} will create a new resource at the URL “/pets/Fido” (since the name property is the primary key) |
PATCH |
The same as POST . Adds new resources or updates existing resources. |
PUT |
The same as PATCH , except that when updating existing resources, the old data is completely overwritten with the new data, rather than merging the data. |
DELETE |
Deletes all resources in the collection. If your API has query parameters, they can be used to limit which resources get deleted (e.g. /pets?age=4&type=dog) |
OPTIONS |
OPTIONS is usually reserved for CORS preflight requests. If you’re not using the CORS middleware, then OPTIONS is treated the same as GET |
How data is stored
The Mock middleware uses a DataStore object to store its data. Each resource is saved as a Resource object.
How files are stored
If your Swagger API has file
parameters, then the uploaded files are stored in your operating system’s temporary directory, with random, unique file names. You can change the default directory, and even the file-naming algorithm using the Parse Request middleware’s options.
Swagger Express Middleware uses Multer to handle file uploads, so for each file
parameter in your API, there will be a corresponding Multer file object in req.files
. This object contains detailed information about the file, such as the original file name, its size, MIME type, etc.
How primary keys are determined
Every REST resource is uniquely identified by a URL. When resources are created by a PUT
, PATCH
, or POST
to a resource path (such as /pets/{petName}), the resource’s URL is obvious. PUT /pets/Fido
creates a resource at /pets/Fido.
But when resources are created by a PUT
, PATCH
, or POST
to a collection path (such as /pets), the Mock middleware needs to determine the resource URL. It does this by trying to determine the primary key of the data model. This consists of the following logic, in order. As soon as a primary key is found, the rest of the steps are skipped.
-
Data type
If your data is a simple data type, such as a string, number, boolean, or date, then the value itself is the primary key. -
Property names
If your data model is an object, then the Mock middleware will look for any property that is usually a primary key, such asid
,key
,code
,number
,num
,nbr
,username
,name
, etc. If the data has more than one of these properties, then it chooses the one that is most likely to be the primary key (for example, it will chooseid
overname
). Also, it will ignore any properties that aren’t simple data types (string, number, boolean, or date). -
Required properties
If your Swagger API specifies any required properties for your data model, then the Mock middleware will use the first required property that is a simple data type (string, number, boolean, or date). -
File name
If the operation has a single file parameter, then the file name is used as the primary key.
If a primary key is found, then it is used as the resource’s URL. If the primary key property has no value, then a random, unique value is generated for it, according to the property’s data type. If no primary key is found at all, then a random value is generated and used as the resource’s URL.
For an example of all this in action, see the Sample 1 walkthrough. When you POST
to the the /pets path, the pet’s name
property is used for the resource URL (e.g. /pets/Fido). When you POST
a photo to the /pets/{petName}/photos path, the id
parameter is used as the resource URL (e.g. /pets/Fido/photos/12345). But the id
parameter is optional, so if you don’t specify it, then a random, unique ID is generated.
3) Send the response
The final step to the Mock middleware is sending a response back to the client. It uses your Swagger API definition to determine the status code, headers, and content-type of the response.
How the status code is set
Each operation in your Swagger API can have multiple responses defined — one for each HTTP status code, plus a “default” response. If you define any 2XX or 3XX responses, then the Mock middleware will use the lowest one as the status code. If you only have a “default” response defined, then it will use HTTP status code that is most appropriate. For POST
and PUT
operations, it will use HTTP 201 (Created). For DELETE
operations that have no response schema, it will use HTTP 204 (No Content). For everything else, it uses an HTTP 200 (OK).
NOTE: If your Swagger API doesn’t ave any 2XX or 3XX responses, and no “default” response, then the Mock will use the first status code it finds, which might be an error code.
TIP: If you set the HTTP status code yourself using custom middleware, then the Mock middleware will use that code as long as it corresponds to a response in the Swagger API. This is useful if you have multiple 2XX responses defined, and you have custom logic that determines which one is sent.
How response headers are set
If your Swagger API includes response headers, then the Mock middleware will set them. If you specify a default
value for the header, then that value will be used. If you don’t specify a default
, then the Mock middleware will set the value as follows:
-
Last-Modified
Is set to the date/time that the resource was last modified. -
Location
Is set to the resource’s URL. This is especially useful when POSTing new resources to a collection path, since it lets the client know the URL of the newly-created resource. -
Content-Disposition
Is set toattachment
with a file name that corresponds to the resource URL. -
Set-Cookie
Sets a cookie named “swagger” with a random, unique value. If the “swagger” cookie already exists, then its existing value is used instead. This makes it easy to useSet-Cookie
in your API for cookie-based tokens. -
Anything else
A random value is generated, based on the data type of the header.
TIP: You can set response headers yourself using custom middleware. The Mock middleware won’t set any headers that already have values.
How the content-type is set
The response body and Conent-Type
header are determined by the response schema in your Swagger API. If there is no response schema at all, then an empty response is sent. If there is a response schema then its type
property determines what is sent.
-
object
orarray
The response body is sent as JSON. TheContent-Type
header is set toapplication/json
unless your API includes a different JSON MIME type in theproduces
list (such astext/json
,application/x-web-app-manifest+json
, etc.) -
file
The file contents are sent as the response body, and theContent-Type
header is determined by the file type. Ifres.body
is a Buffer object, then the buffer contents are sent as the response body, and theContent-Type
header is set toapplication/octet-stream
unless your API includes a different MIME type in theproduces
list. -
Anything else
The response body is sent as plain text, and theContent-Type
header is set totext/plain
unless your API includes a different MIME type in theproduces
list (such astext/html
,text/css
,application/xml
, etc.)
Customizing Behavior
The Mock middleware examines your Swagger API, determines what it thinks is the intended behavior, and then performs that behavior. This includes sending a response to the client, which ends the request. For this reason, it usually makes sense for the Mock middleware to come last in your app’s middleware pipeline. So if it’s the last middleware in the pipeline, then how are you supposed to add additional custom logic? There are a few different ways:
Default and example responses
The Mock middleware is pretty good at figuring out the right response to send. Most REST operations operate on one or more Resources, so by default, the Mock middleware will send one or all of those resources (depending on whether your response schema is an object
or array
). But sometimes no REST resource exists, so the Mock middleware has nothing to send. When this happens, it normally sends an HTTP 404 (Not Found) error.
But you can change that. Rather than sending back a 404, you might want to send back some other value. Perhaps your own custom error object, or maybe a friendly message with instructions, or maybe just a default value. You can do this by specifying a default
or example
value on your response schema. For example:
paths:
/pets/{petName}:
get:
responses:
200:
description: If the requested pet doesn't exist, then the Fido will be sent
schema:
type: object
default:
name: Fido
type: dog
dob: 2000-04-16
tags:
- furry
- brown
Modifying the request
All Express apps use a middleware pipeline. Any middleware in the pipeline can modify the Request object, and other middleware later in the pipeline will get the modified object. You can do the same thing with the Mock middleware.
For example, if your data model has a primary key that is not specified by the client, then the Mock middleware normally generates a random unique value for the key. But what if the random value isn’t good enough for you? Maybe you have a specific algorithm for determining the key, or maybe it needs to be formatted a certain way. In this case, the answer is to simply add your own middleware to set the key yourself. You can modify the Request object so that when it gets to the Mock middleware, the key is already set to whatever value you want. Here’s an example:
// Set the photo's ID using a custom algorithm
app.post('/pets/:petName/photos', function(req, res, next) {
req.body.id = myCustomIdAlgorithm();
next();
});
// Make sure the Mock middleware comes *after* your middleware
app.use(middleware.mock());
Modifying the response
Just like you can modify the Request object in your own middleware, you can also modify the Response object. For example, you can use res.set()
to set response headers, and res.status()
to set the status code. You can also set the res.body
property, which will make the Mock middleware send that value as the response rather than whatever it would normally send. Of course, you could also just send your own response using res.send()
, but that would end the request, so the Mock middleware would never run. Using res.body
allows the Mock middleware to run, so it can do all the other things it does, such as updating the DataStore, setting response headers, setting the status code, etc.
Here’s an example of using res.body
to customize the response:
app.post('/pets', function(req, res, next) {
// Customize the response body
res.body = {
petName: req.body.name,
action: 'created',
date: new Date()
};
// Let the Mock middleware save the pet as usual
next();
});
// Make sure the Mock middleware comes *after* your middleware
app.use(middleware.mock());
Manipulating the mock data store
The Mock middleware uses a DataStore object to get and save all of its data. You can add, delete, or modify data in the data store to change how the Mock middleware behaves. There’s a great example of this in Sample 2. It has a custom middleware function to detect when a pet’s name changes. When that happens, it deletes the old pet from the data store and creates a new pet resource at the new URL (since the pet’s name is part of the URL).
See the Sample 2 Walkthrough for a detailed explanation.
Skip the Mock middleware
The Mock middleware works pretty well for most standard REST operations, but many REST APIs have a few operations that are atypical or require highly customized behavior. In these cases, it may be best to simply bypass the Mock middleware entirely and just write your own implementation. Fortunately the Express middleware pipeline was built with this sort of thing in mind. Any middleware anywhere in the pipeline can choose not to call next()
, which skips the rest of the middleware in the pipeline. So it’s very easy for you to add your own implementation for certain operations that bypasses the Mock middleware entirely, while still allowing other operations to be handled by the Mock middleware.
This is also a great technique for developing your API. You can start-off with the Mock middleware handling all operations in your API, and then start writing your own implementations one-by-one. Once you have every operation implemented, you can remove the Mock middleware. Or you can just leave the Mock middleware in place for when you inevitably add new operations to your API later.