Code Monkey home page Code Monkey logo

awilix-express's People

Contributors

blove avatar jeffijoe avatar jhoscar1 avatar talyssonoc avatar whefter avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

awilix-express's Issues

Accessing controller instance on before/after middlewares

I'm setting up a controller with awilix-express, it works well, but I have some doubts.

In this example below, is it possible to setup a middleware on before that access the instance TodoController?

const { createController } = require('awilix-express')

class TodoController {
  constructor ({ todosService, cacheService }) {
    this.todos = todoService
    this.cacheMiddleware = cacheService
  }
  
  getAll(req, res) => {
    return res.send(this.todosService.getTodos())
  }
  
}

module.exports = createController(TodoController)
  .get('/todos', 'getAll', {
    before: [ this.cacheMiddleware() ] // I know "this" doesn't work here, but I need to access cacheMiddleware, How?
  })

Is there a way to do that? Maybe changing approach and not using a class? Can you show an example please?
Thanks!

Awilix 5.x is out

It would be great if awilix 5 (5.0.1 right now) could be supported

Cannot call a class as a function

Hi,

I tried to use the decorator way, I got this error when I call a route

Called GET http://localhost:4000/users
TypeError: Cannot call a class as a function
    at _classCallCheck (/Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/@babel/runtime/helpers/classCallCheck.js:3:11)
    at UserApi (/Users/guillaume/Developer/Javascript/PiinsBackEnd/routes/users.js:8:26)
    at Layer.handle [as handle_request] (/Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/express/lib/router/index.js:317:13)
    at /Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/express/lib/router/index.js:284:7
    at Function.process_params (/Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/express/lib/router/index.js:335:12)
    at Immediate.next (/Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/express/lib/router/index.js:275:10)
    at Immediate._onImmediate (/Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/express/lib/router/index.js:635:15)
    at runCallback (timers.js:764:11)
    at tryOnImmediate (timers.js:734:5)
    at processImmediate (timers.js:716:5)

routes/users.js

import bodyParser from 'body-parser';
import { route, GET, PUT, before } from 'awilix-express';
import User from '../models/User';
import passport from 'passport';

@route('/users')
export default class UserApi {
    constructor({logger}){
        this.logger = logger;
    }

    @GET()
    @before([passport.authenticate('jwt')])
    async getUsers(req, res) {
        try{
            const users = await User.find();
            res.json(users);
        }catch(err){
            this.logger.log(err);
            res.json({status: 500, message: 'an error occured'});
        }
    }

    @route('/:id')
    @GET()
    @before([passport.authenticate('jwt')])
    async getUser(req, res) {
        try{
            const user = await User.findById(req.params.id);
            res.json(user);
        }catch(err){
            this.logger.log(err);
            res.json({status: 500, message: 'an error occured'});
        }
    }

    @PUT()
    @before([bodyParser.json()])
    async update(req, res){
        const user = await User.findById(req.user.id);
        
        if (!user) {
            return next(new Error('Could not load document'));
        }

        user['pseudo'] = req.body.pseudo;
        user.firstName = req.body.firstName;
        user.lastName = req.body.lastName;
        user.email = req.body.email;
        user.introduction = req.body.introduction;

        try{
            await user.save();
            return res.json({ status: 200, user });
        }catch(err){
            this.logger.log(err);
            return res.status(400).json({ status: 400, error: "update failed" });
        }
    }
    
    @route('/me')
    @GET()
    @before([passport.authenticate('jwt')])
    me(req, res){
        res.json(req.user);
    }
   

    @route('/protected')
    @GET()
    @before([passport.authenticate('jwt')])
    protectedRoute(req, res){
        res.json(req.user);
    }
}

server.js

import express from 'express';
import cors from 'cors';
import bodyParser from 'body-parser';
import mongoose from 'mongoose';
import users from './routes/users';
import upload from './routes/upload';
import passport from './middlewares/passport';
import expressWS from 'express-ws';
import {scopePerRequest, loadControllers} from  'awilix-express';
import {configureContainer} from './lib/container';

import test from './ws-routes/test';

const app = express();

expressWS(app);

const router = express.Router();

const container = configureContainer();

app.use(scopePerRequest(container));
app.use('/static', express.static('public'));
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(passport.initialize());

const connection = mongoose.connection;

connection.once('open', () => {
    console.log('MongoDB database connection established successfully!');
});

app.use('/', router);
app.use(users);
app.use(upload);

