Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non eager joins fail on serverless #313

Open
leohajder opened this issue Oct 30, 2019 · 12 comments
Open

Non eager joins fail on serverless #313

leohajder opened this issue Oct 30, 2019 · 12 comments

Comments

@leohajder
Copy link

No description provided.

@leohajder leohajder changed the title Not eag Non eager joins fail on serverless Oct 30, 2019
@leohajder
Copy link
Author

Locally, run via nodemon, everything works as expected. I can specify join options, and join relations in the url if they are not eager, like expected.
?join=applications&join=applications.type&join=applications.user

If I run via sls offline start, the application breaks, saying:
"applications" alias was not found. Maybe you forgot to join it?

If I set eager to true in join options for each join, everything works.
I don't want to use eager joins everywhere.

@Crud({
  model: { type: Ad },
  query: {
    exclude: ['createdAt', 'updatedAt'],
    join: {
      'applications': {},
      'applications.user': {},
      'applications.type': {},
// ...
    },
    limit: 10,
    cache: 1,
  },
  routes: {
    only: ['getOneBase', 'getManyBase'],
  },
})
@Controller('api/public/ads')
@ApiUseTags('Public/Ads')
export class AdPublicController implements CrudController<Ad> {
  constructor(public service: AdService) {}
}
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, ManyToMany, JoinTable, OneToMany } from 'typeorm';
import { IsArray, IsBoolean, IsDate, IsDecimal, IsInt, IsOptional, IsString, ValidateNested } from 'class-validator';
import { ApiModelProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { AdApplication } from './adApplication.entity';

@Entity('ad')
export class Ad {
  @ApiModelProperty({ required: false, description: 'Id', readOnly: true })
  @PrimaryGeneratedColumn({ type: 'int', name: 'id' })
  id: number;

  @ApiModelProperty({ required: false, description: 'Date created', readOnly: true, type: Date, example: '2019-09-12 14:22:57.358854000' })
  @CreateDateColumn({ nullable: true })
  createdAt?: Date;

  @ApiModelProperty({ required: false, description: 'Date updated', readOnly: true, type: Date, example: '2019-09-12 14:22:57.358854000' })
  @UpdateDateColumn({ nullable: true })
  updatedAt?: Date;

// ...

  @ApiModelProperty({ required: false, isArray: true, type: [AdApplication], description: 'Array of applications' })
  @IsArray()
  @IsOptional({ always: true })
  @ValidateNested({ each: true })
  @Type(() => AdApplication)
  @OneToMany(type => AdApplication, adApplication => adApplication.ad)
  applications: AdApplication[];
}
import { CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, Unique } from 'typeorm';
import { IsDefined, IsNotEmpty } from 'class-validator';
import { ApiModelProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { Ad } from './ad.entity';

@Entity('adApplication')
export class AdApplication {
  @ApiModelProperty({ required: false, description: 'Id', readOnly: true })
  @PrimaryGeneratedColumn({ type: 'int', name: 'id' })
  id: number;

  @ApiModelProperty({ required: true, description: 'Applied user', type: User })
  @IsNotEmpty()
  @IsDefined()
  @Type(() => User)
  @ManyToOne(type => User, user => user.applications)
  user: User;

  @ApiModelProperty({ required: true, description: 'Ad', type: Ad })
  @IsNotEmpty()
  @IsDefined()
  @Type(() => Ad)
  @ManyToOne(type => Ad, ad => ad.applications)
  ad: Ad;

  @ApiModelProperty({ required: true, description: 'Ad application type', type: AdApplicationType })
  @IsNotEmpty()
  @IsDefined()
  @Type(() => AdApplicationType)
  @ManyToOne(type => AdApplicationType)
  type: AdApplicationType;

  @ApiModelProperty({ required: false, description: 'Date created', readOnly: true, type: Date, example: '2019-09-12 14:22:57.358854000' })
  @CreateDateColumn({ nullable: false })
  createdAt: Date;
}
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { IsDefined, IsOptional, IsString } from 'class-validator';
import { ApiModelProperty } from '@nestjs/swagger';

@Entity('adApplicationType')
export class AdApplicationType {
  @PrimaryGeneratedColumn({ type: 'int', name: 'id' })
  id: number;

  @ApiModelProperty({ required: false, description: 'Date created', readOnly: true, type: Date, example: '2019-09-12 14:22:57.358854000' })
  @CreateDateColumn({ nullable: true })
  createdAt?: Date;

  @ApiModelProperty({ required: false, description: 'Date updated', readOnly: true, type: Date, example: '2019-09-12 14:22:57.358854000' })
  @UpdateDateColumn({ nullable: true })
  updatedAt?: Date;

  @ApiModelProperty({ required: true, description: 'Name', maxLength: 128 })
  @IsString()
  @IsDefined()
  @IsDefined({ always: true })
  @Column({ nullable: false, length: 128 })
  name: string;

  @ApiModelProperty({ required: false, description: 'Description' })
  @IsString()
  @IsOptional()
  @Column({ nullable: true })
  description: string;
}

@leohajder leohajder reopened this Oct 30, 2019
@leohajder
Copy link
Author

Also, on sls, only the last join parameter is actually joined, if the joins are not eager...

@michaelyali
Copy link
Member

what about using join[] instead of join?

@leohajder
Copy link
Author

I tried, but it ddn't work. I follow this https://github.com/nestjsx/crud/wiki/Requests#join

The strange thing is that this only fails on serverless. And, even stranger, it works if all joins are eager.

@michaelyali
Copy link
Member

nothing strange here. It looks like serverless doesn't support express like query parsing.
Because express itself will parse join as an array

@leohajder
Copy link
Author

leohajder commented Oct 30, 2019

Hmmm, true... Nest.js runs on serverless via an ExpressAdapter as the handler.

// src/lambda.ts
import { Handler, Context } from 'aws-lambda';
import { Server } from 'http';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ExpressAdapter } from '@nestjs/platform-express';
import * as serverless from 'aws-serverless-express';
import { proxy } from 'aws-serverless-express';
import { ValidationParamPipe } from './pipes/validation.param.pipe';
import { HttpExceptionFilter } from './exceptions/exceptions.filter';
import { NetworkInterceptor } from './interceptors/network.interceptor';

const binaryMimeTypes: string[] = [];

let cachedServer: Server;

process.on('unhandledRejection', (reason) => {
    // tslint:disable-next-line:no-console
    console.error(reason);
});

process.on('uncaughtException', (reason) => {
    // tslint:disable-next-line:no-console
    console.error(reason);
});

function bootstrapServer(): Promise<Server> {
    const expressApp = require('express')();
    const adapter = new ExpressAdapter(expressApp);
    return NestFactory.create(AppModule, adapter)
        .then(app => app.enableCors())
        .then(app => app.useGlobalPipes(new ValidationParamPipe()))
        .then(app => app.useGlobalInterceptors(new NetworkInterceptor()))
        .then(app => app.useGlobalFilters(new HttpExceptionFilter()))
        .then(app => app.init())
        .then(() => serverless.createServer(expressApp));
}

export const handler: Handler = async (event: any, context: Context) => {
    cachedServer = await bootstrapServer();
    return proxy(cachedServer, event, context, 'PROMISE').promise;
};
# severless.yml
# ...
functions:
  main:
    handler: src/lambda.handler
    events:
      - http:
          method: any
          path: /api/{proxy+}
          cors: true

But, I guess you already know all this... 😃

Have you had similar problems? Any more advice to try?

@michaelyali
Copy link
Member

CodeGenieApp/serverless-express#214
yeah, it's kinda very sad that it's still an issue there

@H4ad
Copy link

H4ad commented Oct 30, 2019

I have same issue with Azure Function HTTP, join only works when i set eager equals true.

@dakt
Copy link

dakt commented Oct 31, 2019

Woaah, same issue here. Any hopes for the future?

@alexmantaut
Copy link

alexmantaut commented Dec 10, 2019

Besides setting the nested entity as eager... Is there any other workaround for this issue?

TBH, I am not sure if waiting for Amazon to fix the issue will help. It is a bit concerning how aws-serverless is maintained by Amazon... for what I see, the last commit on the aws-serverless-express was done in April...

@alexmantaut
Copy link

alexmantaut commented Dec 11, 2019

I raised a support issue to AWS to ask for the status of their aws-serverless-express project. The response was that it will still be maintained, but they will do some "roadmap with prioritization" of the issues in Github "sometime in 2020"...

So, it would be nice not to have to wait for them to get around to fix this... would it be an option to use multiple joins with sligtly different names, to avoid the params from dissapearing?

examples:

?join1=applications&join2=applications.type&join3=applications.use 
or
?join[0]=applications&join[1]=applications.type&join[3]=applications.use 
or
?join-application=applications&join-application-type=applications.type&join-application-use=applications.use 
or 
?join[application]=applications&join[application-type]=applications.type&join[application-use]=applications.use 

What do you think?

@michaelyali
Copy link
Member

So in that case we need to add a support for joins as an object and not only arrays

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants