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

express-decorator-router

v0.2.2

Published

use decorators in a simple way without transpiling javascript code

Downloads

15

Readme

express-decorator-router

NPM

use decorators in a simple way without transpiling javascript code

Why?

Have you ever considered using the decorators feature using vanilla javascript to automate the creation of express routes?

The express-decorator-router package came to solve this problem in a simple and didactic way, without the need for transpiling processes in your code.

This package is intended to avoid unnecessary creation of two files where one file contains the route definition and the other file has the function that handles the route request / response process, leaving simpler maintenance and more scalable code.

New in 0.2.0

Now, you can work with dependency injection.

Usage

Let's take a short example using the decorators on a prototype-based controller.

const {
  get,
  controller
} = require ('express-decorator-router')

const controllerFactoryDecorator = controller('/users')

class UsersController {
  constructor () {/*...class constructor definition*/}
        
  getUsers (ctx) {/*...process to get users (database, network, etc)*/}
       
  getUsersById (ctx) {/*...implementation of another endpoint*/}
}

module.exports = controllerFactoryDecorator(UsersController, {
  getUsers: get (),
  getUsersById: get ('/:id')
})

the controller function returns a high order function where the decorator definition is made by associating a decorator with a class method as seen in the example above.

Let's take another example, but let's use middleware and a literal object to define a controller:

const {
  get,
  post,
  controller
} = require ('express-decorator-router')

const authExampleMiddleware = (req, res, next) => {
  if (!req.user) return next(new Error('invalid user'))      
  return next ()
}

const controllerFactoryDecorator = controller('/tasks', authExampleMiddleware)

const getTasks = ctx => {/*...*/}
const createTask = ctx => {/*...*/}

module.exports = controllerFactoryDecorator({
  getTasks,
  createTask
}, {
  getTasks: get(),
  createTask: post()
})

In the example above, we create middleware to authenticate users and associate it with the controller function, so middleware logic will be applied to all controller methods. The same technique can be used at the method level.

Example:

// only getTasks method requires authentication for access
module.exports = controller ('/tasks') ({getTasks, createTask}, {
  getTasks: get (authExampleMiddleware),
  createTask: post ()
})

When we define a controller, we can pass two arguments to function, the first being the base path of the controller and the second an array containing middleware (the same arguments are accepted by route decorators). If the controller function does not receive any arguments, the path is defined by the value received by the method decorator. Let's look at an example:


const middlewareTest = (req, res, next) => { /*any middleware implementation*/ }
const anotherMiddlewareTest = (req, res, next) => { /*any middleware implementation*/ }

module.exports = controller () ({
  getTasks,
  createTask
}, {
  getTasks: get ('/tasks', middlewareTest), // after the path, it is possible to pass an array of middleware
  createTask: post ('/tasks', anotherMiddlewareTest)
})

Once the decorators are applied, every controller instance (being prototype or literal object based) will receive an array of routes, where the metadata of each route is defined, making it possible to dynamically assemble the routes.

Awilix integration (Dependency Injection)

To enforce best practices when using this library, you can use Awilix to work with an IoC Container, aiming for decoupling our services and improving our architecture.

The express-decorator-router package has some features to make the process easier of working with dependency injection, building a container with a map associating a service name with a service instance (class or function) working together with inject function.

Let's take a short example using dependency injection with awilix.

const express = require('express')
const taskService = require('./tasks/service')
const userService = require('./users/service')
const newsService = require('./news/service')
const {
  useAwilixControllers,
  awilix,
  scopePerRequest
} = require('express-decorator-router')

const app = express()
const router = express.Router()

//create awilix container
const container = awilix.createContainer()

//registry your services
container.register({
	taskService: awilix.asValue(taskService).scoped(),
	userService: awilix.asValue(userService).scoped(),
	newsService: awilix.asValue(newsService).scoped()
})

app.use(express.json())

//injects the container in request scope
app.use(scopePerRequest(container))

//injects userService into request object with a `inject` middleware function
app.get('/user/login', inject('userService'), (request, response) => {
  const { userService } = request
  return userService.login(request.body)
    .then(ok => response.status(200).json({ message: "OK" }))
    .catch(err => response.status(500).json({ message: "Error..." }))
})

[TIP]: If you need more information about awilix, i really recommend visiting the repository and checking package documentation.

Register Controllers

Before putting the application to run, it is necessary to re-bind the controller methods to the routes using the metadata produced by the decorators. The express-decorator-router package has a feature that will automatically register express routes. Let's look at an example:


const express             = require('express')
const cors                = require('cors')
const { useControllers, useAwilixControllers }  = require('express-decorator-router')

const app             = express()
const router          = express.Router()

app.use(cors())
app.use(express.json())

app.use('/api', useControllers({
   router,
   controllerExpression: `${__dirname}/**/controller.js`
}))

//or if you use middleware without route prefix
app.use(useControllers({
   router,
   controllerExpression: `${__dirname}/**/controller.js`
}))

//or if you use awilix controllers
app.use('/api/awilix', useAwilixControllers({
	router,
	controllerExpression: `${__dirname}/**/controller.js`
}))

The useControllers and useAwilixControllers methods uses two parameters, the first is the routing mechanism and the second is a glob expression that has the responsibility of finding all controllers that match the pattern of the expression, the only difference between useControllers and useAwilixControllers is that the awilix require to use a container registration for your dependency injection.

Example

You can see a demo in the example folder.

Decorators API

  • register ({ routes: Function, controllerExpression: string }): Function
  • controller (path: string, ...middlewares?:Function []): Function
  • route (method: string, methodPath: string, ...middlewares?:Function []): Function
  • head, options, get, post, put, patch, del, delete, all: partial functions provided by the route method that automatically supply the httpMethod argument.

Run Tests

  npm install
  npm test

Contributing

Contributions via pull requests are welcome :-).

License

MIT © Lucas Mendes Loureiro