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;
}
}
Related
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"
]
}
I need to change the error response 404 error for exemple.
This is de case:
#Entity('role')
export class RoleEntity extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: 'varchar', length: 100, nullable: false })
name: string;
}
#Injectable()
export class RoleService extends TypeOrmCrudService<RoleEntity> {
constructor(#InjectRepository(RoleEntity) repo) {
super(repo);
}
}
#Crud({
model: {
type: RoleEntity,
},
})
#Controller('role')
export class RoleController implements CrudController<RoleEntity> {
constructor(public service: RoleService) {}
}
When i try to get an id not created in the base i got the error
{
"statusCode": 404,
"message": "RoleEntitynot found",
"error": "Not Found"
}
I want to change this error to something like this:
{
"success": false,
"message": "The given id was not found in the database",
}
What is the best way to do this?
Override nestjs/crud getOneBase method (Doc nestjs/crud)
#Override('getOneBase')
getOneAndDoStuff(
#ParsedRequest() req: CrudRequest,
) {
// Catch below error and return your message
this.base.getOneBase(req)
return ...;
}
You can wrap the base call in try catch statement like below, by this you can override errors object.
#Override()
async getOne(#ParsedRequest() req: CrudRequest) {
try {
return await this.base.getOneBase(req);
} catch (error) {
throw new HttpException(
{
returnCode: error.response.statusCode,
message: error.response.message,
type: ResponseTypes[error.response.statusCode],
},
error.status
);
}
}
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.
I'm using NestJs + MongoDB + Mongoose, and I would like to get all the records in MongoDB with the record that I send by parameter, but I'm not getting it, I'm a beginner. How could I get all records from the same category?
I send the category ID in the request, but I don't receive all the records for that category, could you help me?
I need this:
GET /users/food
and return this:
{
"password": "123",
"name": "Brian",
"adress": "",
"email": "a#a",
"category": "food",
"cpfOrCnpj": "string"
},
{
"password": "123",
"name": "Margo",
"adress": "",
"email": "a#a",
"category": "food",
"cpfOrCnpj": "string"
}
my code:
my service:
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { User } from './user.model';
import { Model } from 'mongoose';
#Injectable()
export class UserService {
constructor(#InjectModel('User') private readonly userModel: Model<User>) {}
async create(doc: User) {
//Ok
const result = await new this.userModel(doc).save();
return result.id;
}
async find(id: string) {
return await this.userModel.findById(id).exec();
}
async update(user: User) {
//Test
return await this.userModel.findByIdAndUpdate(user);
}
}
my controller:
import { Body, Controller, Get, Param, Post, Put } from '#nestjs/common';
import { UserService } from './user.service';
import { User } from './user.model';
#Controller('user')
export class UserController {
constructor(private service: UserService) {}
#Get(':id')
async find(#Param('category') id: string) {
return this.service.find(id);
}
#Post('create')
create(#Body() user: User) {
return this.service.create(user);
}
#Put('update')
update(#Body() user: User) {
return this.service.update(user);
}
}
In this function
find(id: string) {
return this.userModel.findById(id).exec();
}
you're searching by the _id, findById method is used to filter by the _id of the document
I think category is not the _id of your document here
so, you need to use the normal find method, and pass an object to it
find(id: string) { // id is not _id here, I suggest you to name it category instead
return this.userModel.find({ category: id }).exec();
}
Note, you don't need the async/await here, as you are returning the promise itself
hope it helps
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.