routing-controllers: fix: TypeDI integration not working as expected on docs

Description

Cannot use TypeDI as explained on the README for basic services.

Obviously, it is written that it only requires the @Service on service side (seems legit), but this actually doesn’t work without telling @Service before the controller as well.

Minimal code-snippet showcasing the problem

Controller snippet

import { Service } from 'typedi'

import { Get, JsonController, Param, State } from 'routing-controllers'
import { CollaboratorService } from '../services/CollaboratorService'

@JsonController('/service/:serviceId/collaborator')
@Service() // This shouldn't be required as described on the README, but it is actually required to run the app as expected
export class CollaboratorController {
  constructor(private collaboratorService: CollaboratorService) { }

  @Get('/')
  async getCollaborators (@State('user') user: User, @Param('serviceId') serviceId: number) {
    return await this.collaboratorService.getCollaborators(
      user.id,
      serviceId
    )
  }

Actual service snippet

import { Service } from 'typedi'

@Service()
export class CollaboratorService {
  async getCollaborators (userId: number, serviceId: number) {
    ...
  }
}

Expected behavior

I’ve imported everything and created the DI Container before the app, the Controller should be working without the @Service decorator declaration.

Actual behavior

It doesn’t, if I try to run the code without the @Service decorator on top of the Controller, I’ll get the following error:

{
    "name": "ServiceNotFoundError",
    "message": "Service with \"MaybeConstructable<CollaboratorService>\" identifier was not found in the container. Register it before usage via explicitly calling the \"Container.set\" function or using the \"@Service()\" decorator.",
    "stack": "ServiceNotFoundError: Service with \"MaybeConstructable<CollaboratorService>\" identifier was not found in the container. Register it before usage via explicitly calling the \"Container.set\" function or using the \"@Service()\" decorator.\n    at ContainerInstance.get (L:\\...\\node_modules\\typedi\\cjs\\container-instance.class.js:45:15)\n    at Object.value (L:\\...\\node_modules\\typedi\\cjs\\decorators\\inject.decorator.js:31:42)\n    at L:\\...\\node_modules\\typedi\\cjs\\container-instance.class.js:286:58\n    at Array.forEach (<anonymous>)\n    at ContainerInstance.applyPropertyHandlers (L:\\...\\node_modules\\typedi\\cjs\\container-instance.class.js:280:46)\n    at ContainerInstance.getServiceValue (L:\\...\\node_modules\\typedi\\cjs\\container-instance.class.js:240:18)\n    at ContainerInstance.get (L:\\...\\node_modules\\typedi\\cjs\\container-instance.class.js:29:25)\n    at Function.get (L:\\...\\node_modules\\typedi\\cjs\\container.class.js:28:36)\n    at Object.getFromContainer (L:\\...\\node_modules\\routing-controllers\\container.js:40:42)\n    at ControllerMetadata.getInstance (L:\\...\\node_modules\\routing-controllers\\metadata\\ControllerMetadata.js:26:28)",
    "normalizedIdentifier": "MaybeConstructable<CollaboratorService>"
}

I don’t know if this is an upstream issue or not.

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 16
  • Comments: 16 (7 by maintainers)

Most upvoted comments

I had to update ~60 classes to add @Service everywhere. This is just crazy. Couldn’t @Controller decorator automatically register controller/middleware classes with the provided DIC? I guess, this should be just a couple of lines of code in the library.

Let’s keep this open for tracking. I think we may auto-register services, but we need to discuss this further.

I hate to ask, but: how does something like this sound when the developer says it out loud?

  • Why should I have to mark all participating classes as services?
  • Why does typedi break dependent libraries on “illegal” instantiation? If this is a preference, rather than a behavior critical to the pattern, shouldn’t it warn and gracefully return undefined, so that I decide if the missing dependency is critical to me or not?

A meme for the ages.

Services everywhere.

This is what I’m using to save me from writing more code than necessary 😁 :

export function ServiceController(...args: Parameters<typeof Controller>) {
    return <TFunction extends Function>(target: TFunction) => {
        Service()(target);
        Controller(...args)(target);
    };
}

export function ServiceJsonController(...args: Parameters<typeof Controller>) {
    return <TFunction extends Function>(target: TFunction) => {
        Service()(target);
        JsonController(...args)(target);
    };
}

Hi!

This is the expected behavior. Since TypeDI 0.9.0 it won’t create instances for unknown classes, so you need to decorate your classes. This needs to be documented and also I believe a fix is possible as routing-controller can auto-register them in its own decrator.

@aaarichter In SemVer, going from version 0.x.y to 0.x+1.0 is considered a major bump. NPM also handles it as such, e.g. depending on typedi@^0.8.0 would install the latest 0.8.x version, not 0.9.x.