Middleware variants
Variants of type middleware
define an Express middleware to be executed when the request is received. It is completely on your hand to send a response, or to pass the request to the next route, etc.
Options
The options
property in a variant of type middleware
must be an object containing next properties:
middleware
(Function): Express middleware. It also receives an extra parameter containing the whole Mocks Server core instance allowing to interact with the server.
const { allUsers } = require("../fixtures/users");
module.exports = [
{
id: "get-users",
url: "/api/users",
method: "GET",
variants: [
{
id: "all-users",
type: "middleware", // variant of type "middleware"
options: {
middleware: (req, res, next, core) => { // middleware to use
core.logger.info("Request received!");
res.status(200);
res.send(allUsers);
},
},
}
]
}
];
Middleware API
The middleware
function defined in a variant of type middleware
receives next arguments when the request is executed:
middleware(req, res, next, core)
:req
Express middlewarereq
object.res
Express middlewareres
object.next
Express middlewarenext
object.core
Mocks Servercore
instance, enabling you to tap into, modify, or extend its internal behavior. Read core API for further info.
Read the Express middleware docs for further info about using middlewares.
Some use cases
Middlewares may be used for several purposes, as persisting data modifications, filtering data from fixtures, adding common headers to all of the routes, logging data from requests, etc.
Here we are going to give some examples, but they enable to do almost anything that can be done using Express, and they also enable to use the Mocks Server JavaScript API when a request is received, so the possibilities are huge.
Some of these examples show how to do things that usually shouldn't be done, because the mock should be maintained as simple as possible. For example, persisting data modifications in the server could be considered a bad practice, but for some special cases maybe you need to do it, so let's see how to achieve it.
Filtering data
Middlewares can be used to get information from the request query or body parameters and filter the response data in consequence. Suppose that you have defined an users.js
file that contains a collection of users fixtures. Then you can create an /api/users
route returning all of your users, and a /api/users/:id
returning always the same user. If this is not enough and you need your mock to return a different user depending on the ID received in the query parameters, you don't have to create a different route for each user. You can use a middleware searching for the desired user and returning it.
const { allUsers } = require("../fixtures/users");
module.exports = [
{
id: "get-users",
url: "/api/users",
method: "GET",
variants: [
{
id: "all-users",
type: "json", // variant of type "json".
options: {
status: 200,
body: allUsers // Send all users
},
}
]
},
{
id: "get-user",
url: "/api/users/:id",
method: "GET",
variants: [
{
id: "real-user",
type: "middleware", // variant of type "middleware"
options: {
middleware: (req, res) => { // Search for the user and return it
const user = allUsers.find((user) => user.id === req.params.id);
if (user) {
res.status(200);
res.send(user);
} else {
res.status(404);
res.send();
}
},
},
}
]
}
];
Changing the current collection
Now, based on the previous example, suppose that you also have a DELETE /api/users/:id
route, and you want the GET api/users/:id
to return a 404 status code after the first one is requested. Suppose also that you have a user-deleted
collection returning a 404 status code whenever GET api/users/:id
is requested. Then, you could use the core API to change the current collection once DELETE /api/users/:id
is requested:
const { allUsers } = require("../fixtures/users");
module.exports = [
{
id: "delete-user",
url: "/api/users/:id",
method: "DELETE",
variants: [
{
id: "change-collection",
type: "middleware", // variant of type "middleware"
options: {
middleware: (req, res, next, core) => { // Search for the user and return it
res.status(204);
res.send();
core.mock.collections.select("user-deleted");
},
},
}
]
}
];
Persisting data modifications
Based on the previous example, we could go a step further and remove the user that is being deleted from the users fixtures, so next requests won't return it. Take into account that the modifications made to any JavaScript object in the routes
folder will be kept in memory, so you could take advantage of this to persist some data modifications while the server is running (note that modifications will be lost when the files are modified and reloaded, for example).
const { allUsers } = require("../fixtures/users");
module.exports = [
{
id: "delete-user",
url: "/api/users/:id",
method: "DELETE",
variants: [
{
id: "delete-user",
type: "middleware", // variant of type "middleware"
options: {
middleware: (req, res, next, core) => { // Search for the user and remove it
const userIndex = allUsers.findIndex((user) => user.id === req.params.id);
if (userIndex >= 0) {
allUsers.splice(userIndex, 1);
res.status(204);
res.send();
} else {
res.status(404);
res.send();
}
},
},
}
]
}
];
Adding common headers to all responses
Middlewares can be used also to modify the response object, and let another route to definitively send it. So, based on this feature, we could add some headers to all API responses without the need to define them in all of our routes.
// mocks/routes/middlewares.js
module.exports = [
{
id: "add-headers",
url: "*",
method: ["GET", "POST", "PUT", "PATCH"],
variants: [
{
id: "add-x-custom-header",
type: "middleware",
options: {
middleware: (req, res, next) => {
res.set('x-custom-header', 'custom header value'); // Set response header
next(); // Let other routes to be processed
},
},
},
]
}
];
In order to make it work, you should add this route to your collection before any other route, because it must be processed the first. Read more about how to define collections in the collections chapter.