@reflet/express-middlewares
🧩
Convenient middleware decorators to use with Reflet/express.
- Getting started
- Request authorizations
- Response interception
- Response side-effect
- Response status
- Response headers
- Response type
- Conditional middleware
Getting started
-
Install the package.
yarn add @reflet/express-middlewares
-
Apply middleware decorators on controllers or specific routes.
Request authorizations
🔦 @UseGuards(...guards)
A guard is a filter function that takes the Request object as parameter and should return a boolean (asynchronously or not) to authorize the request or not.
class Controller {
@UseGuards(
(req) => req.user != null,
(req) => req.user.admin === true,
)
@Get('/secret')
get() {}
}
If the guard returns...
-
true
: The request will be processed. -
false
: The request will be denied with a403
HTTP status and a"Access Denied"
message.
Custom error message
If you want to override the default "Access denied"
message,
you can return (not throw) an Error
instance instead of just false
.
@UseGuards((req) => Boolean(req.user.admin) || Error('You must be admin'))
Error
and not throw it, unless you want it to be handled elsewhere.
Response interception
🔦 @UseInterceptor(mapper)
Intercept and manipulate the response body before it is sent, with a mapping function (asynchronous or not).
class Controller {
@UseInterceptor<{ foo: number }>((data) => ({ foo: data.foo * 5 }))
@Get('/things')
list(@Res res: Response) {
res.send({ foo: 1 }) // expect { foo: 5 }
}
// Gives also access to Request and Response objects:
@UseInterceptor<{ foo: number }>((data, context) => ({ foo: context.res.statusCode }))
@Get('/things/:id')
get(@Res res: Response) {
res.send({ foo: 1 }) // expect { foo: 200 }
}
// You can add a different constraint on the return shape:
@UseInterceptor<{ foo: number }, { foo: string }>((data) => ({ foo: data.foo.toString() }))
@Get('/things/:id')
get(@Res res: Response) {
res.send({ foo: 1 }) // expect { foo: "1" }
}
}
About errors
UseInterceptor
won't intercept errors, whether:
- the response body is an
Error
instance, - the response has an error status (>=400).
If you need to intercept errors as well, you should simply add a @Catch
decorator:
@Catch((err, req, res, next) => { /* intercept errors */ })
@UseInterceptor((data, context) => { /* intercept success */ })
About streams
UseInterceptor
won't intercept streaming responses either (e.g. files sent with res.sendFile
or res.download
).
Instead you should just use a transform stream:
createReadStream('path/to/file').pipe(transform).pipe(res)
In fact, it won't intercept any response sent by chunks with the res.write
native method.
Response side-effect
🔦 @UseOnFinish(sideEffect, exposeResponseBody?)
💫 Related Node.js event:finish
You need to trigger side-effects ? Log details, send an email, etc.UseOnFinish
helps you define a callback on each response, on the finish
event to be exact.
class Controller {
@UseOnFinish((req, res) => {
console.log('Request:', req.method, req.originalUrl, req.body)
console.log('Response:', res.statusCode)
})
@Post('/things')
create() {}
}
As a safety net, any exception happening in your callback will be caught and logged to stderr
instead of crashing the server. You don't want the latter for a side-effect. This does not exempt you to properly handle your errors, though.
Retrieve the response body
You can expose the response body on the Response object by switching the last parameter on. Streaming responses will have their body truncated to the first 64kb, to avoid eating up memory.
class Controller {
@UseOnFinish((req, res) => {
console.log('Request:', req.method, req.originalUrl)
console.log('Response:', res.statusCode, res.body)
}, true)
@Get('/things')
list() {}
}
Response status
🔦 @UseStatus(code)
💫 Related Express method:res.status
Set the response status code with a dedicated decorator.
class Controller {
@UseStatus(201)
@Post('/things')
create() {}
}
UseStatus
input type is narrowed to a union of known 1XX
, 2XX
and 3XX
status codes (instead of just number
). You can still opt-out by expanding the input type to number
or any
:
@UseStatus<number>(199)
💡 Tip
Use HTTP status enums from the same maintainer for an even better developer experience.
Response headers
🔦 @UseSet(header, value)|(headers)
alias@UseHeader
💫 Related Express method:res.set
Set any response header with a dedicated decorator.
@UseSet({ 'x-powered-by': 'brainfuck' })
class Controller {
@UseSet('allow', 'GET')
@Post('/things')
create(@Res res: Response) {
res.sendStatus(405)
}
}
UseSet
/UseHeader
input type is narrowed to a union of known response headers (instead of just string
). You can still opt-out by expanding the input type to string
or any
:
@UseSet<string>('x-custom', 'value')
💡 Tip
Use HTTP response headers enum from the same maintainer for an even better developer experience.
Response type
🔦 @UseType(contentType)
alias@UseContentType
💫 Related Express method:res.type
Set the response Content-Type
with a dedicated decorator.
class Controller {
@UseType('application/msword')
@Send()
@Get('/document')
download() {
return createReadStream('path/to/doc')
}
}
UseType
/UseContentType
input type is narrowed to a union of common content types (instead of just string
). You can still opt-out by expanding the input type to string
or any
:
@UseType<string>('application/x-my-type')
Conditional middleware
🔦 @UseIf(filter, [middlewares])
In some case, you might want to separate a middleware logic from its applying conditions. UseIf
is made for such case.
UseIf((req) => req.method === 'POST', [express.json()])
class Controller {}