You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
hapi v21.0.0 is a medium-sized release focused on modernization and miscellaneous API improvements. All modules in the hapi.js ecosystem have been updated to officially support Node.js v18, be compatible with ESM projects, and drop support for Node.js v12. Plugins in the hapi.js ecosystem now support hapi v20+.
Upgrade time: medium - none to a couple of hours for many users, but could be more impactful for those running Node.js v12.
Complexity: low - requires following the list of changes to verify their impact.
Risk: low - small number of changes with simple ways to retain previous behavior in most cases.
Dependencies: medium - most existing plugins will work as-is, but upgrading from hapi v19 or Node.js v12 may require care.
Update the default server host to be IPv6-friendly ('::' if IPv6 is available, otherwise '0.0.0.0'), inheriting from Node.js defaults (Change default host to be IPv6-friendly #4357).
Plugins throughout the ecosystem (inert, vision, h2o2, jwt, log, cookie, scooter, etc.) now support hapi v20+, dropping support for hapi v19 and below.
Catbox caching engines (catbox-memory, catbox-redis, catbox-memcached, catbox-object) now export the engine as a property Engine rather than as the default export, in order to work well for ESM users. E.g. require('catbox-redis') becomes require('catbox-redis').Engine.
If you use a hapi catbox engine such as catbox-redis, you may optionally choose to update it to the latest version. If you do so, update any references to it to reflect that the engine is now exported as Engine. E.g. require('@hapi/catbox-redis') becomes require('@hapi/catbox-redis').Engine.
If you use route.options.jsonp, remove this option and instead set route.options.cors to true. You should update any clients that utilize JSONP to instead make a regular AJAX/fetch() requests.
Ensure that any stream responses that you serve are an instance of Node.js's Stream.Readable. You can easily check by exercising the route, and seeing if hapi serves a 500 with a message indicating there's an issue. A common fix is to pipe your non-standard stream through Node.js's Stream.PassThrough, e.g. nonStandardStream.pipe(new Stream.PassThrough()).
If you use emit() asynchronously, e.g. await server.events.emit(), then switch it to use the new gauge() method, e.g. await server.events.gauge() with the same arguments.
If you have any listeners to the server's 'start' or 'stop' events that are asynchronous, switch these event listeners to a server lifecycle extension, server.ext('onPreStart', ...) or server.ext('onPostStop', ...) respectively.
Node.js version
Make sure your Node.js version is v14 LTS or newer. This release uses language features that are only available in Node.js v14.15.0 or higher and will not start on older versions. Node.js v18 is the current LTS and is the recommended version.
Default server host aligned with Node.js defaults
When creating a server with Hapi.server(), if you do not specify server.options.host then hapi used to default to 0.0.0.0, i.e. all available IPv4 network interfaces. This did not jibe with IPv6 or dual-stack hosts, particularly since as of Node.js v17, the dns module would resolve a request to localhost to IPv6 network interfaces when available. So hapi now defaults server.options.host to ::1 on IPv6-compatible machines, and otherwise to 0.0.0.0, in alignment with Node.js's bare HTTP server.
Checklist:
To restore the previous behavior, set server.options.host to 0.0.0.0, e.g. Hapi.server({ host: '0.0.0.0' }).
Catbox engines are now exported as Engine
In order to be more ESM-friendly, the catbox engines (catbox-memory, catbox-redis, catbox-memcached, catbox-object) now export the engine as a property Engine rather than as the default export. There have also been changes to catbox-memcached's engine options.
Checklist:
Optionally update to the latest version of your catbox engine.
If you do so, update any references to catbox engines to reflect the new Engine export. For example:
const CatboxRedis = require('@hapi/catbox-redis');
-const engine = new CatboxRedis();+const engine = new CatboxRedis.Engine();
const server = Hapi.server({ cache: { engine } });
If you use catbox-memcached, note that the engine's location option has been renamed to server, and it takes a new format following from memcache-client's configuration.
Remove support for JSONP
JSONP (or, "JSON with Padding") was an approach to circumvent the browser same-origin policy when making AJAX requests for data. With the advent and broad client support for CORS, JSONP no longer belongs in hapi core, so the route.options.jsonp option has been removed.
Checklist:
Identify any routes using route.options.jsonp and remove the option.
You may consider replacing route.options.jsonp with route.options.cors. Setting route.options.cors to true on a route will allow cross-origin requests from any domain, similar to JSONP. You would followup by updating clients to use a standard AJAX request rather than the JSONP approach of appending a script tag to the page.
Support only standard Stream.Readable implementations for responses
Over Node.js's lifetime, we have seen several implementations of streams, and it used to be more common for libraries to implement their own non-standard stream interfaces. The Node.js ecosystem has settled down in this regard as of Node.js v14, and this has allowed hapi to remove code catering to non-standard implementations.
Checklist:
Identify any route handlers that respond with a stream. Lifecycle handlers that takeover the response with a stream are also affected.
Ensure that the stream object used for the response is an instance of Node.js's Stream.Readable.
You may check this by exercising the route. If the response is not a stream, the route will serve a 500 response, and the internal message will read: Cannot reply with a stream-like object that is not an instance of Stream.Readable. If the server serves the stream successfully, then you do not need to make any changes.
In any cases where you are responding with a stream that is not a Stream.Readable, determine the source of the non-standard stream implementation that is being used. For example, the stream may come from an outdated version of a dependency that works with an old version of streams.
If you can upgrade the dependency producing the non-standard stream implementation, consider doing so.
As a last resort, often you can convert a non-standard readable stream into a Stream.Readable by piping it through Node.js's Stream.PassThrough:
The server.events interface comes from the podium package, which has been upgrade to v5 with some breaking changes. Notably, async usage of emit() should be replaced with await gauge().
Checklist:
Identify any asynchronous usage of emit, e.g. await server.events.emit(), and replace it with the new gauge method: await server.events.gauge(). Synchronous usage of emit, e.g. server.events.emit(), does not need to be updated.
Any usage of server.events.registerPodium() should be removed. The events of any podiums registered to server.events should instead be registered directly to server.events using server.event().
Server lifecycle no longer waits on 'start' and 'stop' event handlers
When calling await server.start() and await server.stop(), hapi previously waited for all asynchronous event handlers for 'start' and 'stop' to complete. These event handlers no longer delay server start and stop.
Checklist:
Identify any asynchronous event handlers used with server.events.on('start', ...) and server.events.on('stop', ...).
If await server.start() should wait on any of the async 'start' event handlers, switch the event handler to a server lifecycle extension via server.ext('onPreStart', ...) or server.ext('onPostStart', ...).
If await server.stop() should wait on any of the async 'stop' event handlers, switch the event handler to a server lifecycle extension via server.ext('onPreStop', ...) or server.ext('onPostStop', ...).
Change default x-xss-protection header to 0
The route.options.security.xss option governs the x-xss-protection security header. In the past this option has been true or false, defaulting to true which resulted in a header value of 1; mode=block. This is no longer standard practice, and is recommended against by OWASP. The option now has been expanded to take values 'disable', 'enable', or false. When set to the default value of 'disable', it results in a header value of 0.
Checklist:
To restore the previous default behavior, you may set route.options.security.xss to 'enable'.
If you explicitly set route.options.security.xss to true, instead set it to 'disable' or 'enable'. It's recommended to switch to the new default of 'disable'.
Default status code for CORS preflight requests has changed
In past versions of hapi, successful CORS preflight requests have been served with the status code determined by route.options.response.emptyStatusCode, which is 204 by default. Despite the fact that these responses are indeed empty, it turns out to be compatible with a wider variety of clients to serve a 200, so in the this version of hapi we have switched the status code of CORS preflights to 200.
Checklist:
To restore the previous default behavior, you may set the new option route.options.cors.preflightStatusCode to 200.
Custom response varieties ignore return value of prepare()
If you create a custom response variety using request.generateResponse(), it has been an implicit requirement that prepare(response) return response. If you returned a different response object, then response would be orphaned and it would not be closed, resulting in potential resource leaks. If you returned nothing, it would lead to a runtime error. In order to avoid this, hapi now ignores the return value of prepare().
Checklist:
If you use request.generateResponse() with prepare(), then it's suggested that your implementation of prepare() no longer return any value. Instead, simply mutate the response passed into the method.
Summary
hapi v21.0.0 is a medium-sized release focused on modernization and miscellaneous API improvements. All modules in the hapi.js ecosystem have been updated to officially support Node.js v18, be compatible with ESM projects, and drop support for Node.js v12. Plugins in the hapi.js ecosystem now support hapi v20+.
Breaking Changes
route.options.jsonpoption (Remove JSONP support #4271, Remove JSONP support #4270).Stream.Readableinterfaces for stream responses (Only support native ReadableStream streams #4293, Enforce that response streams are always Stream.Readable #4349).prepare()method provided torequest.generateResponse()(Ignore return value from generateResponse() prepare method #4348, Remove return value from generateResponse() prepare method #4299).204to200(Change default CORS preflight status code w/ configuration #4351, Proposal to add route option for CORS preflight status code #4165).x-xss-protectionheader default to'0'when security headers are enabled (Change XSS header default to '0' #4352, x-xss-protection should default to 0 (not 1; mode=block) #4327).'::'if IPv6 is available, otherwise'0.0.0.0'), inheriting from Node.js defaults (Change default host to be IPv6-friendly #4357).'start'and'stop'event listeners to complete asynchronously during server start and stop (Inconsistent event emits #4184, Upgrade production deps for hapi v21 and node v18 #4361).server.events) have been updated in line with changes in podium v5, notably the removal ofregisterPodium(), no longer waiting for async listeners onemit(), and the addition ofgauge()to wait for async listeners (Upgrade production deps for hapi v21 and node v18 #4361, Inconsistent event emits #4184).Ecosystem-wide
Enginerather than as the default export, in order to work well for ESM users. E.g.require('catbox-redis')becomesrequire('catbox-redis').Engine.New Features
failActionfunction (Make default error available in validation failAction #4350, failAction: detailedError to log, defaultError to response #4040, failAction: detailedError to log, defaultError to response #4229).emptyStatusCodeusingroute.options.cors.preflightStatusCode(Change default CORS preflight status code w/ configuration #4351, Proposal to add route option for CORS preflight status code #4165).server.load(Add documentation for server.options.load.maxEventLoopUtilization #4387, Add event loop utilization support heavy#48).Ecosystem-wide
Bug fixes
prepare()method provided torequest.generateResponse(), causingclose()not to be called, is no longer possible (Ignore return value from generateResponse() prepare method #4348, Remove return value from generateResponse() prepare method #4299).Migration Checklist
TLDR
While a more extensive migration guide exists below, the vast majority of hapi users should be looking for the following:
Engine. E.g.require('@hapi/catbox-redis')becomesrequire('@hapi/catbox-redis').Engine.route.options.jsonp, remove this option and instead setroute.options.corstotrue. You should update any clients that utilize JSONP to instead make a regular AJAX/fetch()requests.Stream.Readable. You can easily check by exercising the route, and seeing if hapi serves a 500 with a message indicating there's an issue. A common fix is to pipe your non-standard stream through Node.js'sStream.PassThrough, e.g.nonStandardStream.pipe(new Stream.PassThrough()).emit()asynchronously, e.g.await server.events.emit(), then switch it to use the newgauge()method, e.g.await server.events.gauge()with the same arguments.'start'or'stop'events that are asynchronous, switch these event listeners to a server lifecycle extension,server.ext('onPreStart', ...)orserver.ext('onPostStop', ...)respectively.Node.js version
Make sure your Node.js version is v14 LTS or newer. This release uses language features that are only available in Node.js v14.15.0 or higher and will not start on older versions. Node.js v18 is the current LTS and is the recommended version.
Default server host aligned with Node.js defaults
When creating a server with
Hapi.server(), if you do not specifyserver.options.hostthen hapi used to default to0.0.0.0, i.e. all available IPv4 network interfaces. This did not jibe with IPv6 or dual-stack hosts, particularly since as of Node.js v17, the dns module would resolve a request tolocalhostto IPv6 network interfaces when available. So hapi now defaultsserver.options.hostto::1on IPv6-compatible machines, and otherwise to0.0.0.0, in alignment with Node.js's bare HTTP server.Checklist:
server.options.hostto0.0.0.0, e.g.Hapi.server({ host: '0.0.0.0' }).Catbox engines are now exported as
EngineIn order to be more ESM-friendly, the catbox engines (catbox-memory, catbox-redis, catbox-memcached, catbox-object) now export the engine as a property
Enginerather than as the default export. There have also been changes to catbox-memcached's engine options.Checklist:
Engineexport. For example:const CatboxRedis = require('@hapi/catbox-redis'); -const engine = new CatboxRedis(); +const engine = new CatboxRedis.Engine(); const server = Hapi.server({ cache: { engine } });locationoption has been renamed toserver, and it takes a new format following from memcache-client's configuration.Remove support for JSONP
JSONP (or, "JSON with Padding") was an approach to circumvent the browser same-origin policy when making AJAX requests for data. With the advent and broad client support for CORS, JSONP no longer belongs in hapi core, so the
route.options.jsonpoption has been removed.Checklist:
route.options.jsonpand remove the option.route.options.jsonpwithroute.options.cors. Settingroute.options.corstotrueon a route will allow cross-origin requests from any domain, similar to JSONP. You would followup by updating clients to use a standard AJAX request rather than the JSONP approach of appending a script tag to the page.Support only standard
Stream.Readableimplementations for responsesOver Node.js's lifetime, we have seen several implementations of streams, and it used to be more common for libraries to implement their own non-standard stream interfaces. The Node.js ecosystem has settled down in this regard as of Node.js v14, and this has allowed hapi to remove code catering to non-standard implementations.
Checklist:
Stream.Readable.Cannot reply with a stream-like object that is not an instance of Stream.Readable. If the server serves the stream successfully, then you do not need to make any changes.Stream.Readable, determine the source of the non-standard stream implementation that is being used. For example, the stream may come from an outdated version of a dependency that works with an old version of streams.Stream.Readableby piping it through Node.js'sStream.PassThrough:Updated
server.eventsinterfaceThe
server.eventsinterface comes from the podium package, which has been upgrade to v5 with some breaking changes. Notably, async usage ofemit()should be replaced withawait gauge().Checklist:
await server.events.emit(), and replace it with the new gauge method:await server.events.gauge(). Synchronous usage of emit, e.g.server.events.emit(), does not need to be updated.server.events.registerPodium()should be removed. The events of any podiums registered toserver.eventsshould instead be registered directly toserver.eventsusingserver.event().Server lifecycle no longer waits on
'start'and'stop'event handlersWhen calling
await server.start()andawait server.stop(), hapi previously waited for all asynchronous event handlers for'start'and'stop'to complete. These event handlers no longer delay server start and stop.Checklist:
server.events.on('start', ...)andserver.events.on('stop', ...).await server.start()should wait on any of the async'start'event handlers, switch the event handler to a server lifecycle extension viaserver.ext('onPreStart', ...)orserver.ext('onPostStart', ...).await server.stop()should wait on any of the async'stop'event handlers, switch the event handler to a server lifecycle extension viaserver.ext('onPreStop', ...)orserver.ext('onPostStop', ...).Change default
x-xss-protectionheader to0The
route.options.security.xssoption governs thex-xss-protectionsecurity header. In the past this option has beentrueorfalse, defaulting totruewhich resulted in a header value of1; mode=block. This is no longer standard practice, and is recommended against by OWASP. The option now has been expanded to take values'disable','enable', orfalse. When set to the default value of'disable', it results in a header value of0.Checklist:
route.options.security.xssto'enable'.route.options.security.xsstotrue, instead set it to'disable'or'enable'. It's recommended to switch to the new default of'disable'.Default status code for CORS preflight requests has changed
In past versions of hapi, successful CORS preflight requests have been served with the status code determined by
route.options.response.emptyStatusCode, which is204by default. Despite the fact that these responses are indeed empty, it turns out to be compatible with a wider variety of clients to serve a200, so in the this version of hapi we have switched the status code of CORS preflights to200.Checklist:
route.options.cors.preflightStatusCodeto200.Custom response varieties ignore return value of
prepare()If you create a custom response variety using
request.generateResponse(), it has been an implicit requirement thatprepare(response)returnresponse. If you returned a different response object, thenresponsewould be orphaned and it would not be closed, resulting in potential resource leaks. If you returned nothing, it would lead to a runtime error. In order to avoid this, hapi now ignores the return value ofprepare().Checklist:
request.generateResponse()withprepare(), then it's suggested that your implementation ofprepare()no longer return any value. Instead, simply mutate theresponsepassed into the method.