Swagger Express Middleware

Swagger 2.0 middlware and mocks for Express.js

Sample 1 Walkthrough

YAML Walkthrough

Now that you have the sample running, it’s time to look at the Swagger API. Open up PetStore.yaml and let’s go through it.

TIP: All of the behavior discussed on this page is the default behavior of Swagger Express Middleware. You can modify/disable any of this behavior, either by passing options to the middleware, or by adding your own custom logic.

Consumes/Produces

The first section defines the default MIME types that any given operation in the API can consume and produce. Most operations in the Swagger Pet Store API send and receive JSON data, but there are a few that override these default values. For example, the POST /pets/{petName}/photos operation accepts multipart/form-data, and the GET /pets/{petName}/photos/{id} operation produces several different image formats.

TIP: If you try to send a request with a Content-Type header that doesn’t match a consumes value, then the Validate Request middleware will return an HTTP 415 (Unsupported Media Type) error.

TIP: If you send a request with an Accept header that doesn’t match a produces value, then the Validate Request middleware will return an HTTP 406 (Not Acceptable) error.

Model Definitions

The next section defines the models for the API: pet, veterinarian, and address. Each model definition is a JSON Schema that defines the model’s properties, whether they’re required or optional, default values, data types, minimum lengths, min/max values, RegEx patterns, enumerations, and more.

TIP: If you send JSON data that doesn’t comply with the model definition, then the Parse Request middleware will return an HTTP 400 (Bad Request) error.

Querying/Deleting Pets

The first two operations in the Pet Store API are GET /pets and DELETE /pets. Both of these operations can be called without any query parameters, in which case they will return or delete all pets, respectively. You can also pass one or more query parameters to filter which pets are returned or deleted.

For example, /pets?type=cat would get/delete all pets where type is “cat”, and /pets?age=4&tags=brown&tags=fluffy would get/delete all pets where age is 4 and the tags array contains both “brown” and “fluffy”. You can also filter by nested properties. For example, /pets?vet.address.state=CA would get/delete all pets where the state of the address of the vet is “CA”.

How Filtering Works

This filtering functionality is provided by the Mock middleware. It will only filter by query parameters that are explicitly defined in the Swagger API. For example, the “name” parameter in /pets?name=Fido will be ignored, even though pets do have a name property, because there is no “name” query parameter defined in the API.

TIP: Notice that all the query parameters are defined in the definitions section and the GET and DELETE operations use $ref pointers to reuse the same parameters. This allows us to save about 60 lines of duplicated code.

Response Codes

The GET /pets and DELETE /pets operations both define a “default” response code rather than a specific HTTP status code such as 200. Because of this, the Mock middleware will return whichever response code makes the most sense. For GET requests, this is always 200. For DELETE requests, it is 200 (OK) or 204 (No Content), depending on whether or not the operation returns data. In this case, the DELETE operation does return data, so a 200 response code is used.

Response Schema

The GET /pets and DELETE /pets operations both define a response schema that is an array of pets. Because of this, the Mock middleware will return the JSON data for all of the pets that match the filter critera. If no pets match the criteria, or there are no pets at all, then an empty array is returned.

If the response schema was just a pet (rather than an array of pets), then the mock middleware would return the first matching pet.

Response Headers

The GET /pets operation has a Last-Modified response header defined, so the Mock middleware will automatically set this header correctly. The middleware keeps track of when each object was last modified, so from the list of pets that match the filter criteria, it will set the header to the max modified date/time.

Adding New Pets

The POST /pets operation lets you add new pets. Each pet that you add gets its own URL. For example, if you add the pet {name: "Fido", type: "dog", age: 4}, then you can later GET, PATCH, or DELETE this pet at /pets/Fido. But how does this URL get created? Why did it use the pet’s name property rather than its type or age?

Determining the primary key

The Mock middleware tries to determine your model’s primary key using a few different techniques, depending on data types. For object types, such as our pet model, it first looks for common property names like id, key, username, name, etc. If that doesn’t work, then it looks for required properties in your JSON schema. If all else fails, then it just generates a random, unique value.

Response Headers

The POST /pets operation has a Location response header defined, so the Mock middleware will automatically set this header to the URL that was created for the pet (using its primary key).

Response Code

The response code for POST /pets will be 201 (Created), since that’s defined in the API.

Response Schema

The response schema is a pet object, so the newly-created pet will be returned. If the response schema was an array of pets, then all pets (including the new one) would be returned.

