Swagger Express Middleware

Swagger 2.0 middlware and mocks for Express.js

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.

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:

  1. Determine if it’s a collection or resource operation
  2. Perform the corresponding action for the HTTP method
  3. 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:

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:

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.

  1. 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.

  2. 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 as id, 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 choose id over name). Also, it will ignore any properties that aren’t simple data types (string, number, boolean, or date).

  3. 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).

  4. 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:

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.

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.