#Get DTO with multiple parameters in NestJs - nestjs

I'm trying to create a controller action in NestJS accessible via GET HTTP request which receives two params but they are undefined for some reason.
How to fix it?
#Get('/login')
login(#Param() params: LoginUserDto) {
console.log(params)
return 'OK'
}
import { ApiModelProperty } from '#nestjs/swagger';
export class LoginUserDto {
#ApiModelProperty()
readonly userName: string;
#ApiModelProperty()
readonly password: string;
}

In Browser
localhost:3001/Products/v1/user2
Controller like this:
#Controller('Products')
export class CrashesController {
constructor(private readonly crashesService: CrashesService) { }
#Get('/:version/:user')
async findVersionUser(#Param('version') version: string, #Param('user') user: string): Promise<Crash[]> {
return this.crashesService.findVersionUser(version, user);
}
}

Nest doesn't support the ability to automatically convert Get query params into an object in this way. It's expected that you would pull out the params individually by passing the name of the param to the #Param decorator.
Try changing your signature to:
login(#Param('userName') userName: string, #Param('password') password: string)
If you want to receive an object instead consider switching to using Post and passing the object in the request body (which makes more sense to me for a login action anyways).

Right now i am using nestJs on 7.0.0 and if you do this:
#Get('/paramsTest3/:number/:name/:age')
getIdTest3(#Param() params:number): string{
console.log(params);
return this.appService.getMultipleParams(params);
}
the console.log(params) result will be(the values are only examples):
{ number:11, name: thiago, age: 23 }
i hope that after all that time i've been helpful to you in some way !

Let's say you need to pass a one required parameter named id you can send it through header params, and your optional parameters can be sent via query params;
#Get('/:id')
findAll(
#Param('id') patientId: string,
#Query() filter: string,
): string {
console.log(id);
console.log(filter);
return 'Get all samples';
}

#Get('/login/:email/:password')
#ApiParam({name:'email',type:'string'})
#ApiParam({name:'password',type:'string'})
login(#Param() params: string[]) {
console.log(params)
return 'OK'
}
Output
{email:<input email >,password:<input password>}

You can get multiple params and map them to your dto in this way:
#Get('/login')
login(#Param() { userName, password }: LoginUserDto) {
console.log({ userName});
console.log({ password });
return 'OK'
}

Related

Nest JS - Client validation failed : Path is required

I'm posting here because I have been stuck on a problem for few hours now.
I am creating an API using Nest JS 8 and MongoDB, and I test it using Postman. When I want to execute a POST request (http://localhost:3000?nom=Antoine) to insert an object in my database, I have an error (500 : Internal server error) message that says "Client validation failed: nom: Path 'nom' is required (nom is the name of my object's property).
I've wandered every topic about this kind of issue, tried to upgrade my version of Nest, to use a middleware, to make sure the right version of every depedency was installed.
I don't want to remove the "required: true" property because i think it is necessary. I tried to set it to "false", which enabled me to insert the object in the database but without my property 'nom' (name in french).
If you guys have any help, here's my schema :
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export type ClientDocument = Client & Document;
#Schema()
export class Client {
#Prop({ required: true })
nom: string;
}
export const ClientSchema = SchemaFactory.createForClass(Client);
And here is my controller :
import { Body, Controller, Delete, Get, Param, Post, Put} from '#nestjs/common';
import { ClientService } from './client.service';
import { ClientDto } from './dto/client.dto';
import { CreateClientDto } from './dto/create-client.dto';
import { UpdateClientDto } from './dto/update-client.dto';
#Controller('/client')
export class ClientController {
constructor(private readonly clientService: ClientService) {}
#Get()
async index(){
return await this.clientService.findAll();
}
#Get(':id')
async find(#Param('id') id: string) {
return await this.clientService.findOne(id);
}
#Post()
async create(#Body() createClientDto: CreateClientDto) {
console.log(createClientDto);
return await this.clientService.create(createClientDto);
}
#Put(':id')
async update(#Param('id') id: string, #Body() updateClientDto: ClientDto) {
return await this.clientService.update(id, updateClientDto);
}
#Delete(':id')
async delete(#Param('id') id: string) {
return await this.clientService.delete(id);
}
}
Thanks for looking
I found the solution (i still don't know why it works this way tho).
In my client.service.ts, i updated the create function from this :
async create(createClientDto: CreateClientDto): Promise<Client> {
return await new this.model({createClientDto}).save();
}
To this
async create(createClientDto: CreateClientDto): Promise<Client> {
return await new this.model({
...createClientDto,
createdAt: new Date(),
}).save();
}
Thanks for taking the time to answer, I hope this will help

How to write #Param and #Body in one DTO

I am using nestjs and I make an effort DTO's and I generate update-todo.dto.ts like this.
How can I use #Param and #Body together in one DTO?
#Param('id') id: string,
#Body('status') status: TodoStatus
So how to convert my code?
import { TodoStatus } from '../todo.model';
export class UpdateTodoDto {
id: string;
status: TodoStatus;
}
#Patch('/:id/status')
updateTodoStatus(
#Param('id') id: string,
#Body('status') status: TodoStatus
// convert this line
): Todo {
return this.todosService.updateTodoStatus(id, status);
}
You'd need, four components to work together.
A custom decorator to combine the #Param() and #Body() decorators
A DTO to hold the shape of the #Param() DTO
A DTO to hold the shape of the #Body() DTO
A DTO to combine the body and param DTOs
This repository goes through an example with an optional body based on a query parameter.
1
export const BodyAndParam = createParamDecorator((data: unknwon, ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest();
return { body: req.body, params: req.params };
});
2
export class ParamsDTO {
#IsString()
id: string;
}
3
export class BodyDTO {
#IsString()
hello: string;
}
4
export class MixedDTO {
#Type(() => ParamsDTO)
params: ParamsDTO;
#Type(() => BodyDTO);
body: BodyDTO;
}
Use
#Controller()
export class FooController {
#Post()
bar(#BodyAndParam() bodyAndParam: MixedDTO) {
// do stuff here
}
}

How to use DTO in services file of nestjs app

I want to fetch data of a particular person from mongodb on the basis of username and password values,but i got all the data of db instead of particular person which i passed in username and password.
Here is the code of DTO:
import {IsString, IsInt,IsEmail,IsNotEmpty, IsNumberString} from 'class-validator';
export class LoginDto {
#IsEmail()
username: string;
#IsNumberString()
password: string;
}
Here is the code of Services:
async loginsys(credentials: LoginDto): Promise<any> {
const cred = await this.student.find({ credentials }).exec();
return cred;
}
Here is the code of controller:
#Post('login')
log(#Body('credentials') credentials: LoginDto): any {
return this.crudservice.loginsys(credentials);
}
If you want to get exactly one entity, you can use findOne instead of find:
return this.student.findOne(credentials)
find should work if you pass your query to where:
find({where: credentials})`
exec() is not necessary.

How to hash password before inserting?

I am using Nest.Js with TypeORM and I want to hash my password before persisting into the DB.
I tried using the event decorator #BeforeInsert() however it wasn't working for me but later I found that it was not working because I was taking an DTO as an input.
user.controller.ts
#Post()
async create(#Body() data: CreateUserDto, #Res() res) {
// if user already exist
const isUserExist = await this.service.findByEmail(data.email);
if (isUserExist) {
throw new BadRequestException('Email already exist');
}
// insert user
this.service.create(data);
// send response
return res.status(201).json({
statusCode: 201,
message: 'User added Successfully',
});
}
user.service.ts
create(data: CreateUserDto) {
return this.userRepository.save(data)
}
So, I was basically using an DTO to save the data. That's why it was not working.
But what I want to do is map the DTO to user object. So, This is what I did.
#Post()
async create(#Body() data: CreateUserDto, #Res() res) {
// Create User object
const user = new User();
// Map DTO to User object
for (const [key, value] of Object.entries(data)) {
user[key] = value;
}
// if user already exist
const isUserExist = await this.service.findByEmail(user.email);
if (isUserExist) {
throw new BadRequestException('Email already exist');
}
// insert user
this.service.create(user);
// send response
return res.status(201).json({
statusCode: 201,
message: 'User added Successfully',
});
}
create-user.dto.ts
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty } from '#nestjs/swagger';
export class CreateUserDto {
#IsNotEmpty()
#IsString()
#ApiProperty()
readonly firstName: string;
#IsNotEmpty()
#IsString()
#ApiProperty()
readonly lastName: string;
#IsNotEmpty()
#IsString()
#IsEmail()
#ApiProperty()
readonly email: string;
#IsNotEmpty()
#IsString()
#ApiProperty()
readonly password: string;
}
Is there any better approach for this? Because currently I have to write the code in every method to map it.
I would first move all the logic from my controller into the service. This would allow you to reuse the logic in other places, if there are any (since you prefer to have that service class).
Personally, I would avoid writing smart code because it saves me 2 or 3 lines of code. When someone else than you would have to review/refactor it would be a pain in the back. Just write something that's easy to understand.
Third, I would avoid using magic things like beforeInsert. Yeah, it might look smart but you don't make it clear how the pass is generated.
If your entity has the same fields as your DTO what's then the benefit of having the dto. Personally I would avoid exposing entity's password property. Instead, I would have a changePassword(generator: IUserPassGenerator) method in the entity. As for checking the pass, I would have somethingnlike verifyPass(validator: IPassChecker) method.
Another thing that I would avoid would be setters or public props mainly because it might cause your entity to enter into an invalid state. In your case e.g. someone else might change the password property with a md5 hash. After all, they can even change it with an unhashed string.
We can easily map Plain Object Literal to Class Instances by using 'class-transformer' package
Answer:
async create(#Body() data: CreateUserDto, #Res() res) {
const user = plainToClass(User, data)
}
this is a valid approach.
What you can do is extract this logic from the create method and create some kind of Builder object to create User objects from the DTO and vice-versa and call the builder where you need it.

typescript jwt.verify cannot access data

I'm trying to use JWT with nodejs.
My problem is that I can't read the data from the JWT verify function.
I'm using it like this :
//encode when logging in
const token = jwt.sign(
{ user: user },
'secret'
);
// decode when fetching the user from token
const decoded = jwt.verify(req.body.jwtToken, 'secret');
return res.send({
user: decoded.user // <-- error here
});
Here are the typings for the verify method:
export declare function verify(
token: string,
secretOrPublicKey: string | Buffer,
): object | string;
linter Error is :
Property user does not exists on typeof "object|string".
How am I supposed to get the data from the decoded token?
Link to the documentation of the library
When using Typescript, you have to remember everything is typed as in e.g. Java or C#.
object is a superclass that has no knowledge of the property user.
While this code is valid in javascript (you are looking at javascript documentation), it is not in typescript.
To fix this error, cast the decoded token using any.
return res.send({
user: (<any>decoded).user
});
You need to cast the decoded token. Although casting to any will work, you'll also lose type checking on that variable.
A more robust approach is to declare an interface that captures the structure of your decoded token and cast using it.
// token.ts
export interface TokenInterface {
user: {
email: string;
name: string;
userId: number;
};
}
and then you can cast using
decoded as TokenInterface
or more exactly in your case
return res.send({
user: (decoded as TokenInterface).user
});
Notes:
The casting is done at compile time, not runtime
Creating an interface has the added benefit that you keep your type definition in one place. This is especially useful if you want to add a field of that particular type to an existing object. An example of this is adding the token as a field on object of the express.Request type.
Create a user payload interface
interface UserPayload {
id: string;
}
interface JwtExpPayload {
expiresIn: string;
exp: number;
}
Cast to UserPaylod
try {
const jwtPayload = jwt.decode(
req.header('authorization')!
) as JwtExpPayload;
req.jwtPayload = jwtPayload;
const payload = jwt.verify(
req.header('authorization')!,
process.env.JWT_KEY!
) as UserPayload;
req.currentUser = payload;
} catch (err) {
console.log(err);
}
middleware function
export const requireAuth = (
req: Request,
res: Response,
next: NextFunction
) => {
if (req.jwtPayload && Date.now() >= req.jwtPayload!.exp * 1000) {
throw new TokenExpiredError();
}
if (!req.currentUser) {
throw new NotAuthorizedError();
}
next();
};
const userJwt = jwt.sign(
{
id: existingUser.id,
},
process.env.JWT_KEY!,
{ expiresIn: 30 }
);

Resources