NestJS - Creating Business Objects From Other Services - node.js

Nowadays I'm playing around with NestJS and trying to understand the best practices of NestJS world.
By following official documentations I have created service/dto/entity/controller for my business object called 'Cat'.
(also using SequelizeJS)
cat.entity.ts
#Table({
freezeTableName: true,
})
export class Cat extends Model<Cat> {
#AllowNull(false)
#Column
name: string;
#Column
breed: string;
}
create-cat.dto.ts
export class CreateCatDto {
#IsString()
readonly name: string;
#IsString()
readonly breed: string;
}
cat.service.ts
export class CatService {
constructor(#Inject('CAT_REPOSITORY') private readonly CAT_REPOSITORY: typeof Cat) {}
async create(createCatDto: CreateCatDto): Promise<Cat> {
const cat = new Cat();
cat.name = createCatDto.name;
cat.breed = createCatDto.breed;
return await cat.save();
}
}
Now I can make POST requests to my controller and create Cat objects successfully. But I want to create a 'Cat' from other service and the create() method only takes CreateCatDto which I cannot/shouldn't initialize (it's read-only).
How can I call create(createCatDto: CreateCatDto) from other services? How you are handling this kind of requirements with NestJS? Should I create one more method like createFromEntity(cat: Cat) and use it?

try this:
const cat: CreateCatDto = { name: 'Miau', breed: 'some' };

Related

TypeORM Migration Generation Fails With With Error "Package subpath '' is not defined by "exports""

I have, among others, a Product entity defined as follows:
#Entity()
export class Product extends BaseEntity {
#Column()
public name: string;
#Column()
public category: ProductCategory;
#Column({ type: 'json', nullable: true })
public additionalInfo?: ExchangeInfo | CoinInfo;
#OneToMany(() => Review, (review) => review.product)
public reviews: Review[];
}
export enum ProductCategory {
Exchange,
Coin,
}
export interface ExchangeInfo {
company: string;
description: string;
}
export interface CoinInfo {
description: string;
url: string;
}
I generate the first migration (on empty db) as follows:
npm run migration:generate src/migrations/InitialMigration
Everything runs smoothly and my database is populated with the tables as expected.
After this initial migration I create a new, empty one which I use to seed data as follows:
npm run migration:create src/migrations/SeedMigration
and the resulting migration runs smoothly:
export class SeedMigration1674664545529 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
...
await queryRunner.manager.save(allItems, { chunk: 100 });
}
public async down(queryRunner: QueryRunner): Promise<void> {
...
await queryRunner.manager.remove(products);
}
}
Finally, I change any entity and try to generate a new migration to reflect these changes:
npm run migration:generate src/migrations/EntityChanged
Here is where I get the following error:
**Package subpath './product.entity' is not defined by "exports" in /../api/node_modules/entities/package.json**
What could this be?
The only thing I notice is that the file that triggers the error is Product.entity.ts, which unlike other entities has additional interfaced defined in the file but not sure how that would be relevant.

What is `Cat.name` in NestJS mongoose docs?

Reading the NestJS docs on the MongoDB technique, I've came along a confusing example:
#Injectable()
export class CatsService {
constructor(#InjectModel(Cat.name) private catModel: Model<CatDocument>) {}
async create(createCatDto: CreateCatDto): Promise<Cat> {
const createdCat = new this.catModel(createCatDto);
return createdCat.save();
}
async findAll(): Promise<Cat[]> {
return this.catModel.find().exec();
}
}
The line that confuses me is the constructor one; where to #InjectModel is given Cat.name. But, in the cats.schema.ts file, there's no inheritance from another class, nor any valorised static property with that name:
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export type CatDocument = Cat & Document;
#Schema()
export class Cat {
#Prop()
name: string;
#Prop()
age: number;
#Prop()
breed: string;
}
export const CatSchema = SchemaFactory.createForClass(Cat);
Am I missing something or could it be a "bug" in the docs?
Cat.name in this case refers to the inherent static property name that all classes have (really all functions have). This gives us a built in constant that we can refer to without having to write our own, but if you'd prefer you can use the string 'Cat' as well. In this case, Cat.name is the string 'Cat', just a different way to reference it that is built in to all classes (and functions).
JavaScript docs on Function.name

TypeORM getRawOne<T> not returning type T

I'm working on refactoring a koa api to nest and am kinda stuck on refactoring the queries from native psql to typeorm. I have the following table, view and dto.
#Entity()
export class Challenge {
#PrimaryGeneratedColumn()
id!: number;
#Column()
endDate!: Date;
#CreateDateColumn()
createdAt!: Date;
}
#ViewEntity({
expression: (connection: Connection) => connection.createQueryBuilder()
.select('SUM(cp.points)', 'score')
.addSelect('cp.challenge', 'challengeId')
.addSelect('cp.user', 'userId')
.addSelect('RANK() OVER (PARTITION BY cp."challengeId" ORDER BY SUM(cp.points) DESC) AS rank')
.from(ChallengePoint, 'cp')
.groupBy('cp.challenge')
.addGroupBy('cp.user')
})
export class ChallengeRank {
#ViewColumn()
score!: number;
#ViewColumn()
rank!: number;
#ViewColumn()
challenge!: Challenge;
#ViewColumn()
user!: User;
}
export class ChallengeResultReponseDto {
#ApiProperty()
id!: number;
#ApiProperty()
endDate!: Date;
#ApiProperty()
createdAt!: Date;
#ApiProperty()
score: number;
#ApiProperty()
rank: number;
test() {
console.log("test")
}
}
As the object I want to return is not of any entity type, I'm kinda lost on how to select it and return the correct class. I tried the following:
this.challengeRepository.createQueryBuilder('c')
.select('c.id', 'id')
.addSelect('c.endDate', 'endDate')
.addSelect('c.createdAt', 'createdAt')
.addSelect('cr.score', 'score')
.addSelect('cr.rank', 'rank')
.leftJoin(ChallengeRank, 'cr', 'c.id = cr."challengeId" AND cr."userId" = :userId', { userId })
.where('c.id = :id', { id })
.getRawOne<ChallengeResultReponseDto>();
Which returns an object that has the correct fields, but that is not of the class type "ChallengeResultReponseDto". If I try to call the function "test" the application crashes. Further it feels weird to use the challengeRepository but not return a challenge, should I use the connection or entity manager for this instead?
I'm rather certain that getRawOne<T>() returns a JSON that looks like whatever you give the generic (T), but an not instance of that class. You should try using getOne() instead to get the instance of the returned entity

