hapi v12.0.0 is a small release focused on removing the framework dependency on the qs module. qs is a URL query string parser with special support for representing complex structures in a simple key=value format. Normally, the query a[b]=1 will parse into the object { "a[b]": "1" }, however when passed through the qs module it results in { a: { b: "1" } }. This release removes this feature and reverts it back to simple query string parsing support as well as similar functionality when parsing form-encoded payloads and multipart form submissions (field names). Additional changes include supporting more complex authentication scopes and a minor change to server.inject().

The v12.0.0 major release is sponsored by Sideway.
query.qs configuration option.payload.qs configuration option.parserOptions argument from the request.setUrl() method.! or + as those prefix characters now have a special meaning.request.route (the route public interface) settings.auth changed to move scope and entity inside a new access array.server.inject(), any HTTP trailers are no longer included in res.headers but instead are provided under res.trailers to be consistent with node.Removed request.session and request.auth.session placeholders (was set to null before).
Allow passing a pre-processed URL object (from node's URL parse()) to request.setUrl().
pendingGenerateTimeout cache option for reducing calls to the generate method while another is already pending.Promise when a callback is not provided.request.info.cors.isOriginMatch.Expose entire request.auth object in validation context.
Allow setting dynamic scope in server.auth.default()
Prevent request.raw.res.end() from being called twice.
subtext from v3.0.1 to v4.0.0
You don't have to change anything if you are not using the special qs format in:
? and the #).There are a few easy ways of identifying if you are expecting this special format:
[] characters (as well as . if you enable that custom feature of qs).qs configuration options or you pass a third argument to request.setUrl().{ a: { b: 1 } }). If this is set on a payload rule, check if you expect form-encoded submissions to that endpoint along with normal JSON.Checklist:
Check out the new hapi-qs plugin which incorporates the code below into a simple plugin form. If you prefer to use custom code, apply the code snippets below.
If you want to parse qs formatted query strings, add this to your server code:
const Url = require('url');
const Qs = require('qs');
const onRequest = function (request, reply) {
const uri = request.raw.req.url;
const parsed = Url.parse(uri, false);
parsed.query = Qs.parse(parsed.query);
request.setUrl(parsed);
return reply.continue();
};
server.ext('onRequest', onRequest);
If you previously used the connection.query.qs configuration option, pass that setting to the Qs.parse() method above.
If you want to parse qs formatted field names in payloads, add this to your server code:
const Qs = require('qs');
const onPostAuth = function (request, reply) {
if (typeof request.payload === 'object' &&
!Buffer.isBuffer(request.payload)) {
request.payload = Qs.parse(request.payload);
}
return reply.continue();
};
server.ext('onPostAuth', onPostAuth);
If you previously used the route.payload.qs configuration option, pass that setting to the Qs.parse() method above.
Note that these code examples are based on simple setups and may require adjustments for your environment.
While the existing route authentication configuration is still supported (for the time being), the scope and entity keys moved from config.auth to config.auth.access. For example:
server.route({
method: 'GET',
path: '/',
handler: handlerFunc,
config: {
auth: {
mode: 'required',
strategy: 'default',
entity: 'user',
scope: ['account']
}
}
}
Should now be expressed as:
server.route({
method: 'GET',
path: '/',
handler: handlerFunc,
config: {
auth: {
mode: 'required',
strategy: 'default',
access: {
entity: 'user',
scope: ['account']
}
}
}
}
The access config can now take an array of objects, each providing a different combination of entity and scopes. This is useful if you want to require a different scope for application accounts from user accounts.
The scope config now supports two special prefix characters: + for required scope strings and ! for forbidden scope strings. For example, the scope ['!a', '+b', 'c', 'd'] means the incoming request credentials' scope must not include 'a', must include 'b', and must include one of 'c' or 'd'.
Checklist:
scope configs in your code and make sure you do not use + or ! in your strings.entity and scope include access to prepare for future changes (nor required at this point).request.route, server.handler(), server.lookup(), server.match(), or server.on('route'), and you access the scope or entity settings, look for them under access.scope and access.entity.res.headers when using server.inject(), user res.trailers instead. The hapi-auth-hawk module uses trailers but this change only affects the module tests.request.session or request.auth.session for equality to null, test for undefined instead as they are no longer initialized to null. This is only likely if you wrote your own session manager (instead of using hapi-auth-cookie or yar.This release note is of exceptional quality! #highfive
+1
Leaving behind a link to these release notes on the releases page would be pretty useful.
@prashaantt http://hapijs.com/updates
Thanks for the update and these great release notes.
good job, will be updating to this one soon
The code here for qs-style querystring parsing seems to clobber the router's stripTrailingSlash option. I suggest using,
const Url = require('url');
const Qs = require('qs');
const onRequest = function (request, reply) {
const uri = request.raw.req.url;
const parsed = Url.parse(uri, false);
request.setUrl(Object.assign({}, request.url, {
query: Qs.parse(parsed.query)
}));
return reply.continue();
};
server.ext('onRequest', onRequest);
CC @hueniverse @nlf in case you'd like to update the release notes.
Most helpful comment
This release note is of exceptional quality! #highfive