jeffijoe / awilix-express

Awilix helpers/middleware for Express
MIT License
114 stars 7 forks source link

Cannot call a class as a function #16

Closed guillaumemaka closed 5 years ago

guillaumemaka commented 5 years ago

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.

jeffijoe commented 5 years ago

One thing i noticed is you are destructuring in the constructor but using classic injection?

guillaumemaka commented 5 years ago

@jeffijoe Found the error after re-reading my issue, duplicate declaration:

app.use(users);
app.use(upload);
jeffijoe commented 5 years ago

Oh yeah, the stack trace didnt even mention Awilix. 😀

guillaumemaka commented 5 years ago

Sorry for polluting your issues

jeffijoe commented 5 years ago

No problem 😀