Reputation: 112
I have a controller called User
and two service classes: UserAdminService
and UserSuperAdminService
.
When a user makes a request to any endpoint of the User
controller, I want to check if the user making the request is an Admin or a Super Admin (based on the roles in the token) and instantiate the correct service (UserAdminService
or UserSuperAdminService
). Note that the two services implement the same UserService
interface (just the internals of the methods that change a bit). How can I make this with NestJS?
What I tried:
user.module.ts
providers: [
{
provide: "UserService",
inject: [REQUEST],
useFactory: (request: Request) => UserServiceFactory(request)
}
],
user-service.factory.ts
export function UserServiceFactory(request: Request) {
const { realm_access } = JwtService.parseJwt(
request.headers["authorization"].split(' ')[1]
);
if (realm_access["roles"].includes(RolesEnum.SuperAdmin))
return UserSuperAdminService;
else
return UserAdminService;
}
user.controller.ts
constructor(
@Inject("UserService") private readonly userService: UserServiceInterface
) {}
One of the reasons my code is not working is because I am returning the classes and not the instantiated objects from the factory, but I want NestJS to resolve the services dependencies. Any ideas?
Upvotes: 2
Views: 2930
Reputation: 70490
Rather than passing back the class to instantiate, which Nest doesn't handle, you could add the UserSuperAdminService
and UserAdminService
to the inject
array, and pass back the instance that Nest then would create per request.
providers: [
{
provide: "UserService",
inject: [REQUEST, UserSuperAdminService, UserAdminService],
useFactory: (request: Request, superAdminService: UserSuperAdminService, adminService: UserAdminService) => UserServiceFactory(request, superAdminService, adminService)
}
...
]
export function UserServiceFactory(request: Request, superAdminService: UserSuperAdminService, adminService: UserAdminService) {
const { realm_access } = JwtService.parseJwt(
request.headers["authorization"].split(' ')[1]
);
if (realm_access["roles"].includes(RolesEnum.SuperAdmin))
return superAdminService;
else
return adminService;
}
Upvotes: 6
Reputation: 1856
Instead of trying to conditionally instantiate a service class you could create a global middleware to redirect the request to the appropriate controller e.g.
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class AdminUserMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
const { realm_access } = JwtService.parseJwt(
req.headers["authorization"].split(' ')[1]
);
if (realm_access["roles"].includes(RolesEnum.SuperAdmin)) {
req.url = req.url.replace(/^\/, '/super-admin/');
}
next();
}
}
Then you can apply it to all routes in your app.module.ts
@Module({
imports: [HttpModule],
controllers: [UserAdminController, UserSuperAdminController]
providers: [UserSuperAdminService, UserAdminService]
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AdminUserMiddleware)
.forRoutes('/');
}
}
and have the following controlers:
@Controller('/')
export class UserAdminController {
private readonly logger: Logger = new Logger(UserAdminController.name);
constructor(private readonly userAdminService: UserAdminService) {}
@Controller('/super-admin')
export class UserSuperAdminController {
private readonly logger: Logger = new Logger(UserSuperAdminController.name);
constructor(private readonly userSuperAdminService: UserSuperAdminService) {}
}
See the NestJS docs and this post for further details
Upvotes: 0