app.use(loadControllers('routes/*.js', { cwd: __dirname }));

test(app);

const port = process.env.PORT || 4000;

app.listen(port, () => console.log(`Express server running on port ${port}`));

lib/container.js

import { createContainer, Lifetime, InjectionMode, asValue, asFunction } from 'awilix'
import { logger } from './logger'
import db from './mongoose'
/**
 * Using Awilix, the following files and folders (glob patterns)
 * will be loaded.
 */
const modulesToLoad = [
  // Services should be scoped to the request.
  // This means that each request gets a separate instance
  // of a service.
  ['services/*.js', Lifetime.SCOPED],
  // Stores will be singleton (1 instance per process).
  // This is just for demo purposes, you can do whatever you want.
  ['stores/*.js', Lifetime.SINGLETON]
]

/**
 * Configures a new container.
 *
 * @return {Object} The container.
 */
export function configureContainer () {
  const opts = {
    // Classic means Awilix will look at function parameter
    // names rather than passing a Proxy.
    injectionMode: InjectionMode.CLASSIC
  }
  return createContainer(opts)
    .loadModules(modulesToLoad, {
      // `modulesToLoad` paths should be relative
      // to this file's parent directory.
      cwd: `${__dirname}/..`,
      // Example: registers `services/todo-service.js` as `todoService`
      formatName: 'camelCase'
    })
    .register({
      // Our logger is already constructed,
      // so provide it as-is to anyone who wants it.
      logger: asValue(logger),
      db:
      // Whenever we first request a `mongoose`, create a connection.
      asFunction(
        () => db
      )
        // Make sure we only have 1 connection per process.
        // (the connection is reused)
        .singleton()
        // Whenever we use `container.dispose()`, close the connection.
        .disposer((conn) => conn.close())
    })
}

.babelrc

{
    "presets": [
        "@babel/preset-env"
    ],
    "plugins": [
        ["@babel/transform-runtime"],
        ["@babel/plugin-proposal-decorators", {legacy: true}],
        ["@babel/plugin-proposal-object-rest-spread"]
    ]
}

Googled the error but got no result, so I maybe I did something wrong.

Integrate with awilix-router-core

Hey @talyssonoc — I've been working on a routing abstraction to make routing + dependency resolution super streamlined. The core package is awilix-router-core which does not cater to any specific HTTP framework.

Here's an example usage and reference implementation I did for the awilix-koa package.

All the builder + decorator stuff resides in awilix-router-core, all you need to do is register the result with app.get|post|... as seen in the reference implementation.

Would you be willing to work on this? If you need any help or if something could be more clear, please let me know.

Thanks again for maintaining awilix-express! 😄

build: expected targetOrResolver to be a function or class, but got '...'

Hi,

I'm trying to inject my middleware function as per your example, https://github.com/talyssonoc/node-api-boilerplate/blob/adce807fdd36ac86a44965d0327ec06b9e803225/src/interfaces/http/user/UsersController.js

But I get this error message": "build: expected targetOrResolver to be a function or class, but got. ....",

This is how I registered it

// Operations
container.register({
  getAllEmplois: asClass(GetAllEmplois),
  getEmploi: asClass(GetEmploi),
});

And in my controller:

get router() {
    const router = Router();

    router.get('/', inject('getAllEmplois'), this.all);
    router.get('/:id', inject('getEmploi'), this.one);

    return router;
  }

Am I missing something?

Thank you

[question] "@before" middleware with injected dependencies

Hello.

I have no idea if this is possible or not, but I can't seem to find a way to make this work.
Let's say I have this class:

export class AuthorizationMiddlewares {

    tokenManager: TokenManager

    constructor({ tokenManager }) {
        this.tokenManager = tokenManager
    }

    async authorize(req, res, next) {
        try {
            const token = await this.tokenManager.getByAccessToken(this.getAuthorizationToken(req))
            await this.tokenManager.validate(token.accessToken)
            req.token = token
            next()
        }catch(error) {
            next(error)
        }
    }
    
    getAuthorizationToken(req): string {
      // blabla
      return "blabla"
    }
}

and I want to use it as a middleware, the question is how?
Inside the route class, in @before, I cannot use the injected AuthorizationMiddlewares class, as I cannot access the route's instance variables. I read something about @inject but I am not sure if that's the way to go.

My route class looks like this:

@route('/users')
export default class UserAPI {

    authorizationMiddlewares: AuthorizationMiddlewares

