How can i do the join of two table in loopback4 - node.js

I have created below mention controller,model and repository in my code. Please have look.
I have developed below mention code but still not able to perform the join operation.
I am going to join two table that is person and info table.
- Info table having one foreign key which is belong to person table.
- Person table: id, name, status
- Info table : id, person_id , name , status
I have also create repository,model and controller file for info and person.
Person Repository ( person.repository.ts)
) {
super(Person, dataSource);
this.infos = this._createHasOneRepositoryFactoryFor(
'info',
getInfoRepository,
);
}
Person Module ( person.module.ts)
#hasOne(() => Info)
infos?: Info;
constructor(data?: Partial<Person>) {
super(data);
}
Info Module (info.module.ts)
#belongsTo(() => Person)
personId: number;
constructor(data?: Partial<Info>) {
super(data);
}
It show me error like this
Unhandled error in GET /people/fetchfromtwotable?filter[offset]=0&filter[limit]=10&filter[skip]=0: 500 TypeError: Cannot read property 'target' of undefined
Is there any idea about join?

drp, Thanks for sharing your models. My post got deleted because I am just starting out and needed to ask for more info which seems strange. ANYWAY, Try to change this line:
this.infos = this._createHasOneRepositoryFactoryFor(
'info',
getInfoRepository
);
to
this.infos = this._createHasOneRepositoryFactoryFor(
'infos',
getInfoRepository,
);
The framework cannot find the 'info' relation on the model because you called the property 'infos'
Here is my example that currently works for me (running latest lb4 and postgres):
User.model.ts
import { model, property, hasOne, Entity } from '#loopback/repository';
import { Address } from './address.model';
#model()
export class User extends Entity {
constructor(data?: Partial<User>) {
super(data);
}
#property({ id: true })
id: number;
#property()
email: string;
#property()
isMember: boolean;
#hasOne(() => Address, {})
address?: Address;
}
Address.model.ts:
import { model, property, belongsTo, Entity } from '#loopback/repository';
import { User } from '../models/user.model';
#model()
export class Address extends Entity {
constructor(data?: Partial<Address>) {
super(data);
}
#property({ id: true })
id: number;
#property()
street1: string;
#property()
street2: string;
#property()
city: string;
#property()
state: string;
#property()
zip: string;
#belongsTo(() => User)
userId: number;
}
User.repository.ts:
import { HasOneRepositoryFactory, DefaultCrudRepository, juggler, repository } from '#loopback/repository';
import { User, Address } from '../models';
import { PostgresDataSource } from '../datasources';
import { inject, Getter } from '#loopback/core';
import { AddressRepository } from '../repositories'
export class UserRepository extends DefaultCrudRepository<
User,
typeof User.prototype.id
> {
public readonly address: HasOneRepositoryFactory<Address, typeof User.prototype.id>;
constructor(
#inject('datasources.postgres')
dataSource: PostgresDataSource,
#repository.getter('AddressRepository')
protected getAccountRepository: Getter<AddressRepository>,
) {
super(User, dataSource);
this.address = this._createHasOneRepositoryFactoryFor('address', getAccountRepository);
} // end ctor
}
User.controller.ts (abridged for length):
#get('/users/{id}/address')
async getAddress(
#param.path.number('id') userId: typeof User.prototype.id,
#param.query.object('filter', getFilterSchemaFor(Address)) filter?: Filter,
): Promise<Address> {
return await this.userRepository
.address(userId).get(filter);
}
Hope this helps.
Good luck!

Related

NestJS: findManyEntities not including relation table

My problem is that, I have this ActivityArticle entity that is has a OneToMany relationship to ActivityArticleImage but findManyEntities is does not include the ActivityArticleImage, but when I create a custom method it works.
Here is my service that needs to work:
#Injectable()
export class ActivityArticleService extends TypeOrmCrudService<ActivityArticle> {
constructor(
#InjectRepository(ActivityArticle)
private activityArticleRepository: Repository<ActivityArticle>,
) {
super(activityArticleRepository);
}
async findManyEntities(options: FindOptions<ActivityArticle>) {
return this.activityArticleRepository.find({
relations: ['articleImage'],
where: options.where,
});
}
async getArticlesWithImage() {
return await this.activityArticleRepository.find({
relations: ['articleImage'],
});
}
}
Controller code:
#Controller({
path: 'activity-article',
version: '1',
})
export class ActivityArticleController
implements CrudController<ActivityArticle>
{
constructor(public service: ActivityArticleService) {}
get base(): CrudController<ActivityArticle> {
return this;
}
#ApiOperation({ summary: 'Get articles with image.' })
#Get('article-with-image')
#HttpCode(HttpStatus.OK)
public async getArticlesWithImage() {
return this.service.getArticlesWithImage();
}
}
Sample output for findManyEntities
Sample output for getArticlesWithImage
How can I possibly work for findManyEntities because I need its this functionality that getArticlesWithImage doesnt have
Edit:
FindOptions code:
import { EntityCondition } from './entity-condition.type';
export type FindOptions<T> = {
where: EntityCondition<T>;
};
Activity Article Entity:
#Entity()
export class ActivityArticle extends EntityHelper {
#PrimaryGeneratedColumn('uuid')
id: string;
/*other fields here*/
#OneToMany(() => ActivityArticleImage, (articleImg) => articleImg.article)
articleImage: ActivityArticleImage[];
}
Acitivity-Article-Image entity:
#Entity()
export class ActivityArticleImage extends EntityHelper {
#PrimaryGeneratedColumn('uuid')
id: string;
/*other fields here*/
#ManyToOne(() => ActivityArticle, (article) => article.articleImage)
article: ActivityArticle;
}

