@reflet/express-middlewares
v2.0.0
Published
Useful express middlewares as decorators
Downloads
7
Maintainers
Readme
@reflet/express-middlewares
🧩
[!IMPORTANT]
Upgrade from v1 to v2 : Migration guide
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.
npm i @reflet/express-middlewares
Apply middleware decorators on routers 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.
@Router('/things')
class ThingRouter {
@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'))
🗣️ Again, be sure to return the 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).
@Router('/things')
class ThingRouter {
@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 */ })
🗣️ Don't forget to set a proper error status in your error handler, or else the error body will actually be intercepted.
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.
@Router('/things')
class ThingRouter {
@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.
@Router('/things')
class ThingRouter {
@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.
@Router('/things')
class ThingRouter {
@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
).
Use Status
enum from @reflet/http
for better discoverability and documentation.
Response headers
Set headers
🔦
@UseResponseHeader(header, value)|(headers)
💫 Related Express method:res.set
Set any response header with a dedicated decorator.
@UseResponseHeader({ 'x-powered-by': 'brainfuck' })
@Router('/things')
class ThingRouter {
@UseResponseHeader('allow', 'GET')
@Post('/things')
create(@Res res: Response) {
res.sendStatus(405)
}
}
UseResponseHeader
input type is narrowed to a union of known response headers (instead of just string
).
Augment the union with the help of the global namespace RefletHttp
:
declare global {
namespace RefletHttp {
interface ResponseHeader {
XCustom: 'x-custom'
}
}
}
@Router('/things')
class ThingRouter {
@Get()
list() {}
}
Use ResponseHeader
enum from @reflet/http
for better discoverability and documentation.
Append header value
🔦
@UseResponseHeader.Append(header, value)
💫 Related Express method:res.append
Appends the specified value
to the HTTP response header field
. If the header is not already set, it creates the header with the specified value
.
@UseResponseHeader.Append('link', ['<http://localhost/>', '<http://localhost:3000/>'])
Response type
🔦
@UseType(contentType)
💫 Related Express method:res.type
Set the response Content-Type
with a dedicated decorator.
@Send()
@Router('/things')
class ThingRouter {
@UseType('docx')
@Get('/document')
download() {
return createReadStream('path/to/doc')
}
}
UseType
input type is narrowed to a union of common MIME types and extensions (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()])
@Router('/things')
class ThingRouter {}