Common columns for all entity in nestjs

let say there are different entity User, Role, Task
All these entities have createdBy, updatedBy, createdOn, updatedOn in common.
I want to know how can I create a base entity such that all entity extends base class in nest js using Typeform.
This is the place where should you use inheritance.
Basically, the idea is to have a base class/entity that gathers common logic or structure where many other classes/entities share that common logic.
For example:
Cat, Dog, Elephant are all having similar characterizations, so we might want to gather all these similar characterizations at a single place in order to avoid duplication of logic and code.
So let's see the simplest example only for basic understanding.
export class Animal {
protected numberOfLegs: number;
protected sound(): void;
}
export class Dog extends Animal {
constructor() {
super();
this.numberOfLegs = 4;
}
sound(): void {
console.log('BARK');
}
}
For your needs:
Export a base entity.
import { Entity, Column } from 'typeorm';
export class BaseEntity {
#Column()
createdBy: string;
#Column()
updatedBy: string;
#Column()
createdOn: Date;
#Column()
updatedOn: Date;
}
And then inherit it from derived entities.
import { Entity, Column } from 'typeorm';
import {BaseEntity} from './base-entity';
export class DerivedEntity extends BaseEntity {
#Column()
id: string;
...
}
Please read about inheritance which is a basic and very important principle in programming and OOP.

Sequelize-typescript 'HasManyCreateAssociationMixin' is not a function

I have a model in sequelize-typescript, Door.ts:
import { Table, Model, Column, AutoIncrement, PrimaryKey, ForeignKey, DataType, AllowNull, BelongsTo, HasMany } from 'sequelize-typescript';
import { Location } from '#modules/location';
import { AkilesServiceV1, AkilesServiceV0, IDoorService } from '#services/DoorService';
import { BelongsToGetAssociationMixin } from 'sequelize/types';
import { DoorLog } from '#modules/door_log';
import { HasManyCreateAssociationMixin } from 'sequelize';
#Table({ tableName: 'door' })
class Door extends Model<Door> {
#PrimaryKey
#AutoIncrement
#Column
id!: number;
#AllowNull(false)
#Column
type!: string;
#Column
button_id!: string;
#Column
gadget_id!: string;
#Column
action_id!: string;
#AllowNull(false)
#Column(DataType.ENUM('vehicular','pedestrian'))
access_type!: 'vehicular' | 'pedestrian';
#AllowNull(false)
#Column
description_tag!: string;
#Column(DataType.VIRTUAL)
description!: string;
#ForeignKey(() => Location)
#AllowNull(false)
#Column
location_id!: number;
#BelongsTo(() => Location)
location!: Location;
#HasMany(() => DoorLog)
door_logs!: DoorLog[];
public getLocation!: BelongsToGetAssociationMixin<Location>;
public createDoorLog!: HasManyCreateAssociationMixin<DoorLog>;
public async open () {
let doorService: IDoorService;
switch(this.type) {
case 'akiles-v0':
doorService = new AkilesServiceV0();
break;
case 'akiles-v1':
doorService = new AkilesServiceV1();
break;
default:
doorService = new AkilesServiceV1();
break;
}
//await doorService.open(this);
return await this.createDoorLog({ door_id: this.id, timestamp: new Date() });
}
public async getParking() {
const location: Location = await this.getLocation();
return await location.getParking();
}
}
export default Door
As you can see it has these two functions associated with Mixins:
public getLocation!: BelongsToGetAssociationMixin<Location>;
public createDoorLog!: HasManyCreateAssociationMixin<DoorLog>;
The first works perfectly using it like this: await this.getLocation(). However, the second when I call it like this: await this.createDoorlog ({door_id: this.id, timestamp: new Date ()}) returns the following error:
TypeError: this.createDoorLog is not a function
I've also tried calling the function without parameters but got the same result. I don't understand why the two functions, while created almost identically, behave differently. Am I missing something with HasManyCreateAssociationMixin?
Thank you.
For when I inevitably come across this question again perplexed by the same problem. The answer Is to ad "as" to the #HasMany mixin. Sequelize appears to have issues with camelcase classes.
So in this case adding
#HasMany(() => DoorLog, options: {as: "doorLog" })
door_logs!: DoorLog[];
or something along these lines should allow you to use this mixin

Resources