Typeorm serverless trying to save data

I just started learning aws and typeorm and now i'am trying to store data. but am getting error.
POST /dev/api/blogs (λ: postBlog)
✖ Column type for Blog#name is not defined and cannot be guessed. Make sure you have turned on an "emitDecoratorMetadata": true option in tsconfig.json. Also make sure you have imported "reflect-metadata" on top of the main entry file in your application (before any entity imported).If you are using JavaScript instead of TypeScript you must explicitly provide a column type.
import { formatJSONResponse } from '#libs/apiGateway';
import { middyfy } from '#libs/lambda';
import Blog from '../../entities/blog'
import { getConnection } from "typeorm";
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
export const main = middyfy(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
console.log(event);
try {
const createBlog = await getConnection()
.createQueryBuilder()
.insert()
.into(Blog)
.values({
name: "test",
description: "test"
})
.execute();
return formatJSONResponse({
createBlog
})
}catch (e) {
return formatJSONResponse({
status: 500,
message: e
});
}
})
Blog entity
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
#Entity()
class Blog {
#PrimaryGeneratedColumn()
public id: string;
#Column()
public name: string;
#Column()
public description: string;
#Column()
public author: string;
#Column()
public image: string;
}
export default Blog;
whats am doing wrong?

TypeORM eager loading invalid column name with select

