NestJs: Why we don't use DTOs to replace all interfaces? - nestjs

Can we make DTOs source of truth for the validations, and use it in both controller and service?
If I already have DTOs, why do I need interfaces ?

You don't need interfaces if you don't want to use them. For DTO's which are meant to be basic models, I don't use interfaces either. That being said, they are really powerful so I'm definitely not discouraging you from using them, take this example:
interface ILogger {
log(message: string) : Promise<void>;
}
class ConsoleLogger implements ILogger {
log(message: string) : Promise<void> {
return Promise.resolve(console.log(message));
}
}
class DatabaseLogger implements ILogger {
private dbConnection;
constructor() {
dbConnection = new DBConnection(); //Fake but it drives the point across
}
async log(message: string) : Promise<void> {
return await this.dbConnection.saveLog(message);
}
}
class DoTheThing {
private _logger: ILogger;
//You can have nest inject the best logger for this situation and your code doesn't have to care
//about the actual implementation :)
constructor(logger: ILogger) {
this._logger = logger;
}
async myMethod() {
const tweetMessage = await sendTweet('...');
this._logger.log(tweetMessage);
}
}

Related

NestJS lifecycle methods invoked without implementing their interface

I am having a small question about NestJS. In my code, there is a service which looks something like:
`
import { Inject, Injectable } from '#nestjs/common';
import neo4j, { Driver, int, Result, Transaction } from 'neo4j-driver';
import { Neo4jConfig } from './neo4j-config.interface';
import { NEO4J_CONFIG, NEO4J_DRIVER } from './neo4j.constants';
#Injectable()
export class Neo4jService {
constructor(
#Inject(NEO4J_CONFIG) private readonly config: Neo4jConfig,
#Inject(NEO4J_DRIVER) private readonly driver: Driver,
) {}
onApplicationBootstrap() {
console.log('Hello');
}
getDriver(): Driver {
return this.driver;
}
getConfig(): Neo4jConfig {
return this.config;
}
int(value: number) {
return int(value);
}
beginTransaction(database?: string): Transaction {
const session = this.getWriteSession(database);
return session.beginTransaction();
}
getReadSession(database?: string) {
return this.driver.session({
database: database || this.config.database,
defaultAccessMode: neo4j.session.READ,
});
}
getWriteSession(database?: string) {
return this.driver.session({
database: database || this.config.database,
defaultAccessMode: neo4j.session.WRITE,
});
}
read(
cypher: string,
params?: Record<string, unknown>,
databaseOrTransaction?: string | Transaction,
): Result {
if (databaseOrTransaction instanceof Transaction) {
return (<Transaction>databaseOrTransaction).run(cypher, params);
}
const session = this.getReadSession(<string>databaseOrTransaction);
return session.run(cypher, params);
}
write(
cypher: string,
params?: Record<string, unknown>,
databaseOrTransaction?: string | Transaction,
): Result {
if (databaseOrTransaction instanceof Transaction) {
return (<Transaction>databaseOrTransaction).run(cypher, params);
}
const session = this.getWriteSession(<string>databaseOrTransaction);
return session.run(cypher, params);
}
private onApplicationShutdown() {
console.log('Goodbye')
return this.driver.close();
}
}
`
Then in my main.ts file I have this method called:
`
await app.listen(port);
`
As you can see my service does not implement neither onApplicationBootstrap nor onApplicationShutdown.
How does it come that those methods still get invoked? Should I implement onApplicationBootstrap and onApplicationShutdown or not?
As you can also see I' d like that my onApplicationBootstrap is a private method which would not be possible if I implement the interface.
So, I would like to ask you:
Why the two lifecycle methods get called event without implementing the interface?
Should I implement those interfaces at all or just go on and use the methods which would allow me to define them as private?
I expected those methods to not work without implementing the interfaces
The Typescript interface is there to help us as devs. It doesn't exist at runtime, there's no information about it, so the only thing Nest can do is just check "Hey, does this class have the onModuleInit method?" If yes, add it to a list of classes to call onModuleInit. Do the same with the other lifecycle methods.
The interfaces aren't explicitly necessary, but they do give us devs a better idea of the class by just looking at the export class... line because we can see what is implemented/extended.

NestJS - Combine multiple Guards and activate if one returns true

Is it possible to use multiple auth guards on a route (in my case basic and ldap auth).
The route should be authenticated when one guard was successful.
Short answer: No, if you add more than one guard to a route, they all need to pass for the route to be able to activate.
Long answer: What you are trying to accomplish is possible however by making your LDAP guard extend the basic one. If the LDAP specific logic succeeds, return true, otherwise return the result of the call to super.canActivate(). Then, in your controller, add either the basic or LDAP guard to your routes, but not both.
basic.guard.ts
export BasicGuard implements CanActivate {
constructor(
protected readonly reflector: Reflector
) {}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
if () {
// Do some logic and return true if access is granted
return true;
}
return false;
}
}
ldap.guard.ts
export LdapGuard extends BasicGuard implements CanActivate {
constructor(
protected readonly reflector: Reflector
) {
super(reflector);
}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
if () {
// Do some logic and return true if access is granted
return true;
}
// Basically if this guard is false then try the super.canActivate. If its true then it would have returned already
return await super.canActivate(context);
}
}
For more information see this GitHub issue on the official NestJS repository.
According to AuthGuard it just works out of the box
AuthGuard definition
If you look at AuthGuard then you see the following definition:
(File is node_modules/#nestjs/passport/dist/auth.guard.d.ts)
export declare const AuthGuard: (type?: string | string[]) => Type<IAuthGuard>;
That means that AuthGuard can receive an array of strings.
Code
In my code I did the following:
#UseGuards(AuthGuard(["jwt", "api-key"]))
#Get()
getOrders() {
return this.orderService.getAllOrders();
}
Postman test
In Postman, the endpoint can have the api-key and the JWT.
Tested with JWT in Postman Authorization: It works
Tested with API-Key in Postman Authorization: It works
That implies there is an OR function between the 2 Guards.
You can create an abstract guard, and pass instances or references there, and return true from this guard if any of the passed guards returned true.
Let's imagine you have 2 guards: BasicGuard and LdapGuard. And you have a controller UserController with route #Get(), which should be protected by these guards.
So, we can create an abstract guard MultipleAuthorizeGuard with next code:
#Injectable()
export class MultipleAuthorizeGuard implements CanActivate {
constructor(private readonly reflector: Reflector, private readonly moduleRef: ModuleRef) {}
public canActivate(context: ExecutionContext): Observable<boolean> {
const allowedGuards = this.reflector.get<Type<CanActivate>[]>('multipleGuardsReferences', context.getHandler()) || [];
const guards = allowedGuards.map((guardReference) => this.moduleRef.get<CanActivate>(guardReference));
if (guards.length === 0) {
return of(true);
}
if (guards.length === 1) {
return guards[0].canActivate(context) as Observable<boolean>;
}
const checks$: Observable<boolean>[] = guards.map((guard) =>
(guard.canActivate(context) as Observable<boolean>).pipe(
catchError((err) => {
if (err instanceof UnauthorizedException) {
return of(false);
}
throw err;
}),
),
);
return forkJoin(checks$).pipe(map((results: boolean[]) => any(identity, results)));
}
}
As you can see, this guard doesn't contain any references to a particular guard, but only accept the list of references. In my example, all guards return Observable, so I use forkJoin to run multiple requests. But of course, it can be adopted to Promises as well.
To avoid initiating MultipleAuthorizeGuard in the controller, and pass necessary dependencies manually, I'm left this task to Nest.js and pass references via custom decorator MultipleGuardsReferences
export const MultipleGuardsReferences = (...guards: Type<CanActivate>[]) =>
SetMetadata('multipleGuardsReferences', guards);
So, in controller we can have next code:
#Get()
#MultipleGuardsReferences(BasicGuard, LdapGuard)
#UseGuards(MultipleAuthorizeGuard)
public getUser(): Observable<User> {
return this.userService.getUser();
}
You can use combo guard that injects all guards what you need and combines their logic.
There is closed github issue:
https://github.com/nestjs/nest/issues/873
There is also a npm package that address this scenario: https://www.npmjs.com/package/#nest-lab/or-guard.
Then you call a unique guard that references all the necessary guards as parameters:
guards([useGuard('basic') ,useGuard('ldap')])
Inspired from https://stackoverflow.com/a/69966319/16730890
This uses Promise instead of Observable.
import {
CanActivate,
ExecutionContext,
Injectable,
SetMetadata,
Type,
} from '#nestjs/common';
import { ModuleRef, Reflector } from '#nestjs/core';
#Injectable()
export class MultipleAuthorizeGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
private readonly moduleRef: ModuleRef,
) {}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const allowedGuards =
this.reflector.get<Type<CanActivate>[]>(
'multipleGuardsReferences',
context.getHandler(),
) || [];
const guards = allowedGuards.map((guardReference) =>
this.moduleRef.get<CanActivate>(guardReference),
);
if (guards.length === 0) {
return Promise.resolve(true);
}
if (guards.length === 1) {
return guards[0].canActivate(context) as Promise<boolean>;
}
return Promise.any(
guards.map((guard) => {
return guard.canActivate(context) as Promise<boolean>;
}),
);
}
}
export const MultipleGuardsReferences = (...guards: Type<CanActivate>[]) =>
SetMetadata('multipleGuardsReferences', guards);
#Get()
#MultipleGuardsReferences(BasicGuard, LdapGuard)
#UseGuards(MultipleAuthorizeGuard)
public getUser(): Promise<User> {
return this.userService.getUser();
}

Use global nest module in decorator

I have a global logger module in nest, that logs to a cloud logging service. I am trying to create a class method decorator that adds logging functionality. But I am struggling how to inject the service of a global nest module inside a decorator, since all dependency injection mechanisms I found in the docs depend are class or class property based injection.
export function logDecorator() {
// I would like to inject a LoggerService that is a provider of a global logger module
let logger = ???
return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
//get original method
const originalMethod = propertyDescriptor.value;
//redefine descriptor value within own function block
propertyDescriptor.value = function(...args: any[]) {
logger.log(`${propertyKey} method called with args.`);
//attach original method implementation
const result = originalMethod.apply(this, args);
//log result of method
logger.log(`${propertyKey} method return value`);
};
};
}
UPDATE: Per reqest a simple example
Basic example would be to log calls to a service method using my custom logger (which in my case logs to a cloud service):
class MyService {
#logDecorator()
someMethod(name: string) {
// calls to this method as well as method return values would be logged to CloudWatch
return `Hello ${name}`
}
}
Another extended use case would be to catch some errors, then log them. I have a lot of this kind of logic that get reused across all my services.
Okay, found a solution. In case anyone else stumbles upon this. First please keep in mind how decorators work – they are class constructor based, not instance based.
In my case I wanted to have my logger service injected in the class instance. So the solution is to tell Nest in the decorator to inject the LoggerService into the instance of the class that contains the decorated method.
import { Inject } from '#nestjs/common';
import { LoggerService } from '../../logger/logger.service';
export function logErrorDecorator(bubble = true) {
const injectLogger = Inject(LoggerService);
return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
injectLogger(target, 'logger'); // this is the same as using constructor(private readonly logger: LoggerService) in a class
//get original method
const originalMethod = propertyDescriptor.value;
//redefine descriptor value within own function block
propertyDescriptor.value = async function(...args: any[]) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
const logger: LoggerService = this.logger;
logger.setContext(target.constructor.name);
logger.error(error.message, error.stack);
// rethrow error, so it can bubble up
if (bubble) {
throw error;
}
}
};
};
}
This gives the possibility to catch errors in a method, log them within the service context, and either re-throw them (so your controllers can handle user resp) or not. In my case I also had to implement some transaction-related logic here.
export class FoobarService implements OnModuleInit {
onModuleInit() {
this.test();
}
#logErrorDecorator()
test() {
throw new Error('Oh my');
}
}

How to extend a class in typescript

My service is designed in nodejs.
Below is my scenario
i have two controllers, one will be extending the other. there is a static function in both the controllers where in a static variable will be assigned some value.
depending on the condition of the data, im trying the make a call to the respective controller so that the static variable gets a appropriate assigned value.
Note:
The below code is just a snippet to explain the scenario and not the actual code of the application. But the order / calling / controller structure of this code snippet is exactly same. Also the listOfDept variable will be having separate business logic in the checkStart function of firstController and secondController.
// firstController.ts
firstController implements IFirstController {
private static listOfDept: string[];
static checkStart(){
firstController.listOfDept = // my logic to fill this object
}
constructor (){}
}
getRelevantData(next: (error: string, response: any) => void): void {
var myObject = firstController.listOfDept;
this.myRepository.uniqueData(myObject, next);
}
}
firstController.checkStart();
export = firstController;
//ifirstController.ts
interface IFirstController {
getRelevantData(next: (error: string, response: any) => void): void;
}
// secondController.ts
secondController extends firstController implements iSecondController {
private static listOfDept: string[];
static checkStart(){
firstController.listOfDept = ["Computer Science"];
}
constructor (){
super();
}
}
secondController.checkStart();
export = secondController;
//isecondController.ts
interface ISecondController implements ifirstController{}
//Controller calling the getRelevantData function
//middlewareController
middlewareController implements IMiddlewareController {
constructor(private firstController: IFirstController, private secondController: ISecondController) {
}
getDepData(data: any, next: (error: string, response: any) => void): void {
if(data.url = "fromParent") {
// im expecting this to make a call to checkStart() of firstController
this.firstController.getRelevantData();
} else {
// im expecting this to make a call to checkStart() of secondController
this.secondController.getRelevantData();
}
}
}
Problem faced with the above code
No matter which way the getRelevantData function is getting called, im always getting the value of listOfDept as computer science. It is never going in the checkStart function of first controller.
In general I would discourage using static methods for this kind of initialization and instead inject the required data into constructors or create factory methods for creating object with necessary data.
But, if you do want to use static properties, the problem is that you need to refer to the right parent class in the getRelevantData implementation. The class that constructed the instance can be accessed through constructor property. TypeScript does not process this scenario well, so you have to make a type cast:
// firstController.ts
class firstController implements IFirstController {
// Need to be `protected` to be accessible from subclass
protected static listOfDept: string[];
static checkStart(){
firstController.listOfDept; // my logic to fill this object
}
constructor (){}
getRelevantData(next: (error: string, response: any) => void): void {
// You need to refer to the constructor
let Class = this.constructor as typeof firstController;
var myObject = Class.listOfDept;
// the rest
}
}
firstController.checkStart();
//ifirstController.ts
interface IFirstController {
getRelevantData(next: (error: string, response: any) => void): void;
}
// secondController.ts
class secondController extends firstController implements ISecondController {
// No `listOfDept` definition here
static checkStart(){
secondController.listOfDept = ["Computer Science"];
}
constructor (){
super();
}
}
secondController.checkStart();

extending a service to create a single definition of a database throughout application

I've created a nodejs service to create/interact with databases.
class CRUDService {
private section: string;
private db: any;
constructor(section: string) {
this.section = section;
this.db = new DB('.' + section);
}
public create(data: any): Promise<string> {
...
}
public retrieve(id: string): Promise<string> {
...
}
public update(id: any, data: any): Promise<string> {
...
}
public delete(item: any): Promise<string> {
...
}
public all(): Promise<string> {
...
}
}
export {CRUDService};
And then I was using it by passing a database name to it:
this.db1 = new DatabaseService('database-one');
this.db2 = new DatabaseService('database-two');
This has been working well for me but recently I noticed that I am defining the same databases over and over again (in different modules) and figured there has got to be a better way to doing it so that the database can be defined once and shared across everything.
Is there a way I can extend my current service to define/initialize the database once and reference it?
export class DbOneService extends CRUDservice {
protected section = 'database-one';
}
And then use it like:
this.db1 = DbOneService;
I'm just a bit lost and the above doesn't seem to work.
UPDATE
By leaving the CRUDService as-is I was able to achieve what I by
import {CRUDService} from '../crud.service';
function DbOneService() {
return new CRUDService('database-one');
}
export {DbOneService};
Then I use it as follows:
import {DbOneService} from 'db/database-one';
const DbOneServiceDB = DbOneService();
DbOneServiceDB.all(){...}
Is there anything wrong with this approach?

Resources