npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@reflet/express-middlewares

v2.0.0

Published

Useful express middlewares as decorators

Downloads

7

Readme

@reflet/express-middlewares 🧩

lines coverage statements coverage functions coverage branches coverage

[!IMPORTANT]
Upgrade from v1 to v2 : Migration guide

Convenient middleware decorators to use with Reflet/express.

Getting started

  1. Get started with Reflet/express.

  2. Install the package.

    npm i @reflet/express-middlewares
  3. 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 a 403 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 {}