fastify
semver
>=5.0.0 <6.0.0postconditions23functions10last verified2026-06-18coverage score91%Postconditions — what we check
- get · route-handler-timeoutinfoWhenroute handler does not send a response within handlerTimeout milliseconds (when handlerTimeout > 0 is configured at server or route level)Throws
FST_ERR_HANDLER_TIMEOUT — 503 Service Unavailable sent through error handler. Handler async work continues running unless request.signal is monitored.Required handlingUse request.signal for cooperative cancellation: pass it to fetch(), database queries, and stream.pipeline() so async work stops when the timeout fires. Override FST_ERR_HANDLER_TIMEOUT in setErrorHandler or route-level errorHandler to return 504 instead of 503 for downstream timeouts. Without signal monitoring, background work leaks resources after the client receives the 503.costmediumin proddegraded serviceusers seeservice unavailablevisibilityvisible - listen · listen-port-in-useerrorWhenport is already bound by another process (listen() called on occupied port)Throws
Error with code EADDRINUSE — Node.js system errorRequired handlingAwait listen() in try-catch. On EADDRINUSE, either use a different port or check that previous server instances were properly closed before restarting. Port 0 picks a random available port as an alternative.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - listen · listen-plugin-timeouterrorWhenregistered plugins do not call done() / resolve their promises within pluginTimeout ms (default 10000ms) before listen() completes initializationThrows
Error with code ERR_AVVIO_PLUGIN_TIMEOUT — thrown by fastify's avvio plugin systemRequired handlingAwait listen() in try-catch. Ensure all plugins call their done callback or resolve async. Set pluginTimeout option higher if plugins legitimately take longer (e.g., database connection pools). Log error and exit process cleanly on failure.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - listen · listen-container-bindingwarningWhenserver needs to be reachable from outside its container/VM but listen() is called with '127.0.0.1' (localhost-only)Throws
No error thrown — server starts successfully but is unreachable externally; requests silently time outRequired handlingIn containerized environments (Docker, Kubernetes, AWS ECS), call listen({ port: 3000, host: '0.0.0.0' }) to bind all interfaces. '127.0.0.1' is correct only for development where external access is not needed.costhighin prodsilent failureusers seeservice unavailablevisibilitysilent - close · close-websocket-connection-leakerrorWhenserver has active WebSocket or HTTP upgrade connections when close() is calledThrows
close() hangs indefinitely — upgraded connections are not tracked by fastify's connection counter and prevent shutdown from completingRequired handlingTrack WebSocket connections manually. In an 'onClose' hook or preClose handler, explicitly call ws.terminate() on all active WebSocket clients before awaiting close(). Without this, process.exit() may be required to force shutdown, risking data loss.costmediumin proddegraded serviceusers seeservice unavailablevisibilitysilent - close · close-hook-errorwarningWhenan onClose or preClose lifecycle hook throws or rejects during server shutdownThrows
Error from the hook propagates as close() promise rejectionRequired handlingAwait close() in try-catch. Log shutdown errors but still proceed with cleanup. Use finally block to ensure any remaining cleanup code runs even if a hook fails.costmediumin proddegraded serviceusers seeservice unavailablevisibilitysilentSources[7] - ready · ready-plugin-timeouterrorWhena registered plugin does not call done() or resolve its async function within pluginTimeout ms (default 10000ms)Throws
Error with code ERR_AVVIO_PLUGIN_TIMEOUT — thrown by avvio, fastify's plugin loading systemRequired handlingAwait ready() in try-catch. Identify the slow plugin by checking the error message which includes the plugin name. Fix the plugin to complete initialization or increase pluginTimeout in fastify options. Log the error with full stack trace for debugging.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - ready · ready-plugin-init-errorerrorWhena plugin's async initializer rejects with an error (e.g., database connection fails during plugin setup)Throws
The plugin's error propagates through ready() promise rejectionRequired handlingAwait ready() in try-catch. Handle initialization failures explicitly — log details, emit alerts, and exit process with non-zero code so process managers (systemd, k8s, Docker) restart the service.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - register · register-errors-deferred-to-readyinfoWhena plugin's async initializer throws or rejects (database connection, external service registration failure, missing configuration)Throws
Error is NOT thrown at register() call time — it defers and surfaces when ready() or listen() is awaitedRequired handlingAlways await ready() or listen() in try-catch after all register() calls. Do not assume that a synchronous register() call means the plugin loaded successfully. Plugin initialization errors will only surface asynchronously.costhighin proddelayed failureusers seeservice unavailablevisibilityvisible - register · register-plugin-timeoutinfoWhenplugin async initializer takes longer than pluginTimeout (default 10000ms) to call done() or resolveThrows
Error with code ERR_AVVIO_PLUGIN_TIMEOUT — surfaces at ready() or listen(), not at register()Required handlingUse fp-options or fastify-plugin options to set a per-plugin timeout, or increase global pluginTimeout. Ensure async plugin inits complete promptly. Log plugin name from error message to identify the culprit.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - after · after-error-parameter-uncheckedwarningWhenthe previous register() call's plugin threw an error during initialization and after() callback ignores the err parameterThrows
Plugin initialization error is silently swallowed — no exception propagates, but the plugin is not loadedRequired handlingAlways check the err parameter in after() callback: app.after((err) => { if (err) throw err; }). Re-throwing ensures the error propagates to ready()/listen() where it can be caught.costmediumin prodsilent failureusers seedegraded performancevisibilitysilent - addHook · addhook-async-hook-no-try-catchwarningWhenasync lifecycle hook (onRequest, preParsing, preValidation, preHandler, onSend, preSerialization) throws or rejects without try-catchThrows
Error triggers request error handling chain — passed to setErrorHandler if configured, otherwise returns 500 Internal Server ErrorRequired handlingEITHER configure setErrorHandler at the server (Fastify routes hook throws through it, formatting the response), OR wrap async hook body in try-catch and return a meaningful response via reply.code(status).send(error). The first path is preferred for projects with cross-cutting error handling.costlowin proddegraded serviceusers seeservice unavailablevisibilityvisibleSources[11] - addHook · addhook-onclose-async-unhandledwarningWhenasync onClose hook throws or rejects, and close() is called without try-catch (e.g., in a SIGTERM handler)Throws
close() promise rejects — if not caught, becomes an unhandled rejection; database connection pool may not be releasedRequired handlingAwait close() in try-catch in SIGTERM/SIGINT handlers. Wrap onClose hook body in try-catch and log errors. Use finally block to ensure cleanup code runs even if a hook fails.costmediumin prodsilent failureusers seedegraded performancevisibilitysilentSources[7] - setErrorHandler · seterrorhandler-called-after-starterrorWhensetErrorHandler() is called after fastify.listen() or fastify.ready() has been awaited (server already started)Throws
Error: Cannot call "setErrorHandler" when fastify instance is already started!Required handlingCall setErrorHandler() during server setup, before awaiting listen() or ready(). In plugins, setErrorHandler() is always safe to call since plugins execute before server starts.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - setErrorHandler · seterrorhandler-not-functionerrorWhensetErrorHandler() is called with a non-function argument (undefined, null, an object)Throws
FST_ERR_ERROR_HANDLER_NOT_FN — TypeError: Error Handler must be a functionRequired handlingPass a function to setErrorHandler(). This is a programming error that surfaces at startup time, not in production traffic.costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - setErrorHandler · seterrorhandler-already-setwarningWhensetErrorHandler() is called twice in the same plugin scope without allowErrorHandlerOverride: trueThrows
FST_ERR_ERROR_HANDLER_ALREADY_SET — TypeError thrown synchronouslyRequired handlingSet allowErrorHandlerOverride: true in fastify() options when you need to replace an existing error handler (e.g., during testing or plugin composition). Each encapsulated plugin scope can have its own error handler without this issue.costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - setErrorHandler · seterrorhandler-handler-itself-throwswarningWhenthe registered error handler function itself throws synchronously or its Promise rejectsThrows
Error propagates to the parent plugin scope's error handler. If no parent handler exists, the root default handler handles it. A completely broken error handler chain causes an unformatted 500 response from the fallback handler.Required handlingWrap error handler body in try-catch. Log errors before sending the response. If the error handler throws, Fastify will NOT retry with the same handler — it falls back to the parent scope or root default handler, which may return a different error format than expected by clients.costmediumin proddegraded serviceusers seedegraded performancevisibilityvisible - addContentTypeParser · addcontenttype-body-too-largewarningWhenrequest body size exceeds the bodyLimit configured in fastify options (default 1MB) or per-route bodyLimitThrows
FST_ERR_CTP_BODY_TOO_LARGE — 413 Request Entity Too Large. Thrown before the custom parser is called.Required handlingHandle FST_ERR_CTP_BODY_TOO_LARGE in setErrorHandler to return a user-friendly 413 message. Increase bodyLimit per-route for file upload routes. Do not increase globally as it expands the attack surface for DoS.costlowin prodimmediate exceptionusers seedegraded performancevisibilityvisible - addContentTypeParser · addcontenttype-invalid-media-typewarningWhenclient sends a content-type for which no parser is registered (neither default JSON/text parser nor a custom addContentTypeParser)Throws
FST_ERR_CTP_INVALID_MEDIA_TYPE — 415 Unsupported Media Type. Returned before any route handler is called.Required handlingRegister parsers for all content-types your API accepts. Add a catch-all parser with '*' or 'application/*' for APIs accepting arbitrary binary payloads. Handle FST_ERR_CTP_INVALID_MEDIA_TYPE in setErrorHandler to return consistent error format.costlowin prodimmediate exceptionusers seedegraded performancevisibilityvisibleSources[14] - addContentTypeParser · addcontenttype-async-parser-rejectswarningWhenan async custom parser rejects or throws (invalid encoding, decompression error, schema mismatch)Throws
The rejection error propagates to setErrorHandler with the same status code the parser set, or 500 if none was set. The raw error object is passed as-is.Required handlingWrap async parser body in try-catch and set err.statusCode before throwing to control the HTTP response code. Pass errors to done(err) for callback-based parsers. Without explicit status codes, parsing errors return 500 instead of the semantically correct 400.costlowin prodimmediate exceptionusers seedegraded performancevisibilityvisibleSources[14] - setNotFoundHandler · setnotfoundhandler-called-after-starterrorWhensetNotFoundHandler() is called after fastify.listen() or fastify.ready() has been awaited (server already started)Throws
Error: Cannot call "setNotFoundHandler" when fastify instance is already started!Required handlingCall setNotFoundHandler() during server setup, before awaiting listen() or ready(). In plugins, setNotFoundHandler() is always safe to call since plugins execute before server starts. Late registration is a startup-only error — visible immediately on next deploy.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - setNotFoundHandler · setnotfoundhandler-already-setwarningWhensetNotFoundHandler() is called twice for the same Fastify instance / plugin prefixThrows
Error: Not found handler already set for Fastify instance with prefix: '<prefix>' — thrown synchronouslyRequired handlingRegister the 404 handler exactly once per encapsulated scope. If you need a different 404 response under a sub-prefix, register the handler inside the corresponding plugin's register() body — the plugin's scope owns its own 404 handler independently of the root.costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - setNotFoundHandler · setnotfoundhandler-handler-throws-routes-via-error-handlerwarningWhenthe registered 404 handler function itself throws synchronously, awaits a rejected promise, or fails to send a responseThrows
Error is routed through setErrorHandler (NOT through the 404 path again) — the client receives the formatted error response from setErrorHandler, typically 500 Internal Server Error, NOT the intended 404. If no setErrorHandler is configured, returns an unformatted 500.Required handlingWrap the 404 handler body in try-catch when it does I/O (e.g., logging unknown-route attempts to a database, fetching analytics). On failure, call reply.code(404).send({ message: 'Not Found' }) explicitly so the response status reflects the real intent. Without this, debugging looks confusing — client logs show 500 from a route that should be 404.costmediumin proddegraded serviceusers seedegraded performancevisibilityvisible
Sources
Every postcondition cites at least one of these. Numbered to match the footnotes above.
- [1]fastify.dev/docs/latesthttps://fastify.dev/docs/latest/Reference/Server/
- [2]fastify.dev/docs/latesthttps://fastify.dev/docs/latest/Reference/Request/#requestsignal
- [3]nodejs.org/api/errors.htmlhttps://nodejs.org/api/errors.html#common-system-errors
- [4]github.com/fastify/avviohttps://github.com/fastify/avvio
- [5]fastify.dev/docs/latesthttps://fastify.dev/docs/latest/Guides/Getting-Started/
- [6]github.com/fastify/fastify-websockethttps://github.com/fastify/fastify-websocket
- [7]fastify.dev/docs/latesthttps://fastify.dev/docs/latest/Reference/Hooks/#onclose
- [8]github.com/fastify/avviohttps://github.com/fastify/avvio#ready
- [9]fastify.dev/docs/latesthttps://fastify.dev/docs/latest/Reference/Plugins/
- [10]github.com/fastify/avviohttps://github.com/fastify/avvio#afterfn
- [11]fastify.dev/docs/latesthttps://fastify.dev/docs/latest/Reference/Hooks/
- [12]fastify.dev/docs/latesthttps://fastify.dev/docs/latest/Reference/Server/#seterrorhandler
- [13]fastify.dev/docs/latesthttps://fastify.dev/docs/latest/Reference/Hooks/#onerror
- [14]fastify.dev/docs/latesthttps://fastify.dev/docs/latest/Reference/ContentTypeParser/
- [15]fastify.dev/docs/latesthttps://fastify.dev/docs/latest/Reference/Server/#bodylimit
- [16]fastify.dev/docs/latesthttps://fastify.dev/docs/latest/Reference/Server/#setnotfoundhandler
Need a different package?
Request a profile