Why DTOs are not throwing Validation error in nestjs? - dto

I am using DTO in my code, and I am getting the response as expected but in code DTOs are not throwing error for example
export class CreateCatDto {
readonly name: string;
readonly age: number;
readonly breed: string;
}
In this name, age, the breed is a required field and each has their data type but while running on the postman when I am not passing all the required field or only one field into postman body I am not getting any errors like age is required if I have passed other two fields or I have given value of the parameter not according to data type like:- age : twenty five then also it should throw error but I am not getting.
So, This is class created for
import { ApiProperty } from '#nestjs/swagger';
export class Cat {
#ApiProperty({ example: 'Kitty', description: 'The name of the Cat' })
name: string;
#ApiProperty({ example: 1, description: 'The age of the Cat' })
age: number;
#ApiProperty({
example: 'Maine Coon',
description: 'The breed of the Cat',
})
breed: string;
}
This is controller in which I am importing class and Dto.
import { Body, Controller, Get, Param, Post } from '#nestjs/common';
import {
ApiBearerAuth,
ApiOperation,
ApiResponse,
ApiTags,
} from '#nestjs/swagger';
import { CatsService } from './cats.service';
import { Cat } from './classes/cat.class';
import { CreateCatDto } from './dto/create-cat.dto';
#ApiBearerAuth()
#ApiTags('cats')
#Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
#Post()
#ApiOperation({ summary: 'Create cat' })
#ApiResponse({ status: 403, description: 'Forbidden.' })
async create(#Body() createCatDto: CreateCatDto): Promise<Cat> {
return this.catsService.create(createCatDto);
}
}

