7

I have NestJS application I have to get the query from the URL. Problem is that my creationDate is an object and I can't get it as the nested object via @Query.

Here is example of the route:

xxx/yyy/orders?key1=value&key2=value&key3=value&createdAt[lte]=2021-07-01&createdAt[gte]=2011-03-30&orderBy=desc&limit=500

I am trying to get createdAt[lte] and createdAt[gte] as the nested object

export interface QueryI {
key1: string;
key2: string;
key3: string;
key4: string;
createdAt: {
    lte: string,
    gte: string,
}
orderBy: 'desc' | 'asc';
limit: number;
}

Here is my controller:

@Get('route')
getAll(
    @Query() query: QueryI): Promise<void> { 
    return this.myService.findAll(query);
}

but this gives me following result

{

key1: 'value',        
  key2: 'value',
  key3: 'value',       
  key4: 'value',
  'createdAt[lte]': 'some date',
  'createdAt[gte]': 'some date',
  orderBy: 'desc',
  limit: '500'

}

I have tried JSON.stringify and consulted similar questions on SOW but no luck.

Thank you

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
LaCodeM
  • 645
  • 7
  • 23
  • What's your HTTP engine? And what are you sending the request through? It may not be encoding the query parameters correctly – Jay McDoniel Oct 27 '21 at 15:52
  • Not sure what do you mean by HTTP engine but I use postman to test it. – LaCodeM Oct 28 '21 at 06:27
  • 1
    By HTTP engine, I mean express or fastify, which are the underlying engines Nest can use. Using `curl` to send the requests this worked: `curl 'http://localhost:3000/?key1=value1&key2%5Binner1%5D=hello&key2%5Binner2%5D=world'` while this didn't: `'http://localhost:3000/?key1=value&key2[inner1]=hello&key2[inner2]=world'`. So like I said, it might be a query parameter encoding issue – Jay McDoniel Oct 28 '21 at 15:21
  • I use Fastify with NestJs. I can't change the encoding of the URL coming. I am building a connector that connect 3 different API. This is the incoming request that I have no control over. Any chance I can make it work? Thanks – LaCodeM Oct 29 '21 at 06:17

2 Answers2

2

EDIT

In the end I have implemented diferent classes and used @Pipe to validate my query: Here is the code:

pipe.ts

@Injectable()
export class ValidateQueryPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    let incomingQuery = value as IncomingQuery;
    let mappedQuery = new QueryParameters()
    mappedQuery.key1 = incomingQuery.key1
    mappedQuery.key2= incomingQuery.key2
    // other mapped valus
    mappedQuery.createdAt = new CreatedAt(incomingQuery['createdAt[lte]'], incomingQuery['createdAt[gte]'])        
    return mappedQuery;
  }

controller.ts

@Get(':someurl/someurl')
getAllOrders(
    @Query(ValidateQueryPipe) query: QueryParameters): Promise<Observable<any>> { 
    return this.service.findAll(erpCode, query);
}

query-parameters.ts

import { CreatedAt } from "./created-at";

export class QueryParameters {
    key1: string;
    key2: string;
    createdAt: CreatedAt;
}

created-at.ts

export class CreatedAt {
    lte: string;
    gte: string;

    constructor(lte: string, gte: string){
        this.lte = lte;
        this.gte = gte;
    };
}

Well I have found a partial solution and I will post it here untill better answer comes along: I am extracting the nested properties into seperate queries and creating my model. It's not perfect but it's doing the job for now...

controller.ts

 getAllOrders(
    @Query() query,
    @Query('createdAt[lte]') lte: string, 
    @Query('createdAt[gte]') gte: string): Promise<void> { 
    const mappedObject = new QueryParameters(query, lte, gte)
    return this.orderService.findAll(erpCode, mappedObject);
}

createdAtModel.ts

    export class CreatedAt {
    lte: string;
    gte: string;

    constructor(lte: string, gte: string){
        this.lte = lte;
        this.gte = gte;
    };
}

my-model.ts

import { CreatedAt } from "./created-at";

export class QueryParameters {
    //other keys
    createdAt: CreatedAt;

    constructor(query, lte, gte){
        //other keys
        this.createdAt = new CreatedAt(lte, gte)
    }
}

I am sure that better answer lies in using the NestJS validation pipes but I didn't manage to find the one that works for me.

LaCodeM
  • 645
  • 7
  • 23
1

Switch QueryI from an interface to a class.

Working on NestJS 7.

Url used for testing

http://localhost:3000/api/v1/store/search?nestedObj[key1]=yolo&nestedObj[key2]=sup%20bruh&el=test

DTO Used

export class TestDto {
  el: string;
  nestedObj: {
    key1: string;
    key2: string;
  };
}

Controller

@Controller('v1/store')
@UsePipes(new ValidationPipe({ exceptionFactory: getFormattedErrors }))
@UseFilters(DomainExceptionFilter)
export class TestController{
@Get('search')
  public async searchStore(@Query() testDto: TestDto) {
    console.log(testDto);
    return 1;
  }
}
// Console log - { nestedObj: { key1: 'yolo', key2: 'sup bruh' }, el: 'test' }

SPS
  • 733
  • 1
  • 8
  • 16
  • Thank you for your answer but this doesn't work, I am not sure why ... Unless the decorators are important for it to work in which case plase add the imports. EDIT: my nestjs/common is 8.0.0 – LaCodeM Oct 28 '21 at 06:25
  • 1
    Did you use a validation pipe? If not, use one, then try. – SPS Oct 28 '21 at 06:38
  • I have just added this one @UsePipes(new ValidationPipe({ transform: true })), it still didn't work... I am new to NestJS can you give me more info pls – LaCodeM Oct 28 '21 at 08:00