Skip to content

Commit

Permalink
feat(express): custom status code for notFoundHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremyben committed Jun 1, 2022
1 parent c7c3aa5 commit 2fd6d99
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 27 deletions.
1 change: 1 addition & 0 deletions express/README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,7 @@ finalHandler({
Like the error handler, Express default route handler always sends a `text/html` response when the route is not found.

* `notFoundHandler: true` defines a default handler similar to the Express one, with a 404 status, but compatible with json.
* `notFoundHandler: <number>` defines the same default handler, with a custom status code.
* `notFoundHandler: (req, res, next) => {}` lets you define your own.

## Application class
Expand Down
4 changes: 2 additions & 2 deletions express/__tests__/final-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('final handler', () => {
return ['name', 'message']
},
log: true,
notFoundHandler: true,
notFoundHandler: 400,
})
)
class App extends Application {
Expand Down Expand Up @@ -63,7 +63,7 @@ describe('final handler', () => {
test('default not found error', async () => {
const res = await rq.get('/bar/baz')

expect(res.status).toBe(404)
expect(res.status).toBe(400)
expect(res.type).toBe('text/html')
expect(res.text).toContain('RouteNotFoundError: Cannot GET /bar/baz')
// expect(res.body).toEqual({ name: 'RouteNotFoundError', message: 'Cannot GET /bar/baz' })
Expand Down
50 changes: 25 additions & 25 deletions express/src/final-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ import * as express from 'express'
* @public
*/
export function finalHandler(options: finalHandler.Options): express.ErrorRequestHandler {
const finalErrorHandler: express.ErrorRequestHandler = (err, req, res, next) => {
const finalErrorHandler: express.ErrorRequestHandler = (error, req, res, next) => {
if (res.headersSent) {
return next(err)
return next(error)
}

// ─── Status ───

const errorStatus = getStatusFromErrorProps(err)
const errorStatus = getStatusFromErrorProps(error)

if (errorStatus) {
res.status(errorStatus)
Expand All @@ -38,17 +38,17 @@ export function finalHandler(options: finalHandler.Options): express.ErrorReques

// ─── Headers ───

if (!!err && !!err.headers && typeof err.headers === 'object') {
res.set(err.headers)
if (!!error && !!error.headers && typeof error.headers === 'object') {
res.set(error.headers)
}

// ─── Log ───

if (options.log) {
const logger = options.logger || ((errr) => setImmediate(() => console.error(errr)))
const logger = options.logger || ((err) => setImmediate(() => console.error(err)))

if (options.log === true || (options.log === '5xx' && res.statusCode >= 500)) {
logger(err)
logger(error)
}

// no need to handle false
Expand All @@ -57,36 +57,45 @@ export function finalHandler(options: finalHandler.Options): express.ErrorReques
// ─── Json ───

if (options.sendAsJson === true) {
return res.json(marshalError(err, res, options.exposeInJson))
return res.json(marshalError(error, res, options.exposeInJson))
} else if (options.sendAsJson === 'from-response-type') {
const responseType = res.get('Content-Type')
// https://regex101.com/r/noMxut/1
const jsonInferredFromResponse = /^application\/(\S+\+|)json/m.test(responseType)

if (jsonInferredFromResponse) {
return res.json(marshalError(err, res, options.exposeInJson))
return res.json(marshalError(error, res, options.exposeInJson))
}
} else if (options.sendAsJson === 'from-response-type-or-request') {
const responseType = res.get('Content-Type')
const jsonInferredFromResponse = /^application\/(\S+\+|)json/m.test(responseType)
const jsonInferredFromRequest = !responseType && (req.xhr || (!!req.get('Accept') && !!req.accepts('json')))

if (jsonInferredFromResponse || jsonInferredFromRequest) {
return res.json(marshalError(err, res, options.exposeInJson))
return res.json(marshalError(error, res, options.exposeInJson))
}
}

// no need to handle false

next(err)
next(error)
}

if (!options.notFoundHandler) {
return finalErrorHandler
}

const notFoundStatus = typeof options.notFoundHandler === 'number' ? options.notFoundHandler : 404

// https://github.com/pillarjs/finalhandler/blob/v1.1.2/index.js#L113-L115
const notFoundHandlerr: express.RequestHandler =
typeof options.notFoundHandler === 'function' ? options.notFoundHandler : notFoundHandler
typeof options.notFoundHandler === 'function'
? options.notFoundHandler
: function notFoundHandler(req: express.Request, res: express.Response, next: express.NextFunction) {
res.status(notFoundStatus)
const notFoundError = new RouteNotFoundError(`Cannot ${req.method} ${req.baseUrl}${req.path}`)
next(notFoundError)
}

return [notFoundHandlerr, finalErrorHandler] as any
}
Expand Down Expand Up @@ -142,10 +151,11 @@ export namespace finalHandler {
/**
* Defines the handler when the route is not found:
*
* - switch to `true` to apply a basic handler throwing a `404` error
* - or declare your own handler
* - switch to `true` to apply a basic handler throwing a `404` error.
* - switch to a number to apply the same basic handler with a custom status code.
* - or declare your own handler.
*/
notFoundHandler?: boolean | express.RequestHandler
notFoundHandler?: boolean | number | express.RequestHandler
}
}

Expand All @@ -155,16 +165,6 @@ export namespace finalHandler {
// https://github.com/microsoft/TypeScript/issues/29729
type ErrorProps = 'name' | 'message' | 'stack' | (string & Record<never, never>)

/**
* @see https://github.com/pillarjs/finalhandler/blob/v1.1.2/index.js#L113-L115
* @internal
*/
function notFoundHandler(req: express.Request, res: express.Response, next: express.NextFunction) {
res.status(404)
const notFoundError = new RouteNotFoundError(`Cannot ${req.method} ${req.baseUrl}${req.path}`)
next(notFoundError)
}

/**
* @internal
*/
Expand Down

0 comments on commit 2fd6d99

Please sign in to comment.