    constructor({ authorizationMiddlewares }) {
        this.authorizationMiddlewares = authorizationMiddlewares
    }

    @route("/test")
    @before(this.authorizationMiddleware.authorize) // this is not possible
    @GET()
    async test(req, res) {
        try {
            res.json({ test: true })           
        }catch(error) {
            next(error)
        }
        const role: any = await Role.findByName("user")
        res.json(role)
    }
}

I am aware that I can import AuthorizationMiddlewares directly, but that won't let me inject the token manager class.

Please help :)

Add TypeScript Definition file (index.d.ts)

This looks like a great solution for dependency injection in Express! It would be great if there was a TypeScript definition file (index.d.ts) so that the container property on the Request interface existed. Otherwise TS developers get an error: Property 'container' does not exist on type 'Request'.

unhandledRejection when returning promises from routes

We would like to start using awilix-express moving forward, but promise rejections are not handled as we expected. Using express-promise-router, our pattern is this:

all routes return promises
When an error happens, throw immediately.
register an error handler (app.use) to inspect the error and return the appropriate response.

I noticed #15 relates to this issue.

But in looking at the code, I see that the error is re-thrown after calling next. I think this is causing the issue.

function asyncErrorWrapper(
  fn: (...args: any[]) => any
): (...args: any[]) => any {
  return function asyncWrappedMiddleware(
    req: Request,
    res: Response,
    next: NextFunction
  ) {
    const returnValue = fn(req, res, next)

    if (
      returnValue &&
      returnValue.catch &&
      typeof returnValue.catch === 'function'
    ) {
      return returnValue.catch((err: any) => {
        next(err)
        throw err // <-- Does this need to be here?
      })
    } else {
      return returnValue
    }
  }
}

Looking at express-promise-router, they don't re-throw in this situation:

// Call the route
var ret = handler.apply(null, args);

// If it doesn't return a promise, we exit.
if (!isPromise(ret)) {
    return;
}

// Since we got a promise, we handle calling next
Promise.resolve(ret).then(
    function(d) {
        if (d === 'next') {
            next();
        } else if (d === 'route') {
            next('route');
        }
    },
    function(err) {
        if (!err) {
            err = new Error('returned promise was rejected but did not have a reason');
        }
        next(err); // <-- no rethrow
    }
);

Maybe I'm not using the library correctly?

Trying minimal example

I have this project structure:

src:
 server.js
 routes
    todos.js

server.js

const Express = require ('express')
const { asClass, createContainer } = require('awilix')
const { loadControllers, scopePerRequest } = require ('awilix-express')


const app = Express()

class TodosService{
  getTodos (){ return "Holi"}
}

const container = createContainer()
.register({
  // Scoped lifetime = new instance per request
  // Imagine the TodosService needs a `user`.
  // class TodosService { constructor({ user }) { } }
  todosService: asClass(TodosService).scoped()
})
app.use(scopePerRequest(container))
// Loads all controllers in the `routes` folder
// relative to the current working directory.
// This is a glob pattern.
app.use(loadControllers('routes/*.js', { cwd: __dirname }))

app.listen(3000)

routes/todos.js

const { makeInvoker } = require('awilix-express')

function makeAPI({ todosService }) {
  return {
    getTodos: (req, res) => {
      return res.send(todosService.getTodos())
    }
  }
}


module.exports = function(router) {
  const api = makeInvoker(makeAPI)
  router.get('/todos', api('getTodos'))
}

I cannot get any response @GET/todos

Is that related to the usage of module.exports instead of exports?
Any insight in how to make this work?
Thanks in advance!

Resloving dependencies inside @before

