site logo
github logotwitter logolinkedin logo

HapiJS multiple query arguments

February 24, 2019

One thing that one happens to do often while developing frontend application is sending array of values as query parameters to a server. In this article, I’m going to show you different way of handling them if your frontend HTTP client sends them in a format that is different from what an Hapi server can interpret by default.

In my current project, we use HapiJS v17+ as our NodeJS web framework for our microservices and VueJS as our frontend framework. We’ve also been using axios as our frontend HTTP client.

In the followings examples, we’ll assume that we need to send an array of statuses to filter a list of users. With axios it looks like this:

import axios from 'axios'

const getUsers = async function() {
  const response = await axios.get('/users', {
    params: { status: ['online', 'busy'] },
  })
}

getUsers()

axios passes the query parameters as: /users?status[]=online&status[]=busy. As you can see it concatenated an opening and closing brackets to the query parameter name defined in the params attribute on the above call. Our server might not accept it if it is not configured so. By default an Hapi server does not handle such query parameter format. But don’t worry! I’ll show you several ways of doing so that your Hapi server knows all about it.

The idiomatic way

By default Hapi does no special query parameters processing except transforming the string in the URL into a JS object. However there is an option on your Hapi server object to pass a custom query parameters parser. This options is: options.query.parser. It accepts a function that takes as parameter the query parameters string from the URL and should return a JS object where each key is a parameter and matching value is the parameter value. If you need more details here is a link to the official documentation.

Alright, that’s sweet but how does it help me?! I don’t want to code that processing myself!

Well, well, this means we can now leverage external query parameters parser that you can find on npm. You just need to find one that handle the case you need. There is qs that does a perfect job for our use case. You just need to use it like so in your Hapi server:

const Hapi = require('hapi')
const Qs = require('qs')

const server = Hapi.server({
  port: 3000,
  host: 'localhost',
  query: {
    parser: query => Qs.parse(query),
  },
})

That’s it you can now receive and successfully parse the query parameters that axios will send you.

The not-so-pretty solution

Initially I made it work using this solution in my current project as I didn’t know about the parser option above. While writing this article I asked some questions on Hapi’s slack to compare my solution with others’ and someone mentioned it.

The other solution I found is to use joi, hapi’s object validation library of choice. If you haven’t used it already, I highly recommend you to give it a try. It will blow you away! In Hapi we can use joi validation for several different parts and one of them is query parameters validation. Here is how it looks like:

server.route({
  method: 'GET',
  path: '/users',
  handler(request, h) {
    return request.query
  },
  options: {
    validate: {      query: Joi.object({        status: Joi.array()          .single()          .items(Joi.string()),      }).rename('status[]', 'status', { ignoreUndefined: true }),    },
  },
})
  1. Joi.array() just tells Hapi that the status query parameter is an array.
  2. .single() tells joi to wrap your query parameter in an array when validating the value if it has only one value.
  3. .items(Joi.string()) tells joi that the array is supposed to contain only string values.
  4. .rename('status[]', 'status', { ignoreUndefined: true }) well that’s the bread and butter of this solution. It tells joi that if the query object it is validating contains a property status[] it should rename it to status. The tricky part is that the renaming process happens before applying the aforementioned validation rules. The options ignoreUndefined, as its name says, will make the rename rule ignored if there are no property status[] on that object.
  5. Joi.object() is required if we want to use .rename.

If you need more information about the functions mentioned above, please refer to joi API documentation. It is well written and you’ll find plenty explanations and examples.

Notes

We encountered a problem using that solution with another plugin that was interfering with that whole process and preventing it from working. The guilty plugin was: disinfect. We ended up deactivating it on the routes where we used that solution. You can do so by passing options in your route configuration:

server.route({
  method: 'GET',
  path: '/users',
  handler(request, h) {
    return request.query
  },
  options: {
    validate: {
      query: Joi.object({
        status: Joi.array()
          .single()
          .items(Joi.string()),
      }).rename('status[]', 'status', { ignoreUndefined: true }),
    },
    plugins: {      disinfect: {        disinfectQuery: false,      },    },  },
})

That’s it! Now you know how to handle query parameters within your HapiJS application.

You can find the two methods implemented in an example repository on GitHub.

If you think I missed something, was not thorough enough or even just to say “hi!” feel free to ping me on Twitter.


Written by Jonas who lives in Lyon, France and he's passionate about Open Source and web development in general. Feel free to ping him on Twitter.