I don't know why you selected nestjs-swagger tag, DTO by itself will not validate inputs, maybe you need to use a ValidationPipe with the class-validator package as suggested on docs https://docs.nestjs.com/techniques/validation#validation
It's as simple as putting a decorator on your code now:
import { IsEmail, IsNotEmpty } from 'class-validator';
export class CreateCatDto {
#IsNotEmpty()
#IsString()
readonly name: string;
#IsNotEmpty()
#IsInt()
readonly age: number;
#IsNotEmpty()
readonly breed: string;
You can see all the items here: https://github.com/typestack/class-validator#validation-decorators
And if you want to sanitize the request body, you should use a serializer to help:
https://docs.nestjs.com/techniques/serialization#serialization
This will show or hide your DTO properties based on decorators of each field. You need to install class-transformer package.
import { Exclude } from 'class-transformer';
export class UserEntity {
id: number;
firstName: string;
lastName: string;
#Exclude()
password: string;
constructor(partial: Partial<UserEntity>) {
Object.assign(this, partial);
}
}
It's important to remember that interceptors will run on your request and response.

Related

Is there any way to get the list of validators used in moongoose schema?

I want to get the list of validators that is used in a moongoose schema? Something like
const userModel = {
firstName: {
type:String,
required: true
}
}
// is there any method to get validations like
console.log(userModel.getValidators())
Output:
{
firstName: {required: true ....etc},
}
Once you setup your model using the SchemaFactory.createForClass method from a class with a #Schema decorator as shown in the docs, you can export the schema. If you then, import the schema and access its obj property, you can extract information about the field.
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export type CatDocument = Cat & Document;
#Schema()
export class Cat {
#Prop({ required: true })
name: string;
#Prop()
age: number;
#Prop()
breed: string;
}
export const CatSchema = SchemaFactory.createForClass(Cat);
import { CatSchema } from './cat.schema';
console.log(CatSchema.obj.name.required); // true
console.log(CatSchema.obj.age.type.name); // 'Number'

why MongoDB returns a CastError when I am trying to delete an Item by id?

I am trying to make the DELETE Rest API method but I get CastError.
The problem is with the id it's of type ObjectID and I made it as a number, even when I chose string I got the same error.
Error
[Nest] 15504 - 22/12/2021, 21:34:11 ERROR [ExceptionsHandler] Cast to ObjectId failed for value "{ id: '61c32ba552a7cec272037b12' }" (type Object) at path "_id" for model "City"
CastError: Cast to ObjectId failed for value "{ id: '61c32ba552a7cec272037b12' }" (type Object) at path "_id" for model "City"
at model.Query.exec (C:\Users\ouss\Desktop\coffeeit-assessment\node_modules\mongoose\lib\query.js:4594:21)
at CitiesService.deleteCity (C:\Users\ouss\Desktop\coffeeit-assessment\src\cities\cities.service.ts:46:64)
at CitiesController.deleteCity (C:\Users\ouss\Desktop\coffeeit-assessment\src\cities\cities.controller.ts:25:31)
at C:\Users\ouss\Desktop\coffeeit-assessment\node_modules\#nestjs\core\router\router-execution-context.js:38:29
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at C:\Users\ouss\Desktop\coffeeit-assessment\node_modules\#nestjs\core\router\router-execution-context.js:46:28
at C:\Users\ouss\Desktop\coffeeit-assessment\node_modules\#nestjs\core\router\router-proxy.js:9:17
cities.service.ts
#Delete(':id')
async deleteCity(id) {
const result = await this.cityModel.deleteOne({ _id: id }).exec();
if (!result) {
throw new NotFoundException('Could not find city.');
}
}
cities.controller.ts
#Delete(':id')
deleteCity(#Param() id: number) {
return this.citiesService.deleteCity(id);
}
city.model.ts
import * as mongoose from 'mongoose';
export const CitySchema = new mongoose.Schema({
name: String,
weather: mongoose.SchemaTypes.Mixed,
});
export interface City {
id: number;
name: string;
weather: mongoose.Schema.Types.Mixed;
}
city.entity.ts
import { BaseEntity, Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
#Entity()
export class City extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
weather: any;
}
cities.service.ts
import { ObjectId } from 'mongodb';
...
#Delete(':id')
async deleteCity(id) {
const cityId = new ObjectId(id); // Cast id to MongoDB Object id
const result = await this.cityModel.deleteOne({ _id: cityId }).exec();
if (!result) {
throw new NotFoundException('Could not find city.');
}
}

Can not Query all users because of MongoDB id

I am coding a CRUD API built in TypeScript and TypeGoose.
I get an error saying,
CannotDetermineGraphQLTypeError: Cannot determine GraphQL output type for '_id' of 'User' class. Is the value, that is used as its TS type or explicit type, decorated with a proper decorator or is it a proper output value?
I have a User entity.
import { Field, ObjectType } from 'type-graphql';
import { ObjectId } from 'mongodb';
import { prop as Property, getModelForClass } from '#typegoose/typegoose';
#ObjectType()
export class User {
#Field()
readonly _id: ObjectId;
#Field()
#Property({ required: true })
email: string;
#Field({ nullable: true })
#Property()
nickname?: string;
#Property({ required: true })
password: string;
constructor(email: string, password: string) {
this.email = email;
this.password = password;
}
}
export const UserModel = getModelForClass(User);
And this is how my query resolver looks like.
#Query(() => [User])
async users() {
const users = await UserModel.find();
console.log(users);
return users;
}
How can I solve this? It seems to be like TypeGraphQL doesn't understand what the MongoDB ID is?
Im not sure about this, but maybe ObjectId.toString() help you.
MongoDB doc about ObjectId.toString()

Mongoose & TypeScript - Property '_doc' does not exist on type 'IEventModel'

I'm learning some JavaScript backend programming from a course I'm taking. It focuses on ExpressJS, MongoDB, and GraphQL. Because I like making things more challenging for myself, I decided to also brush up on my TypeScript while I'm at it by doing all the coursework in TypeScript.
Anyway, so I'm using verison 5.5.6 of mongoose and #types/mongoose. Here is my interface for the type of the DB record:
export default interface IEvent {
_id: any;
title: string;
description: string;
price: number;
date: string | Date;
}
Then I create the Mongoose Model like this:
import { Document, Schema, model } from 'mongoose';
import IEvent from '../ts-types/Event.type';
export interface IEventModel extends IEvent, Document {}
const eventSchema: Schema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
date: {
type: Date,
required: true
}
});
export default model<IEventModel>('Event', eventSchema);
Lastly, I have written the following resolver for a GraphQL mutation:
createEvent: async (args: ICreateEventArgs): Promise<IEvent> => {
const { eventInput } = args;
const event = new EventModel({
title: eventInput.title,
description: eventInput.description,
price: +eventInput.price,
date: new Date(eventInput.date)
});
try {
const result: IEventModel = await event.save();
return { ...result._doc };
} catch (ex) {
console.log(ex); // tslint:disable-line no-console
throw ex;
}
}
My problem is that TypeScript gives me an error that "._doc" is not a property on "result". The exact error is:
error TS2339: Property '_doc' does not exist on type 'IEventModel'.
I can't figure out what I'm doing wrong. I've reviewed the documentation many times and it seems that I should have all the correct Mongoose properties here. For the time being I'm going to add the property to my own interface just to move on with the course, but I'd prefer help with identifying the correct solution here.
This might be a late answer but serves for all that come searching this.
inteface DocumentResult<T> {
_doc: T;
}
interface IEvent extends DocumentResult<IEvent> {
_id: any;
title: string;
description: string;
price: number;
date: string | Date;
}
Now when you call for (...)._doc , _doc will be of type _doc and vscode will be able to interpert your type. Just with a generic declaration. Also instead of creating an interface for holding that property you could include it inside IEvent with the type of IEvent.
This is something that I do when I always use typescript alongside mongoose,
first things first we should define interfaces for the schema and model:
export interface IDummy {
something: string;
somethingElse: string;
}
export interface DummyDocument extends IDummy, mongoose.Document {
createdAt: Date;
updatedAt: Date;
_doc?: any
}
second we should create out schema:
const DummySchema = new mongoose.Schema<DummyDocument>({
something: String,
somethingElse: String,
})
finally we are going to use export model pattern for exporting our model as a module from file:
export const DummyModel = mongoose.model<DummyDocument>
Now the problem has fixed and you are not going to see the typescript error, we have manually attached the _doc to our model with generics that the aid of generics.
interface MongoResult {
_doc: any
}
export default interface IEvent extends MongoResult {
_id: any;
title: string;
description: string;
price: number;
date: string | Date;
}
Then you still have to deal with casting the _doc back to your own IEvent...
add _doc with type any to your custom Model interface
interface IUser extends Document {
...
_doc: any
}
a full example is here https://github.com/apotox/express-mongoose-typescript-starter
The _doc field will be a circular reference. So an easy way to go about it is to simply do something like this.
It also avoids infinite circular references by omitting itself in the child record.
No interface extension is required!
export default interface IEvent {
_doc: Omit<this,'_doc'>;
}
For some reasons, the structure of the return type is not included in #types/mongoose lib. So each time you want to de-structure the return object you get an error that the variable in not definition in the interface signature of both document and your custom types. That should be some sort of bug i guess.
The solution is to return the result itself, that will automatically return the data defined in interface (IEvent) without the meta data .
...
try {
const result = await event.save();
return result;
} catch (ex) {
throw ex;
}
...

