Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript Build a REST API With Express Completing and Testing the API Test Routes with Postman

On unimplemented methods, isn't response 405 Method Not Allowed more appropriate?

According to HTTP/1.1 specs

The 405 (Method Not Allowed) status code indicates that the method received in the request-line is known by the origin server but not supported by the target resource. The origin server MUST generate an Allow header field in a 405 response containing a list of the target resource's currently supported methods.

Yet in those situations, our app returns 404 Not Found.

What would be the best way to implement 405 responses? In our app, I tried this approach:

  • in routes.js, define

    function notAllowedHandler(req, res, next) {
        const methods = Object.keys(req.route.methods)
                        .filter((value) => value !== '_all')
                        .map((value) => value.toUpperCase())
            , err = new Error('Method Not Allowed')
        err.status = 405
        res.set('Allow', methods.join(', '))
        return next(err)
    }
    
  • for each URL, add this as the last handler for all methods

    router
    .route(url)
    .METHOD1(callback1)
    ā‹®
    .METHODn(callbackn)
    .all(notAllowedHandler)
    

Is there a better way?

2 Answers

jobbol
seal-mask
.a{fill-rule:evenodd;}techdegree
jobbol
Full Stack JavaScript Techdegree Student 16,610 Points

Yes there is a better way. Express is a series of middleware functions that get called one after the other and loop. Middleware functions in Express look like this.

app.use(bodyParser.json());
app.use(methodOverride());
app.use(logErrors);
app.use(clientErrorHandler);
app.use(errorHandler);

Order matters here as they get loaded into memory in the same order you call them. So errors must be done last. Middleware error functions use the similar req, res, next parameters but start with an err.

All middleware functions must end with a call to the next middleware function via next(err) on the stack, or it must already be finished with the response. Otherwise Express gets stuck and the loop is broken.

If at any time during this loop next is called with a value, Express will jump to those error handling middleware functions. It knows them because it looks for them with the four parameters.

Putting this all together you have

app.use(error405);
function Error405(err, req, res, next) {
   if (err === 405 && req.xhr) {
      return res.status(405).send('"Method not allowed."');
   }
    next(err);
}

In order for this to work, you need to pass 405 to the next function from inside a route via your routes.js.

next(405);

Express will see sort this as an error, jump to the error middleware. This one needs to be on the top of the stack. It checks for the 405 number and sends the custom message. Any other errors will fall through the stack and be passed onto the next handler.

Further reading here..

Still need help? Post your code and I'll help hack it together.

Thanks for the thoughtful answer. This would be fine except for some minor issues and the stipulation in the HTTP spec, namely

The origin server MUST generate an Allow header field in a 405 response containing a list of the target resource's currently supported methods.

We need the response to list methods the resource at the URL (target resource) does support. Though the code I wrote does that, I don't see where yours does.

FWIW, the app already implements custom error handlers in app.js as

app.use((req, res, next) => {
    const err = new Error('Not Found')
    err.status = 404
    next(err)
})
app.use((err, req, res, next) => {
    res.status(err.status || 500)
    res.json({error: err.message})
})

and passing the status code through err.status. Any suggestions?

jobbol
seal-mask
.a{fill-rule:evenodd;}techdegree
jobbol
Full Stack JavaScript Techdegree Student 16,610 Points

At first I did not understand your question, but - ohh - it's finally clicking. I didn't realize what that long line of code did. The whole thing sorta self documents itself then it says what's not allowed. I also see now that you already had the whole idea about the middleware stack down. I feel bad for writing that long response as well since it seems trivial in comparison to what you have.

So for this part,

.all(notAllowedHandler)

Attaching that to the end of each router.

I think I found a project which addresses that same exact problem.

Now we have a working API, but there is something bothering me. When we try to see data about a model that doesn't exist, like /models/200, we should get a 404 error, but we don't. We get an empty object. That's not cool with me. Let's fix this. Instead of implementing this in every single route handler we have, let's create some middleware that will do it for us.

This is beyond my knowledge of Express, as routers are new to me. I'll get to it, as I'm working on some back-end API stuff. But hopefully this will help you.

No worries. Thanks for the link: I'll check that out.

We can both agree my approach is not elegant, since it means repeating the same code at the end of each URL's route. The only reason I do that is to scrape the method names defined for the route (the keys of the req.route.methods object other than _all), so I can report these as "the target resource's currently supported methods".

To do the same from middleware, we'd need to look up the defined methods for the requested URL's route(s). Though I think you understand, I should save others confusion by clarifying the goal

look up defined methods for the requested URL's route(s) from middleware