As mentioned in the doc i have to import the middleware... Well is it possible to inject the middleware as follows after registering it to the DI container:

  //inside route handler
  @route('/')
  @GET()
  @before([{ middleware }])
  async findMe(req, res) {

Furthermore even if i import the middleware and pass it to @before, inside the middleware function no dependency is resolved.
middleware.js:

export default function middleware({ tokenService }) { 
  //tokenService is unedefined by the time @before invokes the middleware (when receiving a request)
  //while in my route handler it is resolved as expected
}

How do i deal with middleware which has dependencies if none of the above works as i would expect it ? Is this intended ?

Injecting dependencies per route instead of when creating controller

Hi! Love the work you guys have done. Although, I'm having a problem related to registrering certain tokens through middlewares, and then injecting dependencies dependent of these tokens within my controllers. I've looked through awilix and awilix-express documentation and issues, yet I can't find any help regarding the issue I'm facing.

To give you an example, here's my controller:

const InventoryController = {
	get router() {
		const public = Router();
		const controller = makeInvoker(this.makeController);

		public.patch('/:type/route1', controller('function1'));
                public.patch('/route2', controller('function2'));
	
		return { public };
	},

	makeController({ addToSiteInventory, getInventory }) {
		return {
                          function1: async (req, res) => {
				...

				try {
					const addedItems = await addToSiteInventory.execute(...);
					res
						.status(Status.CREATED)
						.json({ status: 'success', data: addedItems });
				} catch (error) {
					console.log('Error', error);
				}
			},

			function2: async (req, res) => {
				...

				try {
					const inventory = await getInventory.execute(...);

					res
						.status(Status.OK)
						.json({ status: 'success', data: inventory });
				} catch (error) {
					console.log('Error', error);
				}
			},
		};
	}
};

module.exports = InventoryController;

Now, I have a couple of middlewares which uses the request's scope to register tokens. In one of my middlewares I register a token called 'type'. Which in this simplified example is a route parameter.

Okay. So the problem is that my usecases, the injected dependencies in my controller; addToSiteInventory and getInventory either rely themselves on 'type' or not. getInventory is a usecase that injects 'type'.

However, when I send a request to route2 I get something along the lines of "Can't inject getInventory, 'token' isn't registered". This is due to my middleware not registering 'token', and it's not suppose to for route2.

My question is, is there anyway to have route-specific injections? In that case I can inject each usecase for each route. Instead of injecting all usecases in my controller which leads to the aforementioned problem. I've tried
public.patch('/:type/route1', inject('getInventory') controller('function1'));
But with that attempt I get something along the lines of "Can't build of undefined".

param decorator

Not a real issue.
Untested but should work. This is a method decorator to put inside your controller, that registers a method as a before middleware that will execute everytime a certain param is present

import { makeInvoker, before } from "awilix-express"

/**
 * Adds a root-level before-middleware that will only be executed if
 * exists a param with the name passed to the function
 */
export function param(paramName:string){
    return function routerDecorator(target : any, name : string ){
        let invoker = makeInvoker(target.constructor)(name)
        function paramMiddleware(req, res, next){
            let param = req.params[paramName]
            param != null ?
                invoker(req, res, next) :
                next()
        }
        before(paramMiddleware)(target)
    }
}

Usage:

class PostController {
  postService : PostService
  constructor({ postService }){
     this.postService = postService
  }

  @param('postId')
   async loadPost(req, res, next) {
      let postId = req.params.postId
      let post = await this.postService.get(postId)
      if(!post){
        throw new NotFoundError()
      }
      req['post'] = post
      next()
   }

  @route('/:postId')
  @GET()
  async getPost(req, res){
    let post : Post = req['post'] // <- should be present otherwise the `loadPost` method would have thrown
    res.json(post)
  }
}

I leave it here in case it is useful for someone

Awilix 7.x

Hi there, congrats with the new major on awilix!

Would love to upgrade awilix with awilix-express etc. Thanks in advance for your awesome work 🥳

Scoped logger per request?

Is possible to have a scoped logger in a submodule used in the controller?

controller(scoped) => moduleA => moduleB.

I want to use a logger on moduleB scoped to the request (with traceId, correlation, etc). Is it possible? Thanks

Cannot convert a Symbol value to a string

I am using awilix-express 0.11.0, awilix 2.8.4 in a Typescript project .
I have a class:

export default class SmsController {
smsService: ISms;
logger: any;

constructor(opts) {
console.log(opts);
this.smsService = opts.smsService;
this.logger = opts.logger;
}

sendCaptcha(req: express.Request, res: express.Response): void {
this.logger.debug(hello sendCaptcha api ${process.pid});
res.json({ msg: 'ok' });
}
}

and in a express router file, I use it like this:

import * as express from 'express';
import Ctrl from '../controllers/sms';
import { SmsService } from '../services/sms';
const { makeClassInvoker } = require('awilix-express');

const router = express.Router();

const api = makeClassInvoker(Ctrl);

router.get('/captcha', api('sendCaptcha'));

export default router;

please note the console.log(opts) in SmsController's constructor, it will trigger an exception which tells:
TypeError: Cannot convert a Symbol value to a string
If I comment this line, everything is ok, I think this is a bug in makeClassInvoker.
Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.