I'm trying to validate the parameters that come in the query of a get request, but for some reason, the validation pipe is unable to identify the elements of the query.
import {
Controller,
Post,
Query,
Body,
UseInterceptors,
Param,
Res,
Logger,
} from '#nestjs/common';
import { Crud, CrudController, Override } from '#nestjsx/crud';
import { OpenScheduleDto } from './open-schedule.dto';
#Crud(Schedule)
export class ScheduleController
implements CrudController<ScheduleService, Schedule> {
constructor(public service: ScheduleService) {}
get base(): CrudController<ScheduleService, Schedule> {
return this;
}
#Override()
async getMany(#Query() query: OpenScheduleDto) {
return query;
}
}
OpenSchedule.dto
import { IsNumber, IsOptional, IsString } from 'class-validator';
export class OpenScheduleDto {
#IsNumber()
companyId: number;
#IsNumber()
#IsOptional()
professionalId: number;
#IsString()
#IsOptional()
scheduleDate: string;
}
When I make a get request to http://localhost:3000/schedules?companyId=3&professionalId=1
I get unexpected errors:
{
"statusCode": 400,
"error": "Bad Request",
"message": [
{
"target": {
"companyId": "3",
"professionalId": "1"
},
"value": "3",
"property": "companyId",
"children": [],
"constraints": {
"isNumber": "companyId must be a number"
}
},
{
"target": {
"companyId": "3",
"professionalId": "1"
},
"value": "1",
"property": "professionalId",
"children": [],
"constraints": {
"isNumber": "professionalId must be a number"
}
}
]
}
That is because when you use #Query parameters, everything is a string. It does not have number or boolean as data types like json. So you have to transform your value to a number first. For that, you can use class-transformer's #Transform:
import { IsNumber, IsOptional, IsString } from 'class-validator';
import { Transform } from 'class-transformer';
export class OpenScheduleDto {
#Transform(id => parseInt(id))
#IsNumber()
companyId: number;
#Transform(id => id ? parseInt(id) : id)
#IsNumber()
#IsOptional()
professionalId?: number;
#IsString()
#IsOptional()
scheduleDate?: string;
}
Note though, that this is unsafe because e.g. parseInt('5abc010') is 5. So you might want to do additional checks in your transformation function.
Related
I am having this error and I am unsure how to resolve it.
The error comes when I try to run getLesson() function, in which I am just trying to get the lesson by a public id.
The error and code are shown below
{
"errors": [
{
"message": "Cannot read property 'prototype' of undefined",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"lesson"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"TypeError: Cannot read property 'prototype' of undefined",
" at FindCursor.cursor.toArray (C:\Users\LENOVO\Projects\practice\graphql-mongodb\src\entity-manager\MongoEntityManager.ts:704:37)",
" at MongoEntityManager. (C:\Users\LENOVO\Projects\practice\graphql-mongodb\src\entity-manager\MongoEntityManager.ts:189:46)",
" at step (C:\Users\LENOVO\Projects\practice\graphql-mongodb\node_modules\tslib\tslib.js:143:27)",
" at Object.next (C:\Users\LENOVO\Projects\practice\graphql-mongodb\node_modules\tslib\tslib.js:124:57)",
" at fulfilled (C:\Users\LENOVO\Projects\practice\graphql-mongodb\node_modules\tslib\tslib.js:114:62)",
" at processTicksAndRejections (internal/process/task_queues.js:95:5)"
]
}
}
}
],
"data": null
}
lesson.service.ts
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Lesson } from './lesson.entity';
import { Repository } from 'typeorm';
import { v4 as uuid } from 'uuid';
#Injectable()
export class LessonService {
constructor(
#InjectRepository(Lesson)
private lessonRepository: Repository<Lesson>,
) {}
async getLesson(id: string): Promise<Lesson> {
return this.lessonRepository.findOne({ id });
}
}
lesson.resolver.ts
import { Resolver, Query, Mutation, Args } from '#nestjs/graphql';
import { LessonService } from './lesson.service';
import { LessonType } from './lesson.type';
#Resolver((of) => LessonType)
export class LessonResolver {
constructor(private lessonService: LessonService) {}
//queries are used to retrieve data and mutations are user to create or modify data
#Query((returns) => LessonType)
lesson(#Args('id') id: string) {
return this.lessonService.getLesson(id);
} }
lesson.type.ts
import { ObjectType, Field, ID } from '#nestjs/graphql';
#ObjectType('Lesson')
export class LessonType {
#Field((type) => ID)
id: string;
#Field()
name: string;
#Field()
startDate: string;
#Field()
endDate: string;
}
lesson.entity.ts
import { Entity, PrimaryColumn, Column, ObjectIdColumn } from 'typeorm';
#Entity()
export class Lesson {
#ObjectIdColumn()
_id: string;
#PrimaryColumn()
id: string;
#Column()
name: string;
#Column()
startDate: string;
#Column()
endDate: string;
}
mongodb v4 has some problem so you need to downgrade to v3
yarn add mongodb#3
npm install mongodb#3
I use version 3.7.1 and it work fine
here: TypeORM error with MongoDB: .find() does not work, error: TypeError: Cannot read property 'prototype' of undefined
It sounds like a quite simple question but I've been searching for a solution for a very long time now. I want to validate an array of UUIDs in an endpoint.
Like this:
["9322c384-fd8e-4a13-80cd-1cbd1ef95ba8", "986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e"]
I have already successfully implemented it as a JSON object { "id": ["9322c384-fd8e-4a13-80cd-1cbd1ef95ba8", "986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e"]} with the following code:
public getIds(
#Body(ValidationPipe)
uuids: uuidDto
) {
console.log(uuids);
}
import { ApiProperty } from '#nestjs/swagger';
import { IsUUID } from 'class-validator';
export class uuidDto {
#IsUUID('4', { each: true })
#ApiProperty({
type: [String],
example: [
'9322c384-fd8e-4a13-80cd-1cbd1ef95ba8',
'986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e',
],
})
id!: string;
}
But unfortunately I can't customize the function that calls that endpoint. So I need a solution to only validate a array of uuids.
instead of type string , write string[]. like below:
import { ApiProperty } from '#nestjs/swagger';
import { IsUUID } from 'class-validator';
export class uuidDto {
#IsUUID('4', { each: true })
#ApiProperty({
type: string[],
example: [
'9322c384-fd8e-4a13-80cd-1cbd1ef95ba8',
'986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e',
],
})
id!: string[];
}
You can build a custom validation pipe for it:
#Injectable()
export class CustomClassValidatorArrayPipe implements PipeTransform {
constructor(private classValidatorFunction: (any)) {}
transform(value: any[], metadata: ArgumentMetadata) {
const errors = value.reduce((result, value, index) => {
if (!this.classValidatorFunction(value))
result.push(`${value} at index ${index} failed validation`)
return result
}, [])
if (errors.length > 0) {
throw new BadRequestException({
status: HttpStatus.BAD_REQUEST,
message: 'Validation failed',
errors
});
}
return value;
}
}
In your controller:
#Post()
createExample(#Body(new CustomClassValidatorArrayPipe(isUUID)) body: string[]) {
...
}
Ensure to use the lowercase functions from class-validator. It has to be isUUID instead of IsUUID. (This is used for the manual validation with class-validator.)
CustomClassValidatorArrayPipe is build modular. You can validate any other type with it. For example a MongoId: #Body(new CustomClassValidatorArrayPipe(isMongoId)) body: ObjectId[]
Result
If you send this:
POST http://localhost:3000/example
Content-Type: application/json
[
"986dcaf4-c1ea-4218-b6b4-e4fd95a3c28e",
"123",
"test"
]
Server will reply:
{
"status": 400,
"message": "Validation failed",
"errors": [
"123 at index 1 failed validation",
"test at index 2 failed validation"
]
}
In NestJs I can not figure out how can get the validaiton error message from ValidationPipe. Always send generic response that includes no "message" parameter like this. :
{
"statusCode": 400,
"timestamp": "2021-05-22T09:59:27.708Z",
"path": "/batchs"
}
In main.ts I put the pipeline defination
app.useGlobalPipes(new ValidationPipe());
And this my dto
export class BatchCreateDto {
#ApiProperty()
#IsNotEmpty({ message: 'username is required' })
code: string;
#ApiProperty({ type: Lang })
title: Lang;
#ApiProperty({ type: Lang })
description: Lang;
#ApiProperty()
startingDate: Date;
#ApiProperty()
duraitonInHours: number;
}
If I send data with emty "code" field to api, the api returns generic Bad Request response that not includes message parameter. If the "code" field contains any data, there is no problem, api insert object to database. So I understand that validation pipe works, but why does not return any detailed message if validation fails.
I want to get the message parameter that includes something like "username is required" or any generic message. Thanks for help.
UPDATE
Body of the request that causes error
{
"code": "",
"title": {
"en": "this is test",
"tr": "bu testtir"
},
"description": {
"en": "some explination...",
"tr": "bazı açıklamalar..."
},
"startingDate": "2021-05-24T07:51:03.197Z",
"duraitonInHours": 14
}
BatchController uses BatchCreateDto
import { Body, Controller, Delete, Get, Param, Post, Put, Query, UsePipes, ValidationPipe } from '#nestjs/common';
import { BatchService } from '../services/batch.service';
import { ApiBody, ApiTags } from '#nestjs/swagger';
import { BatchCreateDto } from 'src/entities/dto/batch.create.dto';
#ApiTags('Batchs')
#Controller('batchs')
export class BatchController {
constructor(private batchService: BatchService) { }
#Post()
public async addBatch(#Body() batch: BatchCreateDto) {
const result = await this.batchService.add(batch);
return result;
}
}
i want to validate alcoholeId and alcoholeName if it is empty.here is my format:
{
"barId": "string",
"barName": "string",
"drinksItems": [
{
"alcoholId": "string",
"alcoholName": "string",
"mixerList": [
{
"mixerId": "string"
}
],
"modifierList": [
{
"modifierId": "string"
}
]
}]
}
For this you need to have an entity and to use #ValidateNested and #Type to cast it correctly (only in case of you use class-transformer).
class Alcohol {
#IsNotEmpty()
alcoholId: string;
#IsNotEmpty()
alcoholName: string;
}
class Bar {
#IsNotEmpty() // <- requires the array to be present
#ValidateNested()
// #Type(() => Alcohol) // <- add only if you use class-transformer
alcohols: Array<Alcohol>;
}
I can not post data json with relation object.
I use mongoDB.
I have 3 table: table_1, table_2, table_3.
I create relation EmbedsMany and EmbedsOne:
- table_2 EmbedsOne table_1.
- table_2 EmbedsMany table_3.
I don't know create post data json to create a new item of table_2 with item of table_1.
import { ..., embedsMany, embedsOne } from '#loopback/repository';
import { Model1, Mode1WithRelations } from './model-1.model';
import { Model3, Model3WithRelations } from './model-2.model';
#model({
settings: {
strictObjectIDCoercion: true,
mongodb: {
collection: 'table_2'
}
}
})
export class Model2 extends Entity {
#property({
type: 'string',
id: true,
mongodb: {
dataType: 'ObjectID' // or perhaps 'objectid'?
}
})
id?: string;
#embedsMany(() => Model3)
model3?: Model3[];
#embedsOne(() => Model1)
model1: Model1;
}
export interface Model2Relations {
// describe navigational properties here
model3?: Model3WithRelations[];
model1: Mode1WithRelations;
}
export type Model2WithRelations = Model2 & Model2Relations;
Repository model 2
import { DefaultCrudRepository } from '#loopback/repository';
import { Model2, Model2Relations } from '../models';
import { DbDataSource } from '../datasources';
import { inject } from '#loopback/core';
export class Model2Repository extends DefaultCrudRepository<
Model2,
typeof Model2.prototype.id,
Model2Relations
> {
constructor(
#inject('datasources.DB') dataSource: DbDataSource,
) {
super(Model2, dataSource);
}
}
Json data post
{
"address": "string",
"status": 1,
"createdAt": "2019-08-04T03:57:12.999Z",
"updatedAt": "2019-08-04T03:57:12.999Z",
"model1": {
"id": "5d465b4cd91e484250d1e54b" /* id of exist item in table_1 */
}
}
Controller is generate by lb4 controller
Expected:
- Item is save success into table_2 with EmbedsOne item of table_1.
Actual:
- Error:
{
"error": {
"statusCode": 422,
"name": "ValidationError",
"message": "The `Model2` instance is not valid. Details: `model1` is not defined in the model (value: undefined).",
"details": {
"context": "Model2",
"codes": {
"project": ["unknown-property"]
},
"messages": {
"model1": ["is not defined in the model"]
}
}
}
}
TL;DR
According to the Loopback 4 team,
#embedsOne
#embedsMany
#referencesOne
#referencesMany
has not implemented yet (2019-Sep-03). (See docs or github)
But I found these decorators and their classes on the sourcecode
So, hopefully, we have to wait untill the implementation is complete. I'll try to update this answer if I got anything new.