Loopback 4 - HasMany relation included in fields

I am trying to setup the relation HasMany with the new Loopback 4 framework.
I have the following model:
import {Entity, model, property, belongsTo, hasMany} from
'#loopback/repository';
import {User} from "./user.model";
import {OrderProduct} from "./order-product.model";
#model({
name: 'sales_order'
})
export class Order extends Entity {
#property({
type: 'number',
id: true,
required: true,
})
id: number;
#property({
type: 'number',
required: true,
})
total_amount: number;
#belongsTo(() => User)
user_id: number;
#hasMany(() => OrderProduct, {keyTo: 'order_id'})
products?: OrderProduct[];
constructor(data?: Partial<Order>) {
super(data);
}
}
And the repository as follow:
import {DefaultCrudRepository, repository, HasManyRepositoryFactory, BelongsToAccessor} from '#loopback/repository';
import {Order, OrderProduct, User} from '../models';
import {DbDataSource} from '../datasources';
import {inject, Getter} from '#loopback/core';
import {OrderProductRepository} from "./order-product.repository";
import {UserRepository} from "./user.repository";
export class OrderRepository extends DefaultCrudRepository<
Order,
typeof Order.prototype.id
> {
public readonly user: BelongsToAccessor<
User,
typeof Order.prototype.id
>;
public readonly products: HasManyRepositoryFactory<
OrderProduct,
typeof Order.prototype.id
>;
constructor(
#inject('datasources.db') dataSource: DbDataSource,
#repository.getter(OrderProductRepository)
getOrderProductRepository: Getter<OrderProductRepository>,
#repository.getter('UserRepository')
userRepositoryGetter: Getter<UserRepository>,
) {
super(Order, dataSource);
this.products = this._createHasManyRepositoryFactoryFor(
'products',
getOrderProductRepository,
);
this.user = this._createBelongsToAccessorFor(
'user_id',
userRepositoryGetter,
);
}
}
When I do for example a get on orders, I have the errors: 500 error: column "products" does not exist and in digging a bit more, I can see that the SQL is trying to retrieve the fields products where it is just a relation.
Anybody have an idea if I am doing something wrong?
I am using pg as DB.
I believe this is a bug in LoopBack 4. When you decorate a class property with #hasMany, the decorator defines a model property under the hood. See here:
export function hasMany<T extends Entity>(
targetResolver: EntityResolver<T>,
definition?: Partial<HasManyDefinition>,
) {
return function(decoratedTarget: Object, key: string) {
property.array(targetResolver)(decoratedTarget, key);
// ...
};
}
When the connector is querying the database, it's trying to include the column products in the query, because it thinks products is a property.
The problem is already tracked by https://github.com/strongloop/loopback-next/issues/1909, please consider upvoting the issue and joining the discussion.