0

I am developing an application using node.js and typescript. I patterned my server.ts file after the approach outlined in this article, http://brianflove.com/2016/11/08/typescript-2-express-node/, here is the sample code for the server.ts file:

import * as bodyParser from "body-parser";
import * as cookieParser from "cookie-parser";
import * as express from "express";
import * as logger from "morgan";
import * as path from "path";
import errorHandler = require("errorhandler");
import methodOverride = require("method-override");

/**
 * The server.
 *
 * @class Server
 */
export class Server {

    public app: express.Application;

    /**
     * Bootstrap the application.
     *
     * @class Server
     * @method bootstrap
     * @static
     * @return {ng.auto.IInjectorService} Returns the newly created injector for this app.
     */
    public static bootstrap(): Server {
        return new Server();
    }

    /**
     * Constructor.
     *
     * @class Server
     * @constructor
     */
    constructor() {
        //create expressjs application
        this.app = express();

        //configure application
        this.config();

        //add routes
        this.routes();

        //add api
        this.api();
    }

    /**
     * Create REST API routes
     *
     * @class Server
     * @method api
     */
    public api() {
        //empty for now
    }

    /**
     * Configure application
     *
     * @class Server
     * @method config
     */
    public config() {
        //empty for now
    }

    /**
     * Create router
     *
     * @class Server
     * @method api
     */
    public routes() {
        //empty for now
    }
}

and here is the sample code for the routes function:

/**
  * Create router.
  *
  * @class Server
  * @method config
  * @return void
  */
private routes() {
  let router: express.Router;
  router = express.Router();
  //let mymodel = this.connection.model<IMyModel>("MyModel", MySchema);
  //let myservice: MyService = new MyService(this.myRepository(mymodel));


  //IndexRoute
  IndexRoute.create(router, myservice);

  //use router middleware
  this.app.use(router);
}

For my particular scenario, I am injecting a service into the route to perform the appropriate actions that may need to be performed. Here is my concern, if I am injecting the service into the controller in this manner. Does this mean each person making a request will receive a different service and repository or the same instance? Ideally, I would imagine it being better if each user receives a different instance. How would this effect the database calls being made in the repository layer? I am new to node.js and am still trying to wrap my head around the relationships between the app.js, server.js and requests being made as node.js is single threaded.

user1790300
  • 2,143
  • 10
  • 54
  • 123

1 Answers1

1

Does this mean each person making a request will receive a different service and repository or the same instance

In your example, all request will use the same instance of MyService.

This is not a big issue as long as you ensure that al the classes in the dependency graph are stateless.

I would imagine it being better if each user receives a different instance.

I agree with you. I like more the idea of one instance per requests.

How would this effect the database calls being made in the repository layer?

As I said it should not be a problem as long as you don't share state between requests. Some database recommends one unique permanent connection (e.g node). In that case sharing the same repository as a singleton is a good idea.

relationships between the app.js, server.js

server.js is the file in which we create and start running one or multiple apps. Each app usually runs on a different VHOST or PORT.

and requests being made as node.js is single threaded

Node.js uses a non-blocking model. When you send a query to the database, Node.js will continue processing other requests. This means that the main thread will not be blocked by the query. When the query returns, the main thread will pick the event and continue the execution of the request.

An alternative approach

I have created a library that allows you to have better control over the injection of dependencies and the scope. For example, you will not need to do the following:

let mymodel = this.connection.model<IMyModel>("MyModel", MySchema);
let myservice: MyService = new MyService(this.myRepository(mymodel));

Instead of that you can declare a class and its dependencies to be injected:

@injectable()
class MyService() {

    @inject("IMyModel") private mymodel: IMyModel;

    // ...
}

Also, instead of declaring routes like:

IndexRoute.create(router, myservice);

You can use a decorator like @Get

@injectable()
class MyService() {

    @inject("IMyModel") private mymodel: IMyModel;

    @Get("/someURL")
    public async get() {
        return await this.mymodel.readAll();
    }

}

Finally, to control the life cycle you just need to use a different type of binding.

One instance shared by all requests:

container.bind<MyService>("MyService").to(MyService).inSingletonScope();

A new instance for each requests:

container.bind<MyService>("MyService").to(MyService).inTransientScope();

The following is a real world example of a controller with an HTTP GET enpoint:

import { Controller, Get } from "inversify-express-utils";
import { injectable } from "inversify";
import { Repository } from "@lib/universal/interfaces/infra";
import { Aircraft } from "@lib/universal/interfaces/aviation";
import { authorize } from "@lib/server/auth/auth_middleware";
import { referenceDataPaths } from "@stellwagen/universal/config/app_paths";
import { FEATURE } from "@lib/server/constants/privileges";
import { aircraftRepository } from "../ioc_module/decorators";

@injectable()
@Controller(
    referenceDataPaths.server.aircraft.name,
    authorize({ feature: FEATURE.APP_ACCESS_REFERENCE_DATA })
)
class AircraftController {

    @aircraftRepository private readonly _aircraftRepository: Repository<Aircraft>;

    @Get(referenceDataPaths.server.aircraft.endpoints.get)
    public async get() {
        return await this._aircraftRepository.readAll();
    }

}

export { AircraftController };

The library is inversify-express-utils and it is powered by InversifyJS.

Remo H. Jansen
  • 23,172
  • 11
  • 70
  • 93
  • Nice! I like this approach. If you don't mind, I do have a few questions. In this scenario, how would your server.ts file need to be setup to accommodate this approach especially if you also need middleware items like winston logging, etc? How would this also affect your bin/www file? – user1790300 Jun 23 '17 at 17:48
  • There are a few examples[here](https://github.com/inversify/inversify-express-example) that you can use as reference. I don't have an example of Express 4 but I assume that it should be compatible. – Remo H. Jansen Jun 25 '17 at 15:23
  • I finally have everything moved over to inversify-express-utils but there are a few items I am having an issue with, would really appreciate your input. 1) For your example above, how are you binding your model to the container? For my scenario, as a background, I am currently injecting my models into the repositories as "Model" to an object type of "Model". 2) How are you establishing db connection before this call: let server = new InversifyExpressServer(container)? – user1790300 Jul 05 '17 at 20:19
  • `Document` leads me to think that you are using mongodb? If so, here you have a demo https://github.com/stelltec/public-tech-demos/tree/master/nodejs-madrid-meetup/demo3 – Remo H. Jansen Jul 06 '17 at 12:37