Getting/Editing/Deleting a Pet by Name

The /pets/{petName} path has three operations: GET, PATCH, and DELETE. These are very similar to the operations we’ve already discussed, except that these three only operate on a single pet, rather than all pets or a filtered list of pets. The Mock middleware will figure out that the {petName} path parameter corresponds to the name property on the pet model (using the same logic as before). This would still work, even if the parameter name was completely different, like theThingICallMyPet.

Since the pet is determined by the {petName} parameter, the GET and DELETE operations don’t need to pass any other parameters. The PATCH operation accepts a pet parameter that is the JSON data to update the pet.

Changing a Pet’s Name

Let’s say you have this pet: {name: "Fido", type: "dog"} at the URL /pets/Fido, and you decide you want to change Fido’s name to “Fluffy”. So, you send a PATCH request to /pets/Fido with the data {name: "Fluffy", type: "dog"}. Presto! Fido is now Fluffy. Except for one thing: Fluffy’s URL is still /pets/Fido, not /pets/Fluffy. Why is that?

Even though the Mock middleware is pretty sure the URL should be /pets/Fluffy, it will do exactly what you told it to do, which is to save the new pet data to the URL you specified. But what if you really want the URL to change whenever a pet’s name changes? That’s a perfect situation for some custom middleware logic, which we cover in the Sample 2 Walkthrough.

PUT vs PATCH

The Mock middleware treats PUT and PATCH operations slightly differently. A PATCH operation merges the new data with the old data, while a PUT operation overwrites the old data entirely.

Adding Photos

Up to now, we’ve only been looking at operations that send and receive JSON data. But the POST /pets/{petName}/photos operation breaks that trend. It consumes multipart/form-data, consisting of two string parameters, an integer parameter, and our first file parameter.

Auto-Generated IDs

The label and photo parameters are required. description and id are optional. If you don’t specify a value for the description parameter, then it will be left undefined. If you don’t specify a value for the id parameter, then the Mock middleware will automatically set it to a random, unique value. This is because the mock middleware uses the same logic as before to determine that the id is the primary key for a photo. Whenever a primary key is not set, the mock middleware will set it.

Min/Max File Size

Notice that the photo parameter has a minLength and maxLength specified. The Parse Request middleware will return an HTTP 400 (Bad Request) error if the file is too small or too large.

Listing Photos

The POST and GET operations both have an object response schema defined. The properties of this schema correspond to the four parameters of the POST request. The photo property is not the raw binary image data, but rather an object containing information about the photo, such as file name, size, MIME type, etc. We’ll discuss how to get the actual image a bit later.

Getting/Deleting a Photo by ID

The GET /pets/{petName}/photos/{id} and DELETE /pets/{petName}/photos/{id} operations should seem pretty straightforward by now. Just as before, the {petName} parameter corresponds to the pet’s name property, and the {id} parameter corresponds to the photo’s id.

Returning the Actual Image

The GET operation has a new twist. Rather than returning JSON data, it returns the actual image file that you uploaded. This is accomplished by two things: First, the produces MIME types are all image/* rather than application/json, and second, the response schema is type: file rather than type: object. Whenever the Mock middleware encouters a file response schema, it will return the raw file data rather than the JSON object describing the file.

TIP: If the image file no longer exists on the server (such as if it has been deleted), then the mock middleware will return an HTTP 410 (Gone) response.

Empty DELETE Response

Notice that the DELETE operation does not have a response schema defined. It simply has a default response code and that’s all. Because of this, the Mock middleware will send an HTTP 204 (No Content) response rather than an HTTP 200 (OK).

Default/Example Responses

The very last thing in the Swagger Pet Store API is GET / operation. This operation is what serves the HTML page that you see whenever you go to http://localhost:8000. How does this work? The Swagger 2.0 spec allows you to specify default and example values for response schemas, model schemas, and parameters, and Swagger Express Middleware will automatically use these values whenever no other value is available. In this case, the response schema for the GET / operation has a default value that points to the index.html file, so that file gets served as the response. It doesn’t have to be an HTML file though. Any type of file would work, or you could include a literal default value such as: default: "<h1>Hello World</h1>" or default: {title: "Hello World", message: "Welcome to the Swagger Pet Store"}. Of course, you’ll want to make sure your default value and your produces MIME types match.

Sample 1 Walkthrough