Hapi-Swagger Gotchas
Tags: hapi, swagger
Published: 2019-11-10 15:09:45

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:

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

You would do this:

```javascript
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:

```javascript
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: 

```javascript
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.)