2

I am looking for a way to load my controller classes in Inversify that are annotated with @controller() from inversify-express-utils without needing to manage a hand crafted/manually constructed module of imports/exports.

All of the examples I am seeing seem to indicate that in your entry file you need to import each of your controllers explicitely so that inversify-express-utils picks up on the @controller annotations, however this feels like it would be rather tedious for applications of a medium to large size.

In plain JavaScript I would do something like recursively require each file in a directory at runtime, however I understand that due to the way TypeScript works, this isn't exactly possible?

Using the inversify-binding-decorators package, I am able to simply annotate a service with @provide() and that seems to be enough to have the class added to the IOC container, however that does not seem to be enough for inversify-express-utils to discover the @controller() annotations on the class.

Is there a way to achieve auto discovery of these @controller() annotated classes without manually importing them into a entry file?

/services/StarwarsNamesService.ts

import { provide } from 'inversify-binding-decorators';
import * as names from 'starwars-names';
import * as matchSorter from 'match-sorter';

@provide(StarwarsNamesService)
export default class StarwarsNamesService {

    public getNames(): Array<string> {
        return names.all;
    }

    public getRandomNames(count?: number): Array<string> {
        if (!count) { count = 1; }
        return names.random(count);
    }

    public searchNames(term?: string): Array<string> {
        return matchSorter(this.getNames(), term);
    }
}

/controllers/StarWarsNamesController.ts

import { inject } from 'inversify';
import { controller, httpGet } from 'inversify-express-utils';
import StarwarsNamesService from '../../starwars-names/StarwarsNamesService';

@controller('/api/starwars-names')
export default class StarWarsNamesController {

    @inject(StarwarsNamesService)
    private starwarsNamesService: StarwarsNamesService;

    @httpGet('/')
    public getAll(): any {
        return this.starwarsNamesService.getNames();
    }
}

boostrap.ts

import 'reflect-metadata';
import * as express from "express";
import * as path from 'path';
import { InversifyExpressServer } from 'inversify-express-utils';
import { buildProviderModule } from "inversify-binding-decorators";
import { Container } from 'inversify';

// Has to be explicitly imported otherwise is undiscovered
import './controllers/StarwarsNamesController.ts';

let container = new Container();

container.load(buildProviderModule());

let server = new InversifyExpressServer(container);

let serverInstance = server.build();

serverInstance.listen(3000, () => {
    console.log('Server started on port 3000 :)');
});

Thrown Error:

E:\..\..\node_modules\inversify-express-utils\lib\utils.js:10
        throw new Error(constants_1.NO_CONTROLLERS_FOUND);
              ^
Error: No controllers have been found! Please ensure that you have register at least one Controller.
    at Object.getControllersFromContainer
    ...
Vigs
  • 1,286
  • 3
  • 13
  • 30

2 Answers2

0
  1. you must initiate express in async way
  2. you must import controller using require and also in async way, it should : await require(''./controllers/StarwarsNamesController');
Rudy Antony
  • 21
  • 2
  • 5
0

You can do the following to expose your controllers

  1. Create index.js or index.ts file in your controller folder
  2. add each controller file like this export * from "./your controller file path"
tobslob
  • 76
  • 1
  • 3
  • 8
  • I was hoping to avoid needing to manually maintain an index file as there could be tens (hundreds?) of files in the codebase. – Vigs Dec 03 '20 at 05:36