Skip to content
Sean Kennedy edited this page Apr 1, 2019 · 34 revisions

Handlers are a special type of service used for implementing Express routes. They are automatically loaded from the handlers directory of the application during server startup.

Handlers can either use the Express app directory in order to set up routes, or they can be configured through Swagger.

Like a service, a handler is a module with an init method. Other services are dependency-injected as parameters of the init function, including the express app via the app parameter. An optional callback can be used for asynchronous initialization.

function(app, callback) {
    ...
}

Express

Below is an example handler module that registers endpoints on the Express app.

exports.init = function(app) {

    app.get('/foo', function(req, res) {
        res.status(200).send('/foo Endpoint');
    });

};

Swagger

Swagger is a tool used to describe and document RESTful APIs. Within BlueOak Server, Swagger can be used to automatically configure Express routes so that it's not necessary to hard-code route paths within handler code.

Additionally, Swagger can enable validation of request payloads and reduce the amount of code needed.

Adding a Swagger document to your projects

Swagger documents in either JSON or YAML format should be placed in the swagger directory within the project.

Wiring up handlers

When the Swagger doc is loaded, the server will look for a file within the handlers directory with the same base name as the Swagger doc. E.g., if your projects contains swagger/endpoints.json, the server will look for a corresponding handler file in handlers/endpoint.js.

For each path defined in the Swagger doc, the server will look for an operationId. The operationId determines the name of the function within the handler that handles requests to the given path.

Swagger Model validation

BlueOak Server will validate incoming requests against Swagger endpoints and respond with 422 and a body providing details of the validation error(s) to the client, e.g.:

{
  "message": "Error validating request body",
  "status": 422,
  "type": "ValidationError",
  "source": {
    "type": "body"
  },
  "validation_errors": [
    {
      "message": "Missing required property: id",
      "schemaPath": "/required/0",
      "code": 302,
      "field": "/id",
      "in": "body"
    }
  ]
}

Another example:

    {
        "message": "Multiple validation errors for this request",
        "status": 422,
        "type": "ValidationError",
        "source":
        {
            "type": "request"
        },
        "validation_errors":
        [
            {
                "message": "Missing effectiveDate query parameter",
                "schemaPath": "",
                "code": 10404,
                "field": "effectiveDate",
                "in": "query"
            },
            {
                "message": "Invalid type: number (expected string)",
                "schemaPath": "/properties/zip/type",
                "code": 0,
                "field": "/zip",
                "in": "body"
            },
            {
                "message": "Missing required property: longitude",
                "schemaPath": "/properties/location/required/1",
                "code": 302,
                "field": "/location/longitude",
                "in": "body"
            }
        ]
    }

In addition to the request body, BlueOak Server will all check for any missing or invalid header, form, path, or query parameters.

By default it validates all aspects the request before issuing a validation error response. If, however, you want it to stop after the first validation error, set rejectRequestAfterFirstValidationError in the swagger stanza of your config, e.g.:

  "swagger": {
    "rejectRequestAfterFirstValidationError": true
  }

Polymorphic Validation

The Swagger spec supports inheritance/polymorphism using the discriminator property.

If the discriminator property is used, BlueOak Server will validate against the actual provided model. For example if the response requires model 'Animal', and the provided model is 'Cat' (a subtype of 'Animal'), then it will validate against the 'Cat' model.

Disabling

