Swagger Express Middleware

Swagger middlware and mocks for Express.js and Node

Sample 2 Walkthrough

Overview

This sample expands upon the Sample 1 walkthrough and demonstrates a few more advanced features of Swagger Express Middleware, such as setting a few options, initializing the mock data store, and adding custom middleware logic.

Running the Sample

Sample 2 uses the same Swagger Pet Store API as Sample 1. Only the JavaScript code is different. Running the sample is the same as running sample 1, except that you replace node sample1.js with node sample2.js.

Once you’ve got the sample running, browse to http://localhost:8000 and you should see the Swagger Pet Store homepage. This is the same page as in Sample 1 (index.html), although it behaves a bit differently now. We’ll got to that next…

Screenshot

Alternate Syntax

In Sample 1, we used the createMiddleware function to load the Swagger file and initialize the middleware.

createMiddleware(swaggerFile, app, function(err, middleware) {

The createMiddleware function is a helper function that simplifies your code a bit. But in Sample 2, we’re not using it so we can show you what’s going on under the hood. When you call the createMiddleware function, it creates a new Middleware object and calls its init() method. That’s exactly what we’re doing in Sample 2:

let middleware = new Middleware(app);
middleware.init('PetStore.yaml', function(err) {

There is no functional difference between these two syntaxes. It’s just a matter of personal taste.

Pre-Populated Data

Sample 1 started out with an empty pet store, so you had to add a pet before GET /pets would return any data. Now in Sample 2, we’re using the MemoryDataStore class to pre-populate the Mock middleware with data.

// Create a custom data store with some initial mock data
let myDB = new MemoryDataStore();
myDB.save(
    new Resource('/pets/Lassie', {name: 'Lassie', type: 'dog', ...}),
    new Resource('/pets/Clifford', {name: 'Clifford', type: 'dog', ...}),
    new Resource('/pets/Garfield', {name: 'Garfield', type: 'cat', ...}),
    new Resource('/pets/Snoopy', {name: 'Snoopy', type: 'dog', ...}),
    new Resource('/pets/Hello%20Kitty', {name: 'Hello Kitty', type: 'cat', ...})
);

...

// The mock middleware will use our custom data store,
// which we already pre-populated with mock data
app.use(middleware.mock(myDB));

Each of the five sample pets is a Resource object, which is what the DataStore class uses to store data. You could also load data using the Resource.parse() method, which accepts plain JSON data and converts it to Resource objects. Here’s an example:

let data = [
    {collection: '/pets', name: '/Lassie', data: {name: 'Lassie', type: 'dog'}},
    {collection: '/pets', name: '/Clifford', data: {name: 'Clifford', type: 'dog'}},
    {collection: '/pets', name: '/Garfield', data: {name: 'Garfield', type: 'cat'}},
    {collection: '/pets', name: '/Snoopy', data: {name: 'Snoopy', type: 'dog'}},
    {collection: '/pets', name: '/Hello%20Kitty', data: {name: 'Hello Kitty', type: 'cat'}}
];

let myDB = new MemoryDataStore();
myDB.save(Resource.parse(data));

Case-Sensitive and Strict Routing

By default Express is case-insensitive and is not strict about whether paths have a trailing slash, but in this sample, we’ve changed both of those settings using the app.set() method.

app.enable('case sensitive routing');
app.enable('strict routing');

In Sample 1, /pets/Fido, /pets/fido, and /pets/Fido/ all pointed to the same pet. Now, those are treated as three different resources, and you could create a different pet at each one. If you only create a pet named “Fido”, then /pets/fido and /pets/Fido/ will return HTTP 404 errors.

All Swagger Express Middleware modules honor Express’s case-sensitivity and strict-routing settings. This is why we passed the Express App to the Middleware constructor. Every middleware module will automatically use the Express App (or Router) from the Middleware object. But you can override this for any individual middleware if you want. That’s exactly what we’ve done in Sample 2 with the Files middleware:

app.use(middleware.files(
    {
        // Override the Express App's case-sensitive and strict-routing settings
        // for the Files middleware.
        caseSensitive: false,
        strict: false
    },
    ...

So, wheras all the other middleware are case-sensitive and strict about trailing slashes, the Files middleware is the opposite. This means you can browse to http://localhost:8000/swagger/api, http://localhost:8000/Swagger/API, http://localhost:8000/Swagger/Api/, etc. and they’ll all work.

Customized Middleware Options

In Sample 1, we didn’t set any middleware options. We just accepted the defaults.

app.use(
    middleware.metadata(),
    middleware.CORS(),
    middleware.files(),
    middleware.parseRequest(),
    middleware.validateRequest(),
    middleware.mock()
);

In Sample 2, we’ve customized the Files middleware and Parse Request middleware a bit.

app.use(middleware.files(
    {
        caseSensitive: false,
        strict: false
    },
    {
        // Serve the Swagger API from "/swagger/api" instead of "/api-docs"
        apiPath: '/swagger/api',

        // Disable serving the "PetStore.yaml" file
        rawFilesPath: false
    }
));

app.use(middleware.parseRequest(
    {
        // Configure the cookie parser to use secure cookies
        cookie: {
            secret: 'MySuperSecureSecretKey'
        },

        // Don't allow JSON content over 100kb (default is 1mb)
        json: {
            limit: '100kb'
        },

        // Change the location for uploaded pet photos (default is the system's temp directory)
        multipart: {
            dest: path.join(__dirname, 'photos')
        }
    }
));

We’ve already discussed the first parameter to the Files middleware, which overrides the default case-sensitivity and strict-routing settings. In addition, we’ve also specified the second parameter, which customizes the file paths. We’ve changed the URL of the Swagger API from the default (/api-docs/) to /swagger/api. And we’ve completely disabled serving the raw Swagger file (/PetStore.yaml). This means that if you click either of the links at the top of the page (“Swagger API (YAML)” and “Swagger API (JSON)”), you’ll get an HTTP 404 (Not Found) error.

As for the Parse Request middleware, we’ve set a few parsing options, just for illustration purposes. By default, the cookie-parser uses unsigned cookies, but we’ve added a “secret” key, so cookies will now be digitally signed with this secret. Of course, in a real app, you’d use a much more secure secret. We’ve also set a limit on the size of JSON payloads. If you try to create a pet with more than 100kb of data, you’ll get an HTTP 413 (Request Entity Too Large) error. Finally, we’ve changed the default directory where uploaded files are saved. Instead of the operating system’s temp directory, pet photos will now be saved to a “photos” folder in the “samples” directory.

Custom Middleware

In addition to all the Swagger Express Middleware modules, Sample 2 also includes a couple custom middleware functions.

Changing a Pet’s Name

In Sample 1, we pointed out that when you change a pet’s name, it’s URL stays the same, since the URL for each resource is assigned when the resource is first created. Well, in Sample 2, we’ve fixed that issue:

app.patch('/pets/:petName', function(req, res, next) {
    if (req.body.name !== req.path.petName) {
        // The pet's name has changed, so change its URL.
        // Start by deleting the old resource
        myDB.delete(new Resource(req.path), function(err, pet) {
            if (pet) {
                // Merge the new data with the old data
                pet.merge(req.body);
            }
            else {
                pet = req.body;
            }

            // Save the pet with the new URL
            myDB.save(new Resource('/pets', req.body.name, pet), function(err, pet) {
                // Send the response
                res.json(pet.data);
            });
        });
    }
    else {
        next();
    }
});

This middleware listens for PATCH operations on the /pets/{petName} path. This is the operation that edits a pet. As an example, let’s say that you send the data {name: 'Fluffy', type: 'dog'} to /pets/Fido. In this case, you are renaming Fido to Fluffy, and you want the new resource URL to be /pets/Fluffy.

The middleware function first checks to see if the pet’s name has changed, by comparing the name property of the new pet data (“Fluffy”) to the petName path parameter (“Fido”). If the names are the same, then it does nothing and proceeds on to the next middleware in the pipeline. But if the names are different, then it deletes the old pet resource (at “/pets/Fido”). Notice that it’s using the same myDB object that we created earlier.

The DataStore.delete() method is asynchronous. The callback function receives the Resource that was deleted, or undefined if the resource didn’t exist (maybe it was already deleted). Either way, we know the old resource URL is no more. Now we need to save the new pet data under the new resource URL.

But first… the PATCH operation is supposed to merge the new data with the old data. That way, you don’t have re-send the entire pet data every time you update a pet. So, if the delete() method returned a pet resource, then we call the Resource.merge() method to merge-in the new data.

Now that the old pet URL is deleted, and the new data is merged with the old data, we’re ready to save the data as a new pet (with a new URL). We create a new Resource object, using the three-parameter constructor that allows us to specify the collection path (“/pets”), the resource name (“Fluffy”), and the resource data.

Finally, we pass this new Resource object to the DataStore.save() method, which is another asynchronous method. In the callback function, we send the newly-saved pet data back to the client as JSON. Note that we don’t call the next() function here, since sending a response terminates the request.

Formatting Error Messages

The second custom middleware function is pretty straightforward. Notice that it has four parameters instead of the usual three, which tells Express that it’s an error-handling middleware. It’s also the very last middleware in the pipeline, which means it will handle any unhandled errors that occur anywhere in the pipeline.

The first parameter is the Error object that was thrown. Just like most other Express middleware, Swagger Express Middleware always sets the status property of any errors to the corresponding HTTP status code. So, the middleware function calls res.status() using the err.status property.

The middleware formats the error message as HTML, so it also calls res.type() to explicitly set the Content-Type header to text/html. Then it calls res.send() to send the response.