Skip to content

Route config is lost when reply.callNotFound() is called #4025

@markwainwright

Description

@markwainwright

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.0.3

Plugin version

No response

Node.js version

16.15.1

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

12.3.1

Description

After reply.callNotFound() has been called, request.context.config no longer contains the config object that the route was created with, and request.routerMethod and request.routerPath are undefined.

Since the context.config object seems to be commonly referred to as "route config", not "handler config", this is not what I expected. I expected that the config that the route was created with would be available regardless of whether the request was ultimately routed to the not found handler.

This happens because reply.callNotFound() internally sets the request's context property to the fourOhFour context, which contains an independent config.

reply.request.context = reply.context[kFourOhFourContext]

Our use case: we set a unique routeId property in the config of each route that we then retrieve in an onResponse handler to send a metric to our monitoring system – allowing us to monitor the usage and performance of each route separately. See steps to reproduce below. However if reply.callNotFound() was called by the handler, this value is undefined.


I'd like to hear opinions about whether this should be considered a bug or expected behaviour.

  • If it's considered a bug, I'm guessing that fixing this in-place would be considered a breaking change. Is that correct? If so I'd like to propose instead fixing it by adding the route config as a new property on request and reply (e.g. request.routerConfig) that doesn't change when reply.callNotFound() is called. This would be nice because it would allow us to remove request.context and reply.context from the public API in a future major version (they have the disclaimer "A Fastify internal object. You should not use it directly or modify it" in the docs).
  • If it's considered to be expected behaviour, then I think the docs should make this clear.

My preference is obviously for the former, but in either case I'd be happy to make a PR if it would be welcomed.

Steps to Reproduce

const fastify = require('fastify')()

fastify.addHook('onResponse', (request, reply, done) => {
  console.log('onResponse routeId=', request.context.config.routeId)
  console.log('onResponse routerPath=', request.routerPath)
  console.log('onResponse routerMethod=', request.routerMethod)
  done()
})

fastify.setNotFoundHandler((request, reply) => {
  console.log('notFoundHandler routeId=', request.context.config.routeId)
  console.log('notFoundHandler routerPath=', request.routerPath)
  console.log('notFoundHandler routerMethod=', request.routerMethod)
  reply.status(404).send()
})

fastify.route({
  method: 'GET',
  url: '/',
  config: { routeId: 'myRoute' },
  handler: (request, reply) => {
    console.log('handler routeId=', request.context.config.routeId)
    console.log('handler routerPath=', request.routerPath)
    console.log('handler routerMethod=', request.routerMethod)
    reply.callNotFound()
  }
})

fastify.listen({ port: 3000 })
curl localhost:3000

Expected Behavior

I would expect the router config to be available in the not found handler and in hooks, i.e.

handler routeId= myRoute
handler routerPath= /
handler routerMethod= GET
notFoundHandler routeId= myRoute
notFoundHandler routerPath= /
notFoundHandler routerMethod= GET
onResponse routeId= myRoute
onResponse routerPath= /
onResponse routerMethod= GET

Instead, it logs:

handler routeId= myRoute
handler routerPath= /
handler routerMethod= GET
notFoundHandler routeId= undefined
notFoundHandler routerPath= undefined
notFoundHandler routerMethod= undefined
onResponse routeId= undefined
onResponse routerPath= undefined
onResponse routerMethod= undefined

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationgood first issueGood for newcomers

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions