NestJs - How to modify interceptor - node.js

I have following JSON response from my NestJS Controller:
{
"data": [{ ... users ... }]
}
To achieve this "envelop" thing, I use the interceptor:
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '#nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { plainToClass } from 'class-transformer'
import { ResponseObjectInterface } from './response-object.interface'
interface ClassType<T> {
new (): T
}
#Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<Partial<T>, ResponseObjectInterface<T> | T> {
constructor(
private readonly classType: ClassType<T>,
private readonly envelope = true
) {}
intercept(
context: ExecutionContext,
next: CallHandler
): Observable<ResponseObjectInterface<T> | T> {
return next
.handle()
.pipe(
map(data =>
this.envelope
? { data: plainToClass(this.classType, data) }
: plainToClass(this.classType, data)
)
)
}
}
It works as expected.
Now I have to change it and add another root property on the response. This:
{
"data": { ... user ... },
"_signed": "jwt" --> String or NULL
}
I have other objects (products, subscriptions...etc). All of them have the same JSON signature. But they will have NULL in the _signed. It will be empty. Only user is signed.
I thought to have some logic in the the interceptor that will add the property, but I am not sure how to do it. What would be the best way of achieving this functionality?

I managed to resolve my issue without using Interceptor whatsoever.
I used concept of two DTO objects. One is generic, and another one is plain one.
To make story short:
import { IsNumber, IsString } from 'class-validator';
import { Exclude, Expose } from 'class-transformer';
import { ApiProperty } from '#nestjs/swagger';
#Exclude()
export class GenericResponse {
constructor(data) {
this.data = data
}
#Expose()
#IsNumber()
#ApiProperty()
data: any
public getData() {
return this.data
}
#Expose()
#IsString()
#ApiProperty()
private _sign: string;
public setSignedPayload(signedPayload: string) {
this._sign = signedPayload
}
public getSignedPayload() {
return this._sign
}
}
And when I land in userService, I set the Data, also I set the jwt.
If I end somewhere else, I can choose whether to set the jwt or not.

Related

How to extend TypeORM repository in NestJS 9 (TypeORM 3.+)

Previously, TypeORM repository could be extended and injected directly into services, e.g.:
import { User } from './entities/user.entity';
import { EntityRepository, Repository } from 'typeorm';
#EntityRepository(User)
export class UsersRepo extends Repository<User> {
// my custom repo methods
}
import { Injectable } from '#nestjs/common'
import { UsersRepo } from './users.repo';
#Injectable()
export class UsersService {
constructor(private readonly usersRepo: UsersRepo) {}
}
But since version 3.0.0 TypeORM does not support repository extending via inheritance.
How to achieve such behavior in NestJS 9 (which depends on TypeORM 3.+)? The only solution I came up with is to add custom methods to the service layer. But I would like to keep all ORM-related methods (query, aggregations, etc.) in the repository layer.
Let me tell you straight away: I have no idea whether this is a recommended solution, I don't know if the authors of TypeORM actually considered such approach at all. But what I just did a minute ago is this:
#Injectable()
export class UserRepository extends Repository<UserEntity> {
constructor(
#InjectRepository(UserEntity)
repository: Repository<UserEntity>
) {
super(repository.target, repository.manager, repository.queryRunner);
}
}
And the best part: it worked :D
I use:
"#nestjs/typeorm": "^9.0.0" just so you know.
I will keep checking if it doesn't break anything, but for now, seems like a good workaround.
hope helpful for you.
In my UserServices
constructor(
#InjectRepository(UserEntity)
private readonly repository: BaseRepository<UserEntity>,
) {}
UserModule
#Module({
imports: [TypeOrmModule.forFeature([UserEntity])],
exports: [UserService],
providers: [UserService],
})
export class UserModule {}
*note: BaseRepository from typeorm-transactional-cls-hooked
This is solved here (similar to the other answers here, but more complete):
How to do custom repository using TypeORM (MongoDB) in NestJS?
Not sure why they deprecated #EntityRepository because this is more complicated, but it works
Hope helpful for you:
import { DataSource, Repository } from 'typeorm';
import { EntityTarget } from 'typeorm/common/EntityTarget';
export class GenericRepository<T> extends Repository<T> {
constructor(target: EntityTarget<T>, dataSource: DataSource) {
super(target, dataSource.createEntityManager());
}
async someCommonMethod() {
return {};
}
}
import { DataSource } from 'typeorm';
import { User } from '../../entities/User';
import { Injectable } from '#nestjs/common';
import { GenericRepository } from '../common/generic.repository';
#Injectable()
export class UserRepository extends GenericRepository<User> {
constructor(dataSource: DataSource) {
super(User, dataSource);
}
}
import { Injectable } from '#nestjs/common';
import { User } from '../../entities/User';
import { InjectRepository } from '#nestjs/typeorm';
import { UsersRepository } from './users.repository';
#Injectable()
export class UsersService {
constructor(
#InjectRepository(User)
private readonly usersRepository: UsersRepository,
) {}
}

Property-based injection with an Abstract Class

I am trying to add an Abstract method to serialize my response objects in order to hash the id's. That would require to have a property-based injection in order to not pass lots of variables through the constructor.
The class that will be used to Serialize :
import { AbstractHash } from 'src/global/abstract.hash';
import { IItems } from '../items.interface';
import { Injectable } from '#nestjs/common';
#Injectable()
export class ResponseItem extends AbstractHash implements IItems {
name: string;
price: number;
type: string;
constructor(partial: Partial<IItems>) {
super();
Object.assign(this, partial);
}
}
The abstract class :
import { HashIdHelper } from 'src/hash-id.helper';
import { Exclude, Expose } from 'class-transformer';
import { Inject, Injectable } from '#nestjs/common';
#Injectable()
export abstract class AbstractHash {
#Exclude()
id: number;
//#Inject('HASHID_HELPER') // Neither works
#Inject(HashIdHelper)
public readonly hashIdHelper: HashIdHelper;
#Expose({ name: 'id' })
encodedId() {
console.log(this.hashIdHelper); // -> undefined
console.log(HashIdHelper);
return this.hashIdHelper.encode(this.id);
}
}
The controller :
// imports
#UseInterceptors(ClassSerializerInterceptor)
#Controller('items')
export class ItemsController {
constructor(
private readonly itemsService: ItemsService,
#Inject('HASHID_HELPER') private readonly hash: HashIdHelper, // <-- Ok
) {}
#Get(':id')
async findOne(#Param('id') id: string) {
console.log(this.hash.encode(+id)); // <-- Ok
const item = await this.itemsService.findOne(parseInt(id));
console.log(new ResponseItem(item));
return new ResponseItem(item); // <-- undefined attribute, see above
}
}
The repository : https://github.com/befabry/caddie/tree/hash_id
Thanks for the help
You're calling new ClassInstance() so you are 100% in control of the class and it's properties. Nest will not inject anything on a manually instantiated class, because it's not managed by Nest's lifecycle in the first place. You can use moduleRef.create to have Nest create an instance for you, but once you use the keyword new you are in charge of that instance.

Injecting custom repository using inversify

I'm practicing mikroorm with inversify.
I have TodoEntity
import { Entity, Property, PrimaryKey, EntityRepositoryType } from '#mikro-orm/core'
import { TodoRepository } from '../../repositories'
#Entity({tableName: 'todo'})
export class TodoEntity {
// [EntityRepositoryType]?: TodoRepository;
#PrimaryKey()
id?: number
#Property()
title: string
#Property()
description: string
constructor(title: string, description: string) {
this.title = title
this.description = description
}
}
And this is the custom repository TodoRepository
import { EntityRepository } from '#mikro-orm/mysql'
import { Repository } from '#mikro-orm/core'
import { TodoEntity } from '../models/entities'
import { injectable } from "inversify"
#injectable()
#Repository(TodoEntity)
export class TodoRepository extends EntityRepository<TodoEntity> {}
I'm injecting the repository into the service via
import { injectable, inject } from "inversify"
import { TodoRepository } from "../repositories"
import TYPES from "../../di/types"
import autoBind from "auto-bind";
#injectable()
export class TodoService {
private todoRepository: TodoRepository
constructor (#inject(TYPES.TodoRepository) todoRepository: TodoRepository) {
this.todoRepository = todoRepository
autoBind(this)
}
public async getTodos() {
return await this.todoRepository.findAll()
}
}
and this is my controller
import { injectable, inject } from "inversify"
import { TodoService } from "../services/todoService"
import { Request, Response, NextFunction} from "express";
import TYPES from "../../di/types";
import autoBind from "auto-bind";
#injectable()
export class TodoController {
todoService: TodoService
constructor(#inject(TYPES.TodoService) todoService: TodoService) {
this.todoService = todoService
autoBind(this)
}
public async getAllTodos(req: Request, res: Response, next: NextFunction) {
res.send(await this.todoService.getTodos())
}
}
This is the types
const TYPES = {
TodoRepository: Symbol.for("TodoRepository"),
TodoService: Symbol.for("TodoService"),
TodoController: Symbol.for("TodoController")
};
export default TYPES
Container
import { TodoRepository } from "../todos/repositories"
import { TodoService } from "../todos/services/todoService"
import { TodoController } from "../todos/controllers/todoController"
import { Container } from "inversify"
import TYPES from "./types"
const container = new Container();
container.bind<TodoController>(TYPES.TodoController).to(TodoController);
container.bind<TodoService>(TYPES.TodoService).to(TodoService);
container.bind<TodoRepository>(TYPES.TodoRepository).to(TodoRepository);
export { container }
How can I inject the custom repository to service, i'm having an error saying
Error: Missing required #injectable annotation in: SqlEntityRepository
But when I did not include the repository only the service and the controller and I tried to test dump. the DI is just working fined
Im following this example Inversify example with mikro orm
but on the example he's not using the custom repo, any idea where can I start to fix the issue?
You should be able to add decorator with decorate utility that inversify is providing.
import { decorate, injectable } from "inversify";
import SomeClass from "some-module";
decorate(injectable(), SomeClass);
return SomeClass;
Read docs over here: https://github.com/inversify/InversifyJS/blob/master/wiki/inheritance.md#what-can-i-do-when-my-base-class-is-provided-by-a-third-party-module
Also there is global Container option skipBaseClassChecks that will skip #injectable check on all base classes.

Nestjs How to extend service from Generic

I've been implemented a code, written by https://github.com/krazibit
import { Type } from '#nestjs/common'
import { InjectRepository } from '#nestjs/typeorm'
import * as DataLoader from 'dataloader'
import { keyBy } from 'lodash'
import { Repository } from 'typeorm'
export interface IDataService<T> {
readonly repository: Repository<T>
load: (id: string | number) => Promise<T>
loadMany: (ids: Array<string | number>) => Promise<T[]>
}
type Constructor<I> = new (...args: any[]) => I // Main Point
export function DataService<T>(entity: Constructor<T>): Type<IDataService<T>> {
class DataServiceHost implements IDataService<T> {
#InjectRepository(entity) public readonly repository: Repository<T>
private get primaryColumnName(): string {
return this.repository.metadata.primaryColumns[0]?.propertyName
}
private loader: DataLoader<number | string, T> = new DataLoader(async ids => {
const entities = await this.repository.findByIds(ids as any[])
const entitiesKeyed = keyBy(entities, this.primaryColumnName)
return ids.map(id => entitiesKeyed[id])
})
public async load(id: string | number): Promise<T> {
return this.loader.load(id)
}
public async loadMany(ids: Array<string | number>): Promise<T[]> {
return this.loadMany(ids)
}
}
return DataServiceHost
}
It should be a generic function to extend to services.
But, I'm trying to implement it using:
export class MyService extends IDataService(MyEntity)
But now, I'm trying to use the methods load and loadMany but every time I try to do it, I get an error.
Does someone knows how to do reference to those methods to execute them?
You need to inject repository inside constructor, so:
export function DataService<T>(entity: Constructor<T>): Type<IDataService<T>> {
class DataServiceHost implements IDataService<T> {
constructor(#InjectRepository(entity) public readonly repository: Repository<T>) {}
// ...
}
}

Type 'Subscription' is missing the following properties from type 'typeof Subscription': prototype, EMPTY

import { Component, OnInit, OnDestroy, ViewChild, AfterViewInit, ChangeDetectorRef } from "#angular/core";
import { UIService } from "./shared/ui.service";
import { Subscription } from "rxjs";
import { RadSideDrawerComponent } from "nativescript-ui-sidedrawer/angular";
import { RadSideDrawer } from "nativescript-ui-sidedrawer";
//These are my imports
#Component({
selector: "ns-app",
templateUrl: "./app.component.html"
})
export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
#ViewChild(RadSideDrawerComponent, { static: false }) drawerComponent: RadSideDrawerComponent;
enteredChallenge: string[] = [];
private drawerSub = Subscription; //I have put subscription here
private drawer: RadSideDrawer;
constructor (private uiService: UIService, private changeDetectionRef: ChangeDetectorRef) {}
onChallengeInput(enterText: string){
this.enteredChallenge.push(enterText);
}
ngOnInit(){
this.drawerSub = this.uiService.drawerState.subscribe(
() => {
if(this.drawer){
this.drawerComponent.sideDrawer.toggleDrawerState();
}
}
);
}
ngAfterViewInit(){
this.drawer = this.drawerComponent.sideDrawer;
this.changeDetectionRef.detectChanges();
}
ngOnDestroy(){
if(this.drawerSub){
this.drawerSub.unsubscribe();
}
}
}
I have 2 errors
1 -> Property 'unsubscribe' does not exist on type 'typeof Subscription'.
2 -> Type 'Subscription' is missing the following properties from type 'typeof Subscription': prototype, EMPTY
I have included Subscription and the stuff I need but I don't understand why I still get this error. Can someone help me out?
I also made this simple mistake and couldn't spot the typo.
The line:
private drawerSub = Subscription;
Should be:
private drawerSub: Subscription;

Resources