DEV Community

wilfred@eastpole.nl for East Pole

Posted on

Hapi-Swagger Gotchas

I am currently working on an API and I'm using Hapi to make sure my endpoints are validated without too much trouble, and to make sure I'm getting a properly documented API in Swagger for (almost) free.

Now, the good news is: there is not a lot that I need to do in order to have an API that is documented quite properly. The bad news is, instead of having to understand Swagger, you now have to understand all the Hapi incantations to get Swagger output in the way you want it.

There are a couple of things I ran into that I'm sure I will have forgotten the next time I get to it, so I will post a note here. In some of these cases, it required some digging around to understand how to get it to work properly, so hopefully, by posting it here, you don't have to.

Producing either JSON or a text stream

Hapi already allows you to document to validations for the response of your endpoint. Unfortunately, that only works in case your response is an object. So what do you do if your endpoint can both produce a JSON object, as well as a plain text stream? In that case, you might not care too much about the validations, but having the JSON version documented might still be useful.

In that case, rather than documenting your response as a Joi schema in the validate section of your endpoint configuration, you might consider relying on Hapi-Swagger's mechanism for documenting responses.

So rather than doing this:

options: {
  validate: {
    response: Joi.object().keys(...)
  }
}

You would do this:

options: {
  'hapi-swagger': {
    responses: {
      200: {
        schema: Joi.object().keys(...)
      }
    }
  }
}

Labels

If your endpoints are returning JSON and if you define the structure of your JSON with Joi validations, either using the validate key or the hapi-swagger.responses.200.schema key of your definition, then you might want to consider adding labels to all of your Joi.object()s. Without it, hapi-swagger will automatically label the 'models', which will render your documentation pretty much unreadable.

Markdown

This might be a surprise, but Swagger descriptions are supporting Markdown. I suggest you use it. It helps to add a little bit more structure to your documentation.

Security

If you don't provide unauthorized access to your endpoints, then it helps including information on how to authenticate in your swagger.json file. I found the Hapi-Swagger documentation a little dense on how to do it. It turns out you will have to do two things:

In the general plugin options of your Hapi-Swagger configuration, you will have to add a securityDefinitions option:

options: {
  securityDefinitions: {
    'API Key': {
      type: 'apiKey',    // apiKey is defined by the Swagger spec
      name: 'apiKey',    // the name of the query parameter / header
      in: 'query'        // how the key is passed
    }
  }

The next thing you need to do is add an option to the route configuration of every route that requires authentication:

options: {
  'hapi-swagger': {
    security: [{ 'API Key': {} }]
  }
}

Note that the key in your security definitions and the key in your route options need to correspond.

If you add it this way, then the Swagger UI will include an «Authorize» button at the top of your API description, allowing you to set the API key for all further invocations. You can also click the little lock in all the endpoints. That will bring up the same dialog.

Now, bear in mind that adding this does not guarantee that your API is also protected against unauthorized access. It just guarantees that Swagger understands the need for an API key. You will still have to add an authentication scheme to your Hapi server, and include it in the auth key of your route configuration.

If you do that, and if you use a query param based API key, then you might find that your query param validations no longer work. After all, you are now all of a sudden passing in an additional apiKey parameter, that your endpoint itself is completely unaware of.

There are two ways to solve it: either you change the Joi object associated with the query key of your route definition to include unknowns, or you remove the apiKey in the lifecycle hook of your authentication scheme. (I did the latter.)

Top comments (0)