I am using typeorm to manage my postgresql database in nestjs. I had to save a json response in the db, so I saved whole response under one column. This is my entity.
/* eslint-disable prettier/prettier */
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
#Entity({name:'travel_bookings'})
export class TravelBookings {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column('uuid')
userId:string;
#Column({ type: 'json' })
booking_response: string;
#Column({ nullable: true })
Status:string;
#CreateDateColumn({ name: 'created_at' }) 'created_at': Date;
#UpdateDateColumn({ name: 'updated_at' }) 'updated_at': Date;
}
`
The booking_response is a long json data, in which there is one id. Now I am creating a function to update Status where the id inside the json matches with the id I provide.
This is one example json response -
{"type":"flight-order","id":"eJzTd9f397V09w8FAAs4AmY%3D","queuingOfficeId":"NCE4D31SB","associatedRecords":[{"reference":"OM9GOU","creationDate":"2023-02-07T11:40:00.000","originSystemCode":"GDS","flightOfferId":"1"}],"flightOffers":[{"type":"flight-offer","id":"1","source":"GDS","nonHomogeneous":false,"lastTicketingDate":"2023-02-08","itineraries":[{"segments":[{"departure":{"iataCode":"BOS","terminal":"C","at":"2023-03-10T22:55:00"},"arrival":{"iataCode":"LIS","terminal":"1","at":"2023-03-11T10:20:00"},"carrierCode":"TP","number":"216","aircraft":{"code":"32Q"},"duration":"PT6H25M","id":"9","numberOfStops":0,"co2Emissions":[{"weight":303,"weightUnit":"KG","cabin":"ECONOMY"}]},{"departure":{"iataCode":"LIS","terminal":"1","at":"2023-03-11T11:45:00"},"arrival":{"iataCode":"MAD","terminal":"2","at":"2023-03-11T14:05:00"},"carrierCode":"TP","number":"1014","aircraft":{"code":"32Q"},"duration":"PT1H20M","id":"10","numberOfStops":0,"co2Emissions":[{"weight":68,"weightUnit":"KG","cabin":"ECONOMY"}]}]},{"segments":[{"departure":{"iataCode":"MAD","terminal":"2","at":"2023-03-11T21:10:00"},"arrival":{"iataCode":"LIS","terminal":"1","at":"2023-03-11T21:30:00"},"carrierCode":"TP","number":"1019","aircraft":{"code":"321"},"duration":"PT1H20M","id":"83","numberOfStops":0,"co2Emissions":[{"weight":68,"weightUnit":"KG","cabin":"ECONOMY"}]},{"departure":{"iataCode":"LIS","terminal":"1","at":"2023-03-12T11:40:00"},"arrival":{"iataCode":"BOS","terminal":"E","at":"2023-03-12T15:20:00"},"carrierCode":"TP","number":"217","aircraft":{"code":"32Q"},"duration":"PT7H40M","id":"84","numberOfStops":0,"co2Emissions":[{"weight":303,"weightUnit":"KG","cabin":"ECONOMY"}]}]}],"price":{"currency":"USD","total":"613.85","base":"184.00","fees":[{"amount":"0.00","type":"TICKETING"},{"amount":"0.00","type":"SUPPLIER"},{"amount":"0.00","type":"FORM_OF_PAYMENT"}],"grandTotal":"613.85","billingCurrency":"USD"},"pricingOptions":{"fareType":["PUBLISHED"],"includedCheckedBagsOnly":false},"validatingAirlineCodes":["TP"],"travelerPricings":[{"travelerId":"1","fareOption":"STANDARD","travelerType":"ADULT","price":{"currency":"USD","total":"613.85","base":"184.00","taxes":[{"amount":"5.60","code":"AY"},{"amount":"4.40","code":"J9"},{"amount":"15.80","code":"JD"},{"amount":"0.70","code":"OG"},{"amount":"16.10","code":"PT"},{"amount":"3.60","code":"QV"},{"amount":"42.20","code":"US"},{"amount":"3.83","code":"XA"},{"amount":"4.50","code":"XF"},{"amount":"7.00","code":"XY"},{"amount":"6.52","code":"YC"},{"amount":"27.60","code":"YP"},{"amount":"292.00","code":"YQ"}],"refundableTaxes":"86.75"},"fareDetailsBySegment":[{"segmentId":"9","cabin":"ECONOMY","fareBasis":"UUSDSI0E","brandedFare":"DISCOUNT","class":"U","includedCheckedBags":{"quantity":0}},{"segmentId":"10","cabin":"ECONOMY","fareBasis":"UUSDSI0E","brandedFare":"DISCOUNT","class":"U","includedCheckedBags":{"quantity":0}},{"segmentId":"83","cabin":"ECONOMY","fareBasis":"UUSDSI0E","brandedFare":"DISCOUNT","class":"U","includedCheckedBags":{"quantity":0}},{"segmentId":"84","cabin":"ECONOMY","fareBasis":"UUSDSI0E","brandedFare":"DISCOUNT","class":"U","includedCheckedBags":{"quantity":0}}]}]}],"travelers":[{"id":"1","dateOfBirth":"1982-01-16","gender":"MALE","name":{"firstName":"JORGE","lastName":"GONZALES"},"documents":[{"number":"00000000","issuanceDate":"2015-04-14","expiryDate":"2025-04-14","issuanceCountry":"ES","issuanceLocation":"Madrid","nationality":"ES","birthPlace":"Madrid","documentType":"PASSPORT","holder":true}],"contact":{"purpose":"STANDARD","phones":[{"deviceType":"MOBILE","countryCallingCode":"34","number":"480080076"}],"emailAddress":"jorge.gonzales833#telefonica.es"}}],"remarks":{"general":[{"subType":"GENERAL_MISCELLANEOUS","text":"ONLINE BOOKING FROM INCREIBLE VIAJES"}]},"ticketingAgreement":{"option":"DELAY_TO_CANCEL","delay":"6D"},"automatedProcess":[{"code":"IMMEDIATE","queue":{"number":"0","category":"0"},"officeId":"NCE4D31SB"}],"contacts":[{"addresseeName":{"firstName":"PABLO RODRIGUEZ"},"address":{"lines":["Calle Prado, 16"],"postalCode":"28014","countryCode":"ES","cityName":"Madrid"},"purpose":"STANDARD","phones":[{"deviceType":"LANDLINE","countryCallingCode":"34","number":"480080071"},{"deviceType":"MOBILE","countryCallingCode":"33","number":"480080072"}],"companyName":"INCREIBLE VIAJES","emailAddress":"support#increibleviajes.es"}]}
This is my function -
async flightCancel(data) {
var amadeus = await new Amadeus({
clientId: process.env.API_KEY,
clientSecret: process.env.API_SECRET
});
// const output=await this.travelBookingsRepo.findOne({where:{ booking_response:"eJzTd9f397V09w8FAAs4AmY%3D"}})
// console.log(output);
this.travelBookingsRepo.update({ booking_response: { id: data } },{Status:'Cancelled'})
return amadeus.booking.flightOrder(data).delete();
}
I found some solutions on web using entityManager , getManager/getConnection but these are deprecated and work no more. If issue is about update function, then I have tried it with findOne and save methods too but still unsuccessful. Please help me in resolving my issue.
If you are using after typeorm#0.3.0, you can not use entityManager , getManager/getConnection.
Instead you could use 'DataSource'
You could check my github.
At the moment, I have a very simple class-validator file with a ValidationPipe in Nest.js as follows:
import {
IsDateString,
IsEmail,
IsOptional,
IsString,
Length,
Max,
} from 'class-validator';
export class UpdateUserDto {
#IsString()
id: string;
#Length(2, 50)
#IsString()
firstName: string;
#IsOptional()
#Length(2, 50)
#IsString()
middleName?: string;
#Length(2, 50)
#IsString()
lastName: string;
#IsEmail()
#Max(255)
email: string;
#Length(8, 50)
password: string;
#IsDateString()
dateOfBirth: string | Date;
}
Lets say in the above "UpdateUserDto," the user passes an "email" field. I want to build a custom validation rule through class-validator such that:
Check if email address is already taken by a user from the DB
If the email address is already in use, check if the current user (using the value of 'id' property) is using it, if so, validation passes, otherwise, if it is already in use by another user, the validation fails.
While checking if the email address is already in use is a pretty simple task, how would you be able to pass the values of other properties within the DTO to a custom decorator #IsEmailUsed
It was pretty simple to solve, I solved it by creating a custom class-validation Decorator as below:
import { PrismaService } from '../../prisma/prisma.service';
import {
registerDecorator,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
} from 'class-validator';
import { Injectable } from '#nestjs/common';
#ValidatorConstraint({ name: 'Unique', async: true })
#Injectable()
export class UniqueConstraint implements ValidatorConstraintInterface {
constructor(private readonly prisma: PrismaService) {}
async validate(value: any, args: ValidationArguments): Promise<boolean> {
const [model, property = 'id', exceptField = null] = args.constraints;
if (!value || !model) return false;
const record = await this.prisma[model].findUnique({
where: {
[property]: value,
},
});
if (record === null) return true;
if (!exceptField) return false;
const exceptFieldValue = (args.object as any)[exceptField];
if (!exceptFieldValue) return false;
return record[exceptField] === exceptFieldValue;
}
defaultMessage(args: ValidationArguments) {
return `${args.property} entered is not valid`;
}
}
export function Unique(
model: string,
uniqueField: string,
exceptField: string = null,
validationOptions?: ValidationOptions,
) {
return function (object: any, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [model, uniqueField, exceptField],
validator: UniqueConstraint,
});
};
}
However, to allow DI to that particular Decorator, you need to also add this to your main.ts bootstrap function:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
...
// Line below needs to be added.
useContainer(app.select(AppModule), { fallbackOnErrors: true });
...
}
Also, make sure to import the "Constraint" in the app module:
#Module({
imports: ...,
controllers: [AppController],
providers: [
AppService,
PrismaService,
...,
// Line below added
UniqueConstraint,
],
})
export class AppModule {}
Finally, add it to your DTO as such:
export class UpdateUserDto {
#IsString()
id: string;
#IsEmail()
#Unique('user', 'email', 'id') // Adding this will check in the user table for a user with email entered, if it is already taken, it will check if it is taken by the same current user, and if so, no issues with validation, otherwise, validation fails.
email: string;
}
Luckily for us, the class-validator provides a very handy useContainer function, which allows setting the container to be used by the class-validor library.
So add this code in your main.ts file (app variable is your Nest application instance):
useContainer(app.select(AppModule), { fallbackOnErrors: true });
It allows the class-validator to use the NestJS dependency injection container.
#ValidatorConstraint({ name: 'emailId', async: true })
#Injectable()
export class CustomEmailvalidation implements ValidatorConstraintInterface {
constructor(private readonly prisma: PrismaService) {}
async validate(value: string, args: ValidationArguments): Promise<boolean> {
return this.prisma.user
.findMany({ where: { email: value } })
.then((user) => {
if (user) return false;
return true;
});
}
defaultMessage(args: ValidationArguments) {
return `Email already exist`;
}
}
Don't forget to declare your injectable classes as providers in the appropriate module.
Now you can use your custom validation constraint. Simply decorate the class property with #Validate(CustomEmailValidation) decorator:
export class CreateUserDto {
#Validate(customEmailValidation)
email: string;
name: string;
mobile: number;
}
If the email already exists in the database, you should get an error with the default message "Email already exists". Although using #Validate() is fine enough, you can write your own decorator, which will be much more convenient. Having written Validator Constraint is quick and easy. We need to just write decorator factory with registerDecorator() function.
export function Unique(validationOptions?: ValidationOptions) {
return function (object: any, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: CustomEmailvalidation,
});
};
}
As you can see, you can either write new validator logic or use written before validator constraint (in our case - Unique class).
Now we can go back to our User class and use the #Unique validator instead of the #Validate(CustomEmailValidation) decorator.
export class CreateUserDto {
#Unique()
email: string;
name: string;
mobile: number;
}
I think your first use case (Check if email address is already taken by a user from the DB), can be solved by using custom-validator
For the second one there is no option to get the current user before the validation. Suppose you are getting the current user using the #CurrentUser decorator. Then once the normal dto validation is done, you need to check inside the controller or service if the current user is accessing your resource.
I am using typeorm with MongoDB. When I am searching for a document by Id with this syntax
const manager = getMongoManager(); const user = await manager.findOne(User, {userId}); I got undefined result and when I use const manager = getMongoManager(); const user = await manager.findOne(User, {}); I got the result with userId expected in the first syntax.
Besides the same syntax works fine with any other criteria other than Id.
The declaration of the Id in the model is: #ObjectIdColumn({ name: '_id' }) #IsNotEmpty() public userId: ObjectID;
For now, I have only one user in the database with the userId that I search for.
Where could be the problem? And how to resolve that?
When you do await manager.findOne(User, {}); You are basically selecting the first entry in your User collection, that's why you get the user you were expecting.
What you need to do is query by the _id field if you have it, or create a custom field to query by, for example:
#Entity()
export class UserEntity
{
#ObjectIdColumn()
_id: string;
#PrimaryColumn()
id: string;
#Column()
public name: string;
}
This way you can do a query like this await manager.findOne(User, { _id: <your _id>});
Or await manager.findOne(User, { id: <your id> });
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.
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'
}