When implementing User entity and Roles entity in TypeORM, I used #ManyToMany with eager on true.
I implemented a UserRepository that extends Repository.
When using this.find() it works, without a problem (but also loads the password and other fields an API doesn't need to serve). When using this.find({select: 'email firstname roles'}), it suddenly gives me this error:
RequestError: Invalid column name 'userId'.
I also tried adding option relations, but that gives me error
QueryFailedError: Error: Invalid column name 'userId'.
Can anyone help me with this?
Node version: 12.16.2
TypeORM version: 0.2.24
Typescript version: 3.7.4
Database: SQL Server
Role entity:
#Entity()
export class Role {
#ManyToMany(type => User, user => user.roles)
#PrimaryColumn()
role!: string
}
User Entity
#Entity()
export class User {
#PrimaryGeneratedColumn()
id!: number;
#Column()
public email!: string;
#Column()
public password!: string;
#Column()
public firstname!: string;
#ManyToMany(type => Role, role => role.role, {eager: true, nullable: true})
#JoinTable()
public roles!: Role[];
}
User Repository:
#EntityRepository(User)
export class UserRepository extends Repository<User> {
whitelist: IWhitelist<User> = {
admin: ['email', 'firstname','roles', 'id',]
};
getOptions = (list: string) => {
return {select: this.whitelist[list], relations: ['roles']};
};
adminGetUsers = async (): Promise<Array<User> | undefined> => {
return await this.find(this.getOptions('admin'));
};
}
Have you tried
this.find({select: ['email', 'firstname', 'roles']}
from the documentation :
https://github.com/typeorm/typeorm/blob/master/docs/find-options.md#basic-options

Why I am getting cannot determine GraphQL output error?

I am trying to create simple appliaction with Nest.js, GraphQL and MongoDB. I wnated to use TypeORM and TypeGraphql to generate my schema and make a connection with localhost databasebut but i can not run my server with nest start becouse I am getting this error:
UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL output type for getArticles
I have no idea why i am getting this error. My class ArticleEntity does't has any not primary types, so there should not be any problem. I tried to remove () => ID from #Field() decorator of filed _id of ArticleEntity class but it didn't helped
ArticleResolver
#Resolver(() => ArticleEntity)
export class ArticlesResolver {
constructor(
private readonly articlesService: ArticlesService) {}
#Query(() => String)
async hello(): Promise<string> {
return 'Hello world';
}
#Query(() => [ArticleEntity])
async getArticles(): Promise<ArticleEntity[]> {
return await this.articlesService.findAll();
}
}
ArticleService
#Injectable()
export class ArticlesService {
constructor(
#InjectRepository(ArticleEntity)
private readonly articleRepository: MongoRepository<ArticleEntity>,
) {}
async findAll(): Promise<ArticleEntity[]> {
return await this.articleRepository.find();
}
}
ArticleEntity
#Entity()
export class ArticleEntity {
#Field(() => ID)
#ObjectIdColumn()
_id: string;
#Field()
#Column()
title: string;
#Field()
#Column()
description: string;
}
ArticleDTO
#InputType()
export class CreateArticleDTO {
#Field()
readonly title: string;
#Field()
readonly description: string;
}
If you need anything else comment
ArticleEntity should be decorated with the #ObjectType decorator as shown in the docs https://typegraphql.com/docs/types-and-fields.html.
#Entity()
#ObjectType()
export class ArticleEntity {
...
}
For anyone who gets this error and uses enums, you may be missing a call to registerEnumType.
In my case, I was using the #ObjectType decorator, but I was importing from type-graphql. I imported from #nestjs/graphql instead, and the problem was resolved.
import { ObjectType } from '#nestjs/graphql';
See here for a related discussion on GitHub.
I was using MongoDB and I had my Query return the schema instead of the model class.
Changing #Query((returns) => UserSchema) to #Query((returns) => User) fixed the issue for me.
user.schema.ts
#ObjectType()
#Schema({ versionKey: `version` })
export class User {
#Field()
_id: string
#Prop({ required: true })
#Field()
email: string
#Prop({ required: true })
password: string
}
export const UserSchema = SchemaFactory.createForClass(User)
user.resolver.ts
#Query((returns) => User)
async user(): Promise<UserDocument> {
const newUser = new this.userModel({
id: ``,
email: `test#test.com`,
password: `abcdefg`,
})
return await newUser.save()
}
Output model class should be decorated with #ObjectType() and then all properties of that class will decorated with #Field().
For any one who is using a custom output model class and NOT an entity(sequelize, typeorm, prisma etc). Because I was using database entity first, everything was working fine till I moved to a more customized output model.
One more case would be someone using class A as output and class B is used within A
export class A{
id: number;
name:string;
childProperty: B
. . . . .
}
export class B{
prop1:string;
prop2:string;
}
In that case class B should also be decorated with #ObjectType and fields (prop1 , prop2 ) should be also be decorated with #Field as well.

Nestjs Response Serialization with array of objects

I want to serialize a controller response by the nestjs serialization technique. I didn't find any approach and my solution is as follows:
User Entity
export type UserRoleType = "admin" | "editor" | "ghost";
#Entity()
export class User {
#PrimaryGeneratedColumn() id: number;
#Column('text')
username: string;
#Column('text')
password: string;
#Column({
type: "enum",
enum: ["admin", "editor", "ghost"],
default: "ghost"
})
roles: UserRoleType;
#Column({ nullable: true })
profileId: number;
}
User Response Classes
import { Exclude } from 'class-transformer';
export class UserResponse {
id: number;
username: string;
#Exclude()
roles: string;
#Exclude()
password: string;
#Exclude()
profileId: number;
constructor(partial: Partial<UserResponse>) {
Object.assign(this, partial);
}
}
import { Exclude, Type } from 'class-transformer';
import { User } from 'src/_entities/user.entity';
import { UserResponse } from './user.response';
export class UsersResponse {
#Type(() => UserResponse)
users: User[]
constructor() { }
}
Controller
#Controller('user')
export class UsersController {
constructor(
private readonly userService: UserService
) {
}
#UseInterceptors(ClassSerializerInterceptor)
#Get('all')
async findAll(
): Promise<UsersResponse> {
let users = await this.userService.findAll().catch(e => { throw new NotAcceptableException(e) })
let rsp =new UsersResponse()
rsp.users = users
return rsp
}
It works, but I must explicitly assign the db query result to the response users member.
Is there a better way? Thanks a lot
Here the actual Response and wanted result, for a better explanation.
Result in this Approach
{
"users": [
{
"id": 1,
"username": "a"
},
{
"id": 2,
"username": "bbbbbb"
}
]
}
Result Wanted
{
{
"id": 1,
"username": "a"
},
{
"id": 2,
"username": "bbbbbb"
}
}
I would recommend to directly put the #Exclude decorators on your entity class User instead of duplicating the properties in UserResponse. The following answer assumes you have done so.
Flat Response
If you have a look at the code of the ClassSerializerInterceptor, you can see that it automatically handles arrays:
return isArray
? (response as PlainLiteralObject[]).map(item =>
this.transformToPlain(item, options),
)
: this.transformToPlain(response, options);
However, it will only transform them, if you directly return the array, so return users instead of return {users: users}:
#UseInterceptors(ClassSerializerInterceptor)
#Get('all')
async findAll(): Promise<User> {
return this.userService.findAll()
}
Nested Response
If you need the nested response, then your way is a good solution.
Alternatively, you can call class-transformer's serialize directly instead of using the ClassSerializerInterceptor. It also handles arrays automatically:
import { serialize } from 'class-transformer';
#Get('all')
async findAll(): Promise<UsersResponse> {
const users: User[] = await this.userService.findAll();
return {users: serialize(users)};
}
Wow, what easy, if i know! Perfect, this solves my problem. Also your recommendation for the User Entity with the class-transformer #Exclue() decorator.
And i know that i do not need a custom UsersResponse class in this use case.
This solution was that what i was looking for, but i overjump this quite easy way
Thank you so much for your superfast answer and the problem solution.
Greetings to Berlin from Rostock :)
Here my final approach:
Controller
#UseInterceptors(ClassSerializerInterceptor)
#Get('all')
async findAll(
): Promise<User> {
return await this.userService.findAll().catch(e => { throw new NotAcceptableException(e) })
}
User Entitiy
import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn, OneToMany } from 'typeorm';
import { Profile } from './profile.entity';
import { Photo } from './photo.entity';
import { Album } from './album.entity';
import { Exclude } from 'class-transformer';
export type UserRoleType = "admin" | "editor" | "ghost";
#Entity()
export class User {
#PrimaryGeneratedColumn() id: number;
#Column('text')
username: string;
#Exclude()
#Column('text')
password: string;
#Column({
type: "enum",
enum: ["admin", "editor", "ghost"],
default: "ghost"
})
roles: UserRoleType;
#Exclude()
#Column({ nullable: true })
profileId: number;
#OneToMany(type => Photo, photo => photo.user)
photos: Photo[];
#OneToMany(type => Album, albums => albums.user)
albums: Album[];
#OneToOne(type => Profile, profile => profile.user)
#JoinColumn()
profile: Profile;
}
Response Result
[
{
"id": 1,
"username": "a",
"roles": "admin"
},
{
"id": 2,
"username": "bbbbbb",
"roles": "ghost"
}
]
I have alternative way for your problem.
you can remove #UseInterceptors(ClassSerializerInterceptor) from your Controller. Instead use serialize and deserialize function.
import { serialize, deserialize } from 'class-transformer';
import { User } from './users.entity';
#Get('all')
async findAll() {
const users = serialize(await this.userService.findAll());
return {
status: 200,
message: 'ok',
users: deserialize(User, users)
};
}
it's work too for single data
import { Param } from '#nestjs/common';
import { serialize, deserialize } from 'class-transformer';
import { User } from './users.entity';
#Get(':id')
async findById(#Param('id') id: number) {
const user = serialize(await this.userService.findById(id));
return {
status: 200,
message: 'ok',
user: deserialize(User, user)
};
}
Your approach is recommended by nestjs but that has a fault. You are excluding some properties from being exposed to the client. What if, you work in a project that has an admin and admin wants to see all the data about the users or products. If you exclude fields in the entities, your admin won't see those fields either. Instead, leave the entities as it is, and write dto's for each controller or for each request handler and in this dto's just list the properties you want to expose.
Then write a custom interceptor and create specific dto for ecah entity. For example in your example, you create a userDto:
import { Expose } from 'class-transformer';
// this is a serizalization dto
export class UserDto {
#Expose()
id: number;
#Expose()
roles: UserRoleType;
#Expose()
albums: Album[];
// Basically you list what you wanna expose here
}
custom interceptor is a little messy:
import {
UseInterceptors,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '#nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { plainToClass } from 'class-transformer';
// Normally user entity goes into the interceptor and nestjs turns it into the JSON. But we we ill turn it to User DTO which will have all the serialization rules.then nest will take dto and turn it to the json and send it back as response
export class SerializerInterceptor implements NestInterceptor {
// dto is the variable. so you can use this class for different entities
constructor(private dto:any){
}
intercept(context: ExecutionContext, handler: CallHandler): Observable<any> {
// you can write some code to run before request is handled
return handler.handle().pipe(
// data is the incoming user entity
map((data: any) => {
return plainToClass(this.dto, data, {
// this takes care of everything. this will expose things that are set in the UserDto
excludeExtraneousValues: true,
});
}),
);
}
}
Now you use this in the controller:
// See we passed UserDto. for different entities, we would just write a new dto for that entity and our custom interceptor would stay reusable
#UseInterceptors(new SerializerInterceptor(UserDto))
#Get('all')
async findAll(
): Promise<UsersResponse> {
let users = await this.userService.findAll().catch(e => { throw new NotAcceptableException(e) })
let rsp =new UsersResponse()
rsp.users = users
return rsp
}

Resources