nestjs entity to dto Is this a best practice? - nestjs

user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName: string;
#Column({ default: true })
isActive: boolean;
}
create-user-dto.ts
export class CreateUserDto extends PartialType(User) {}
This code works.
Is this a best practice?

In my experience it is never a best practice, dtos and entities should not be strictly tied. You are mixing logics.
Just consider for instance, that in this way it comes very hard to add validation on your dto's fields.
Also, a change in the data model not always trigger a change on client's side (e.g. a form at FE side where the form's fields are sent to BE with the dto).
Finally, consider also that sometimes you may need to keep separated the repository from the controller/dtos part because they "live" on different streams. (e.g. AWS lambdas).

Related

How can I add additional properties to an entity in NestJS?

I am using NestJS to build an api, and everything is working great so far!
I have a users table and a corresponding users.entity.ts file:
#Entity({ name: 'users' })
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({
unique: true,
})
email: string;
...
I am using AWS Cognito as an authentication provider - everything is working great there too. I am trying not to duplicate things, so I'd like to keep given_name and family_name values with Cognito and not have redundant fields on my postgres table.
I can get the user without problems, but I'm not sure how to "combine" them into my user entity.
For example,
// users.service.ts
const user = await this.usersRepository.findOne({
where: {
id: userId,
},
});
// id: dbc92...
// email: example#email.com
const cognitoUser = await this.cognitoService.adminGetUser(user.id);
// id: dbc92...
// email: example#email.com
// given_name: "Tony"
// family_name: "Stark"
return user;
I don't have a given_name property on my user entity—I'm not sure if I need one?
Ideally, I think what I'm trying to do is something like,
user.givenName = cognitoUser.given_name
user.familyName = cognitoUser.family_name
...
return user
But since my user.entity file doesn't have a givenName property, it's not to happy to do that.
I know I can "merge" the two objects using a spread operator, but I'd like to return the actual user entity if possible. How can I pull from two sources and return the proper entity?
As Mohammad said, if what you are trying to accomplish is:
user.givenName = cognitoUser.given_name
user.familyName = cognitoUser.family_name
then you can do this:
#Entity({ name: 'users' })
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({
unique: true,
})
email: string;
given_name: string;
family_name: string;
}
and be able to handle the data just from User but without replicating data in the database with unnecessary columns.
You can add your desired properties without adding #Column decorator. It will not make column in your table but still you have them in your entity.
From the official docs: https://github.com/typeorm/typeorm/blob/master/docs/listeners-and-subscribers.md#afterload
You can define a method with any name in entity and mark it with #AfterLoad and TypeORM will call it each time the entity is loaded using QueryBuilder or repository/manager find methods.
TypeORM Entity with custom properties / virtual fields:
// tested with `typeorm#0.3.7`
#Entity()
export class Post {
#AfterLoad()
updateCounters() {
if (this.likesCount === undefined) this.likesCount = 0
}
}
I don't think the chosen answer is quite correct because an exception for missing columns is thrown when an insert or update operation is attempted:
https://github.com/typeorm/typeorm/blob/cdabaa30287d357c0ae994209e573f97f92dad22/src/metadata/EntityMetadata.ts#L806-L814
If you are interested in reading more about the issue, is sounds like a feature selectAndMap is soon coming to typeorm to support this in a way that is more intuitive https://github.com/typeorm/typeorm/issues/1822#issuecomment-376069476

Does nestjs support the pattern multiple DTO's are used to "Infer" which controller method to use on a single route?

Disclaimer: My aspiration has obvious and good alternatives, but from what little I know about Nestjs I would not be surprised if supported the following "pattern" somehow.
Given these 2 DTOs:
export class AddExistingMemberDto {
#IsInt()
memberId: number;
}
export class AddNonExistingMemberDto {
#IsString()
username: string;
#IsEmail()
email: string;
}
Can Nestjs check which DTP the request body fulfils, and switch between different controller methods?
I tried:
#Controller('group')
export class GroupController {
#Patch(':id/members/add')
addExistingMember(
#Body() addExistingMemberDto: AddExistingMemberDto,
) {
console.log('Existing', addExistingMemberDt);
}
#Patch(':id/members/add')
addNonExistingMember(
#Body() addNonExistingMemberDto: AddNonExistingMemberDto,
) {
console.log('Non-existing', addNonExistingMemberDto);
}
}
But this always invokes with the first method.
Rationale: Having one controller method that checks the DTO is a fine alternative, but it does require me to merge my two different DTO classes. So I was just curious.

NestJS Rest API show only some columns from database using TypeORM

I would like to show only some of the columns in the Entity using NestJS and TypeORM. These are my columns in the Entity:
#Entity({ name: "products" })
export class Product extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
quality: number;
#Column()
color: string;
#Column()
desc: string;
My repository.ts:
#EntityRepository(Product)
export class ProductRepository extends Repository<Product> {}
For example is someone wants to open products/specific it should show only the name and desc.
This is my function in services.ts now:
async get(productid: number): Promise<Product> {
var product: Product= await this.productRepositroy.findOne(productid);
return product;
}
How should I modify it to return only the chosen Columns? I have read about QueryBuilders, but I have no idea how i should implement them.
Your repository has inherited from the method createQueryBuilder by extending Repository. Thus, you can use it with select in order to only get the expected columns:
const product = await this.productRepository
.createQueryBuilder('p')
.where('p.productId = :productId', { productId })
.select(['p.name', 'p.desc']);
.getOne();
Additionally, Nest is able to modify your outgoing objects by using a Serializer: serialization doc. But this is always a good idea to only select the required columns.
NB: I'd also advise not mixing your business logic (here, handling "products") with your database too much (you'd better split the responsibility of Getting a product and Fetching data from the database, for testability/maintainability reasons).

TypeORM creates double tables - is it by design?

I am a newbie to TypeORM. In a NestJS + TypeORM/postgres app, I have these three very simple entities
#Entity()
export class Book extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
title: string;
#Column()
year: string;
}
#Entity()
export class Author extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName: string;
}
#Entity()
export class Genre extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
}
As you see, no relations so far, no custom table or column names, plain default entities.
When I start the app (with synchronize: true) and look into pgAdmin, I can see that the author and genre tables get created along with duplicate tables, sharing same set of columns:
author
author_entity
genre
genre_entity
book
Is it so by design?
What is the reason book entity doesn't get a duplicate. If this is unexpected behavior, any clue where I should look into?
My guess is that you probably originally named your Author and Genre entities AuthorEntity and GenreEntity. I don't think synchronize will automatically remove database tables whose entity was renamed; it'll just create a new table.
So a simple solution would be to drop the tables in your database and re-run the sync fresh, and you should no longer have those orphaned tables.

How to model a followers system using TypeORM Entities

I am creating an express app using typeORM to help map my objects to my database. I wish to create a followers system. I have a base layout of my User entity as shown below. However, this creates two join tables. I wish to have only one table that goes along the lines of 'user_follows_user', where userId_1 follows userId_2. I would like to be able to keep account of the number of followers and the number of following for each user as well. Is this possible? I'm still not fully understanding of what's even happening in my code.
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable, RelationCount } from 'typeorm';
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName: string;
#Column()
email: string;
#Column()
password: string;
#Column()
bio: string;
#ManyToMany( type => User, user => user.followers )
#JoinTable()
following: User[]
#ManyToMany( type => User, user => user.following )
#JoinTable()
followers: User[]
#RelationCount((user: User) => user.followers)
followersCount: number;
#RelationCount((user: User) => user.following)
followingCount: number;
}
After running my code my database schema looked like this:
https://imgur.com/XfXjfGq
As stated above I feel the need for only one table. Although I am open to other reasonable schemas for a followers system using this ORM.
Your code could work, but keeping the followers in an array can get pretty ugly after a while.
Querying an array is harder and it takes longer too. And you can't keep some important information like the time of getting followed with your schema.
What you want to do is, make another Entity and keep the id of follower and the followed. Like this one:
import { Entity, Column, CreateDateColumn } from 'typeorm';
#Entity()
export class Subscription {
#Column()
userId: number;
#Column()
folowingId: number;
#CreateDateColumn()
createdAt: Date;
}
getting the count of the followers or all of the followers can be done easily now by querying this entity.

Resources