@hapi/hapi
>=21.0.0 <22.0.0postconditions16functions8last verified2026-06-24coverage score100%Postconditions: what we check
- route · route-handler-errorerrorWhenroute handler throws error or promise rejects (database error, validation error)Throws
Error causing 500 Internal Server Error response if not handled - this will crash the application.Required handlingRoute handlers MUST handle errors with try-catch or return error via h.response().code() to prevent server crash and provide proper error responses. Use h.response(error).code(statusCode) for controlled error responses.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - start · start-errorerrorWhenserver fails to start (port in use, invalid configuration, plugin error)Throws
Error with startup failure detailsRequired handlingCaller MUST wrap server.start() in try-catch to handle startup errors. Port conflicts, plugin failures, and configuration errors crash application if unhandled.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - initialize · initialize-plugin-not-registerederrorWhencalled before async plugin registration callbacks have completedThrows
Error: 'Cannot start server before plugins finished registration'Required handlingCaller MUST await all server.register() calls to completion before calling server.initialize(). Calling initialize() during concurrent plugin registration throws synchronously and leaves server in an unusable state.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - initialize · initialize-invalid-phaseerrorWhencalled when server is not in 'stopped' phase (already initializing, starting, or started)Throws
Error: 'Cannot initialize server while it is in <phase> phase'Required handlingCall server.initialize() only once from the 'stopped' phase. Repeated initialization is a no-op if already 'initialized', but throws if in any other active phase. Wrap in try-catch to handle race conditions in concurrent startup code.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - initialize · initialize-cache-start-errorerrorWhencache engine fails to connect or start (Redis unavailable, Memcached unreachable)Throws
Error from catbox cache client — connection refused, authentication failure, or timeoutRequired handlingWrap server.initialize() in try-catch. Cache connection failures during initialization set the server phase to 'invalid', meaning the server CANNOT be recovered without process restart. Log the error and exit the process.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilitysilent - stop · stop-invalid-phaseerrorWhencalled while server is in 'stopping', 'starting', or 'initializing' phaseThrows
Error: 'Cannot stop server while in <phase> phase'Required handlingAvoid calling stop() concurrently or during active phase transitions. In graceful shutdown handlers (SIGTERM), check server state before calling stop(). Wrap in try-catch to handle phase errors in signal handlers — an unhandled rejection in a shutdown hook terminates the process ungracefully.costlowin prodimmediate exceptionusers seeservice unavailablevisibilitysilentSources[3] - stop · stop-lifecycle-hook-errorerrorWhenonPreStop or onPostStop extension method throws or rejectsThrows
Error from the lifecycle method — sets server phase to 'invalid'Required handlingExtension methods registered with server.ext('onPreStop') or server.ext('onPostStop') MUST handle their own errors internally. An error thrown from a stop lifecycle hook sets the server phase to 'invalid', preventing future start/stop cycles and leaving the process in an undefined state. Always wrap database disconnection, cache flush, and external cleanup calls in try-catch inside these hooks.costlowin prodimmediate exceptionusers seeservice unavailablevisibilitysilentSources[3] - register · register-duplicate-pluginerrorWhensame plugin registered twice without once: true optionThrows
AssertionError: 'Plugin <name> already registered'Required handlingAlways use once: true when there is any chance of duplicate plugin registration (e.g. in tests that share a server instance, or in plugin dependency chains where multiple plugins register the same utility plugin). Without once: true, the second registration throws and aborts the entire startup sequence.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - register · register-version-requirement-not-meterrorWhenplugin specifies hapi or node version requirements that the current runtime does not satisfyThrows
AssertionError: 'Plugin <name> requires hapi version <x> but found <y>'Required handlingWrap server.register() in try-catch and handle version mismatch errors at startup. This error is not recoverable at runtime — the process must be restarted with a compatible hapi or Node.js version. Log the error clearly before exiting.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[5] - register · register-plugin-init-errorerrorWhenplugin's register() function throws an error or returns a rejected promiseThrows
Error from plugin initialization — database connection, missing config, authentication setup failureRequired handlingWrap server.register() in try-catch. Plugin initialization errors (failed DB connections, missing environment variables, invalid config) propagate directly from the plugin's register() function. The server cannot start if any plugin registration fails. Ensure all plugins validate their configuration before performing async operations in their register() function.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - auth.test · auth-test-unknown-strategyerrorWhenstrategy name passed to auth.test() is not registered on the serverThrows
AssertionError: 'Unknown authentication strategy: <name>'Required handlingValidate strategy names at startup rather than at request time. Wrap auth.test() calls in try-catch when the strategy name is dynamic (e.g. from config or request parameters). An unknown strategy throws synchronously inside the async function, causing an unhandled rejection if not caught.costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[7] - auth.test · auth-test-authentication-failureerrorWhenthe strategy's authenticate() method returns an unauthenticated result (invalid token, missing credentials)Throws
Boom.unauthorized error with the authentication challenge detailsRequired handlingWrap server.auth.test() in try-catch. The method throws the full Boom error from the authentication strategy, not a simple boolean. The error contains statusCode, message, and WWW-Authenticate headers. Callers must catch and handle this error to return appropriate responses rather than letting it propagate as an unhandled 500.costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[7] - auth.verify · auth-verify-credentials-invaliderrorWhenstrategy's verify() method determines credentials have been revoked, expired, or invalidatedThrows
Error from the strategy's verify() implementation — typically Boom.unauthorizedRequired handlingWrap server.auth.verify() in try-catch in any handler that re-validates credentials after initial authentication (e.g. WebSocket message handlers, long-polling endpoints). Failure to catch this error leaves the connection open with invalid credentials and causes an unhandled rejection that may crash the process or silently fail depending on the error handler setup.costlowin prodimmediate exceptionusers seeservice unavailablevisibilitysilentSources[7] - inject · inject-deprecated-credentials-optionerrorWhenoptions.credentials passed to server.inject() — removed in hapi 17+, replaced by options.authThrows
AssertionError: 'options.credentials no longer supported (use options.auth)'Required handlingMigrate to options.auth = { strategy: <name>, credentials: <obj> } before calling inject(). The legacy options.credentials shape throws synchronously inside inject's async body, producing an unhandled rejection if the test/integration code does not await + try-catch. A common failure mode is older test suites copied from hapi 16 code that never get updated — every call site silently breaks at runtime, not at type-check time.costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[5] - inject · inject-auth-options-malformederrorWhenoptions.auth provided but missing required shape (not an object, or missing .credentials, or missing .strategy)Throws
AssertionError: 'options.auth must be an object' / 'options.auth.credentials is missing' / 'options.auth.strategy is missing'Required handlingWrap server.inject() in try-catch when options.auth is constructed dynamically (e.g. from a test factory or a stored mock credentials object). All three asserts fire BEFORE any request lifecycle code runs, so the failure is purely a config shape issue — but it produces a synchronous throw inside the async function that becomes an unhandled promise rejection if uncaught. Validate the auth object shape at the call site, or normalize through a typed helper that asserts the shape before passing to inject().costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[5] - inject · inject-handler-throws-boom-errorerrorWhenroute handler throws (or returns a rejected promise) during the simulated request, OR request processing fails inside hapi internals (validation, auth, pre-handlers)Throws
Boom error (the partial ServerInjectResponse is exposed on .data per official docs). Custom handler errors are rethrown via `throw custom.error` in lib/server.js line 351-352.Required handlingWrap server.inject() in try-catch in test and adapter code paths. Unlike the live HTTP server (which sends a 500 response to the client), inject() rethrows the handler's error to the caller. Serverless adapters (Lambda, Cloud Functions) that translate cloud-event payloads through inject() will crash the function invocation and trigger a retry storm if the underlying handler error is not caught and translated into a domain-appropriate response. Tests that don't catch this end up as unhandled promise rejections that crash the test runner instead of failing the specific assertion. The error.data property carries the partial response (status code reached, headers set) — useful for diagnostic logging.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
Sources
Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.
- [3]github.com/hapijs/hapi/blobhapijs/hapi · core.js
- [4]github.com/hapijs/catboxhapijs/catbox
- [5]github.com/hapijs/hapi/blobhapijs/hapi · server.js
- [7]github.com/hapijs/hapi/blobhapijs/hapi · auth.js
- [8]github.com/hapijs/hapi/blobhapijs/hapi · API.md
Research notes
Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.
Sources: @hapi/hapi
Package: @hapi/hapi
Version: 21.x
Category: framework (Web framework)
Status: ✅ Complete
Official Documentation
- Main Docs: https://hapi.dev/
- API: https://hapi.dev/api/?v=21.4.6
- Routes: https://hapi.dev/api/?v=21.4.6#-serverrouteroute
- Extensions: https://hapi.dev/api/?v=21.4.6#-serverextoptions
- npm: https://www.npmjs.com/package/@hapi/hapi
Behavioral Requirements
Route Handler Errors: Unhandled errors crash server Server Start Errors: Port conflicts, configuration errors Must wrap server.start() in try-catch Route handlers should handle errors and return proper responses Use onPreResponse extension for centralized error handling
Contract Rationale
Route errors crash server: Unhandled exceptions stop process Server startup can fail: Port in use, invalid config Centralized error handling ensures consistency
Created: 2026-02-26 Status: ✅ COMPLETE