You can disable polymorphic validation (usually you wouldn't want to do this) in your config, e.g.:

  "swagger": {
    "polymorphicValidation": "off"
  }

Alternately, you can set it to only print warnings in the console, e.g.:

  "swagger": {
    "polymorphicValidation": "warn"
  }

(This is meant to help teams migrating from BOS < 2.4.0 that had made use of the discriminator property before the instance would be validated against the specific match sub-model.)

Response Model Validation

It can also be configured to validate the responses you send. (This is very valuable during development and testing, but probably something you'd never want to have on in production.)

Turn on the response model validation feature by setting the swagger.validateResponseModels property in your config to either "warn", "error" or "fail". (If unset, or set to anything else, response model validation will be off.)

  • if set to "warn", any validation problems are printed using logger.warn
  • if set to "error", any validation problems are included in a _response_validation_errors property added to the response body, as well as printed using logger.error
  • if set to "fail", any validation problems cause a completely different response:
    • the failing response body is returned in the the invalidResponse.body parameter
    • the status code of the response is changed to 522
    • (see the related test case for an example response)
  • if response validation is enabled in any mode, the validated body is attached to the Express response object in a field named sentBody for use by handlers and/or middleware that are configured to execute after the response is sent

example config:

  "swagger": {
    "validateResponseModels": "warn"
  }

Swagger Ref Compiler

The purpose of the swagger ref compiler is allow you write all your models (the definitions, parameters, and responses sections of the swagger spec), as individual files, and have them included into your specification without having to manage all the $ref statements yourself.

Configuration

  • Include "refCompiler" configuration in the swagger config stanza
  • For each swagger API that you are serving, specify the name of the base spec file and the corresponding reference directories. Each reference directory can contain /definitions, /parameters, and /responses directories.
  • For .yaml specs, ref compiler looks for the comment ### ref-compiler: BEGIN when deciding where to overwrite the existing spec. Make sure to add this comment the first time ref compiler runs if you have have any existing definitions, parameters or responses that you want overwritten.

Sample config (in config/default.json, for example):

"swagger": {
  "refCompiler": {
    "petstore": {
      "baseSpecFile": "petstore.json",
      "refDirs": [
        "public",
        "v1-assets"
      ]
    },
    "api-v1": {
      "baseSpecFile": "api-v1.yaml",
      "refDirs": [
        "public"
      ]
    }
  }
}

Related directory structure for that sample config:

config/
handlers/
index.js
middleware/
nodemon.json
swagger/
  api-v1.yaml
  petstore.json
  public/
    definitions/
      Activities.yaml
      Activity.yaml
      FoodProduct.yaml
      Label.yaml
      PriceEstimate.yaml
      Product.yaml
      Profile.yaml
      ToyLabel.yaml
      ToyProduct.yaml
    parameters/
      QueryName.yaml
    responses/
      ProductSearchResults.yaml
    paths.yaml
  v1-assets/
    definitions/
      Error.yaml
    parameters/
      PathId.yaml
    responses/
      NotFound.yaml
      Unauthorized.yaml
test/

Also, take a look at the Swagger example.

Serving the Swagger Spec

By default the swagger specs are served up through :/swagger/. There's no extension on the filename, meaning that if you have a petstore.json or a petstore.yaml, the file will be served through :/swagger/petstore. By serving the spec, it's possible to import it into a tool like swagger ui and easily make calls to swagger endpoints.

Disabling

To disable serving of spec files, set `serve' to false in the swagger config, e.g.

  "swagger": {
    "serve": false,
  }

Changing the swagger context root

The default context root for serving swagger specs is /swagger, but this can be changed through the context setting in the swagger config.

Changing the host name

Since the swagger specs are most often used during development, the host field in the spec is automatically changed to localhost:. This makes it easier to test the API in a local swagger ui. This behavior can be changed through the useLocalhost setting in the swagger config. When set to false, the host value from the spec will remain unchanged.

Example Swagger Config

  "swagger": {
    "context": "/specs",
    "serve": true,
    "useLocalhost": false
  },

Extensions

NOTE: As of version 2.8.0 the BlueOak Server recommends the use of "x-bos-" prefixed extensions. The previous "x-" prefixed extensions still work, but new code should use "x-bos-handler" and "x-bos-middleware".

x-bos-handler

This is a path-level custom field that can be used to change the handler file that defines the callback functions.

Normally the callback function will be loaded from a handler file with the same name as the swagger document, e.g. petstore.json will look for functions in the handlers/petstore.js. If you wish to load a function from a different handler file, specify the name of the handler in "x-bos-handler"

  "paths": {
    "/pets/{id}": {
      "x-bos-handler": "foo", //use callbacks from handlers/foo.js
       ...
    }
  }

x-handler

Deprecated as of v2.8.0. See the x-bos-handler above.

x-bos-middleware

Use to add additional middleware callbacks to a swagger-defined endpoint. These are registered after any authentication callback and before the main handler callback.

This is a method-specific field. It can either be a single string, or an array of strings in case more than one callback is needed.

  "paths": {
    "/pets": {
      "get": {
        "x-bos-middleware": ["callback1", "callback2"]
        ...

By default it will look for the callback function in the default handler for the given endpoint. If, however, you wish to load a callback from a different handler, specify the callbacks using the form handler.function. For example, using "foo.bar" would register the bar function from handlers/foo.js.

x-middleware

Deprecated as of v2.8.0. See the x-bos-middleware above.

File uploads

File uploads can be handled as form data. See the fileupload example for details.

Custom Formats

Data types in Swagger can have both a type (string, number, etc) and format property. The spec defines a small number formats. Any format that isn't included in the spec can be customized by the user. For example, an "email" data type could have a type of "string" and format of "email".

In order to validate custom types, the swagger service allows additional formats to be specified.

swagger.addFormat('uppercase', function(data, schema) {
    //check that a tag is uppercase
    if (data !== data.toUpperCase()) {
        return 'String must be all uppercase';
    }
    return null;
});

The parameters for addFormat:

  • format - A string corresponding to the "format" value in schemas.
  • validationFunction - A function that either returns a string describing the validation error, or null if no error has occurred.

Validation Errors

The Swagger validator creates JS Errors if it hits a validation problem. The errors are passed off via a call to next(error) in order for the error-handling middleware to deal with it however it wants. The errors will have a name field that you can check the type of error. In the case of the swagger validation, it returns an error with name ValidationError.

The default error handler will attempt to create an appropriate JSON response from the error and return a 422 status code. To handle them differently, see the custom middleware in examples/swagger.

Notes

Some errors are generated by our swagger validator, such as missing required query params or headers. In those cases the Error contains a simple message such as "Missing required parameter X".

However, in all other cases, or in the case of validating a body, the validation failures are generated by the tv4 library. BlueOak server creates a single ValidatorError, and embeds all the tv4-generated errors under the subErrors field.