I'd like to DI for repository interface and service interface like Spring using typedi

2.5k views Asked by At

I'd like to DI for repository interface and service interface like Spring using typedi.

Below code (example code of DI for repository) is working correctly when calling api.

Repository

import { Service } from "typedi";
import { EntityRepository, Repository } from "typeorm";
import { User } from "../entity/User";

export interface IUserRepository {
  findAllUsers();

  findUserByUserId(id: number);

  addUser(user: any);

  removeUserByUserId(user: any);
}

@Service()
@EntityRepository(User)
export class UserRepository
  extends Repository<User>
  implements IUserRepository {
  findAllUsers() {
    return this.find();
  }

  findUserByUserId(id: number) {
    return this.findOne({ id });
  }

  addUser(user: any) {
    return this.save(user);
  }

  removeUserByUserId(user: any) {
    return this.remove(user);
  }
}

Service

import { Service } from "typedi";
import { InjectRepository } from "typeorm-typedi-extensions";
import { User } from "../entity/User";
import { UserRepository } from "../repository/userRepository";

export interface IUserService {
  all();

  one(id: any);

  save(user: any);

  remove(id: any);
}

@Service()
export class UserService implements IUserService {
  @InjectRepository(User)
  private userRepository: UserRepository;

  async all() {
    return this.userRepository.findAllUsers();
  }

  async one(id: any) {
    let user = await this.userRepository.findUserByUserId(id);
    if (typeof user === "undefined") {
      throw new Error(`userId ${id} is not found.`);
    }
    return user;
  }

  async save(user: any) {
    return this.userRepository.addUser(user);
  }

  async remove(id: any) {
    let userToRemove = await this.userRepository.findUserByUserId(id);
    if (typeof userToRemove === "undefined") {
      throw new Error(`userId ${id} is not found.`);
    }
    return this.userRepository.removeUserByUserId(userToRemove);
  }
}

However, when I'd like to inject repository using interface, it does not work correctly and occur the error message.
The build is succes. The error message is occur when calling api
In addition, error message are different for the first time and the second time later when call api.
like this

Repository


import { Service } from "typedi";
import { InjectRepository } from "typeorm-typedi-extensions";
import { User } from "../entity/User";
import { UserRepository } from "../repository/userRepository";

...

@Service()
export class UserService implements IUserService {
  @InjectRepository(User)
  private userRepository: UserRepository;

  async all() {
    return this.userRepository.findAllUsers();
  }

  ...
}

Error message of first time.

{
  "name": "CustomRepositoryNotFoundError",
  "message": "Custom repository Object was not found. Did you forgot to put @EntityRepository decorator on it?",
  "stack": "CustomRepositoryNotFoundError: Custom repository Object was not found. Did you forgot to put @EntityRepository decorator on it? (The following is omitted)"
}

Error message of second time later.

{
  "name": "TypeError",
  "message": "Cannot read property 'all' of undefined",
  "stack": "TypeError: Cannot read property 'all' of undefined(The following is omitted)"
}

Service does not work well either.

Below code is success code.

Controller

import {
  Get,
  JsonController,
  OnUndefined,
  Param,
  Post,
  Body,
  Delete,
} from "routing-controllers";
import { Inject, Service } from "typedi";
import { UserService } from "../service/userService";

@Service()
@JsonController("/users")
export class UserRestController {
  @Inject()
  private userService: UserService;

  @Get("/")
  getAll() {
    return this.userService.all();
  }

  @Get("/:id")
  @OnUndefined(404)
  getOne(@Param("id") id: number) {
    return this.userService.one(id);
  }

  @Post("/")
  add(@Body() user: any) {
    return this.userService.save(user);
  }

  @Delete("/:id")
  delete(@Param("id") id: number) {
    return this.userService.remove(id);
  }
}

But the below is not work well. In this case, even the build does not work.

Controller

import {
  Get,
  JsonController,
  OnUndefined,
  Param,
  Post,
  Body,
  Delete,
} from "routing-controllers";
import { Inject, Service } from "typedi";
import { IUserService } from "../service/userService";

@Service()
@JsonController("/users")
export class UserRestController {
  @Inject()
  private userService: IUserService;

  @Get("/")
  getAll() {
    return this.userService.all();
  }

  @Get("/:id")
  @OnUndefined(404)
  getOne(@Param("id") id: number) {
    return this.userService.one(id);
  }

  @Post("/")
  add(@Body() user: any) {
    return this.userService.save(user);
  }

  @Delete("/:id")
  delete(@Param("id") id: number) {
    return this.userService.remove(id);
  }
}

Error Message

CannotInjectValueError: Cannot inject value into "UserRestController.userService". Please make sure you setup reflect-metadata properly and you don't use interfaces without service tokens as injection value.

As described at the beginning, I'd like to DI for repository interface and service interface like Spring using typedi.
TypeDI cannnot using like this? or my code is wrong? Please help me.
Thank you.

2

There are 2 answers

1
Parzh from Ukraine On BEST ANSWER

Interfaces are ephemeral, they don't actually exist when your code is running doing its job, they exist only when you write the code. Classes, on the other hand, are pretty much tangible, they always exist. That's why when you use UserService class, it works, but when you use IUserService interface, it doesn't work.

The error you are getting tells you something useful:

Please make sure […] you don't use interfaces without service tokens as injection value.

0
Elvis Lev On

I've used:

@Inject('BLAH') private userService: IUserService;

And that helped me to avoid this error.
That what is called "Injecting Service Tokens" on the docs:
https://docs.typestack.community/typedi/02-basic-usage-guide/06-service-tokens#injecting-service-tokens