In nestjs, how can we change default error messages from typeORM globally? - nestjs

I have this code to change the default message from typeorm when a value in a unique column already exists. It just creates a custom message when we get an error 23505.
if (error.code === '23505') {
// message = This COLUMN VALUE already exists.
const message = error.detail.replace(
/^Key \((.*)\)=\((.*)\) (.*)/,
'The $1 $2 already exists.',
);
throw new BadRequestException(message);
}
throw new InternalServerErrorException();
I will have to use it in other services, so I would like to abstract that code.
I think I could just create a helper and then I import and call it wherever I need it. But I don’t know if there is a better solution to use it globally with a filter or an interceptor, so I don’t have to even import and call it in different services.
Is this possible? how can that be done?
If it is not possible, what do you think the best solution would be?
Here all the service code:
#Injectable()
export class MerchantsService {
constructor(
#InjectRepository(Merchant)
private merchantRepository: Repository<Merchant>,
) {}
public async create(createMerchantDto: CreateMerchantDto) {
try {
const user = this.merchantRepository.create({
...createMerchantDto,
documentType: DocumentType.NIT,
isActive: false,
});
await this.merchantRepository.save(user);
const { password, ...merchantData } = createMerchantDto;
return {
...merchantData,
};
} catch (error) {
if (error.code === '23505') {
// message = This COLUMN VALUE already exists.
const message = error.detail.replace(
/^Key \((.*)\)=\((.*)\) (.*)/,
'The $1 $2 already exists.',
);
throw new BadRequestException(message);
}
throw new InternalServerErrorException();
}
}
public async findOneByEmail(email: string): Promise<Merchant | null> {
return this.merchantRepository.findOneBy({ email });
}
}

I created an exception filter for typeORM errors.
This was the result:
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpStatus,
InternalServerErrorException,
} from '#nestjs/common';
import { Response } from 'express';
import { QueryFailedError, TypeORMError } from 'typeorm';
type ExceptionResponse = {
statusCode: number;
message: string;
};
#Catch(TypeORMError, QueryFailedError)
export class TypeORMExceptionFilter implements ExceptionFilter {
private defaultExceptionResponse: ExceptionResponse =
new InternalServerErrorException().getResponse() as ExceptionResponse;
private exceptionResponse: ExceptionResponse = this.defaultExceptionResponse;
catch(exception: TypeORMError | QueryFailedError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
exception instanceof QueryFailedError &&
this.setQueryFailedErrorResponse(exception);
response
.status(this.exceptionResponse.statusCode)
.json(this.exceptionResponse);
}
private setQueryFailedErrorResponse(exception: QueryFailedError): void {
const error = exception.driverError;
if (error.code === '23505') {
const message = error.detail.replace(
/^Key \((.*)\)=\((.*)\) (.*)/,
'The $1 $2 already exists.',
);
this.exceptionResponse = {
statusCode: HttpStatus.BAD_REQUEST,
message,
};
}
// Other error codes can be handled here
}
// Add more methods here to set a different response for any other typeORM error, if needed.
// All typeORM erros: https://github.com/typeorm/typeorm/tree/master/src/error
}
I set it globally:
import { TypeORMExceptionFilter } from './common';
async function bootstrap() {
//...Other code
app.useGlobalFilters(new TypeORMExceptionFilter());
//...Other code
await app.listen(3000);
}
bootstrap();
And now I don't have to add any code when doing changes in the database:
#Injectable()
export class MerchantsService {
constructor(
#InjectRepository(Merchant)
private merchantRepository: Repository<Merchant>,
) {}
public async create(createMerchantDto: CreateMerchantDto) {
const user = this.merchantRepository.create({
...createMerchantDto,
documentType: DocumentType.NIT,
isActive: false,
});
await this.merchantRepository.save(user);
const { password, ...merchantData } = createMerchantDto;
return {
...merchantData,
};
}
}
Notice that now I don't use try catch because nest is handling the exceptions. When the repository save() method returns an error (actually it is a rejected promise), it is caught in the filter.

Related

How to add JSON data to Jetstream strams?

I have a code written in node-nats-streaming and trying to convert it to newer jetstream. A part of code looks like this:
import { Message, Stan } from 'node-nats-streaming';
import { Subjects } from './subjects';
interface Event {
subject: Subjects;
data: any;
}
export abstract class Listener<T extends Event> {
abstract subject: T['subject'];
abstract queueGroupName: string;
abstract onMessage(data: T['data'], msg: Message): void;
private client: Stan;
protected ackWait = 5 * 1000;
constructor(client: Stan) {
this.client = client;
}
subscriptionOptions() {
return this.client
.subscriptionOptions()
.setDeliverAllAvailable()
.setManualAckMode(true)
.setAckWait(this.ackWait)
.setDurableName(this.queueGroupName);
}
listen() {
const subscription = this.client.subscribe(
this.subject,
this.queueGroupName,
this.subscriptionOptions()
);
subscription.on('message', (msg: Message) => {
console.log(`Message received: ${this.subject} / ${this.queueGroupName}`);
const parsedData = this.parseMessage(msg);
this.onMessage(parsedData, msg);
});
}
parseMessage(msg: Message) {
const data = msg.getData();
return typeof data === 'string'
? JSON.parse(data)
: JSON.parse(data.toString('utf8'));
}
}
As I searched through the documents it seems I can do something like following:
import { connect } from "nats";
const jsm = await nc.jetstreamManager();
const cfg = {
name: "EVENTS",
subjects: ["events.>"],
};
await jsm.streams.add(cfg);
But it seems there are only name and subject options available. But from my original code I need a data property it can handle JSON objects. Is there a way I can convert this code to a Jetstream code or I should change the logic of the whole application as well?

Cancel data insert inside beforeInsert event if it already exists in table

There is a need to insert multiple rows in a table. I am using TypeOrm which has .save() method.
.save() method is used to insert data in bulk, and it checks for the inserting primary key ID. If they exist it updates, otherwise inserts.
I want to do the checking on some other field shortLInk. Which is not possible with .save()
So I tried to do inside beforeInsert() event, things are fine but I need to cancel the isnertions if row is find.
Is there any way to achieve it? I couldn't find anything in documentation.
I can throw an error inside beforeInsert() but it will cancel whole insertions.
async shortLinks(links: Array<string>): Promise<Array<QuickLinkDto>> {
const quickLinks: Array<QuickLinkDto> = links.map((link) => ({
actualLink: link,
}));
return this.quickLinkRepository.save(quickLinks, {});
}
#Injectable()
export class QuickLinkSubscriber
implements EntitySubscriberInterface<QuickLink>
{
constructor(
datasource: DataSource,
#InjectRepository(QuickLink)
private readonly quickLinkRepository: Repository<QuickLink>,
) {
datasource.subscribers.push(this);
}
listenTo() {
return QuickLink;
}
async beforeInsert(event: InsertEvent<QuickLink>) {
const shortLink = await getShortLink(event.entity.actualLink);
const linkExists = await this.quickLinkRepository.findOne({
where: {
shortLink,
},
});
if (linkExists) {
// Discard the insertion if the row already exists
return delete event.entity; // throws error
}
event.entity.shortLink = shortLink;
}
}
When you use a transaction, all the operations are atomic, meaning either all of them are executed or none of them are. you can check for the existence of the shortLink before the insert and if it exists, you can cancel the whole transaction.
async shortLinks(links: Array<string>): Promise<Array<QuickLinkDto>> {
const quickLinks: Array<QuickLinkDto> = links.map((link) => ({
actualLink: link,
}));
const queryRunner = this.quickLinkRepository.manager.connection.createQueryRunner();
try {
await queryRunner.startTransaction();
const result = await this.quickLinkRepository.save(quickLinks, {});
await queryRunner.commitTransaction();
return result;
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
#Injectable()
export class QuickLinkSubscriber
implements EntitySubscriberInterface<QuickLink>
{
constructor(
datasource: DataSource,
#InjectRepository(QuickLink)
private readonly quickLinkRepository: Repository<QuickLink>,
) {
datasource.subscribers.push(this);
}
listenTo() {
return QuickLink;
}
async beforeInsert(event: InsertEvent<QuickLink>) {
const shortLink = await getShortLink(event.entity.actualLink);
const linkExists = await this.quickLinkRepository.findOne({
where: {
shortLink,
},
});
if (linkExists) {
// Discard the insertion if the row already exists
throw new Error('The short link already exists');
}
event.entity.shortLink = shortLink;
}
}

how to prevent file upload when body validation fails in nestjs

I have the multipart form to be validated before file upload in nestjs application. the thing is that I don't want the file to be uploaded if validation of body fails.
here is how I wrote the code for.
// User controller method for create user with upload image
#Post()
#UseInterceptors(FileInterceptor('image'))
create(
#Body() userInput: CreateUserDto,
#UploadedFile(
new ParseFilePipe({
validators: [
// some validator here
]
})
) image: Express.Multer.File,
) {
return this.userService.create({ ...userInput, image: image.path });
}
Tried so many ways to turn around this issue, but didn't reach to any solution
Interceptors run before pipes do, so there's no way to make the saving of the file not happen unless you manage that yourself in your service. However, another option could be a custom exception filter that unlinks the file on error so that you don't have to worry about it post-upload
This is how I created the whole filter
import { isArray } from 'lodash';
import {
ExceptionFilter,
Catch,
ArgumentsHost,
BadRequestException,
} from '#nestjs/common';
import { Request, Response } from 'express';
import * as fs from 'fs';
#Catch(BadRequestException)
export class DeleteFileOnErrorFilter implements ExceptionFilter {
catch(exception: BadRequestException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
const getFiles = (files: Express.Multer.File[] | unknown | undefined) => {
if (!files) return [];
if (isArray(files)) return files;
return Object.values(files);
};
const filePaths = getFiles(request.files);
for (const file of filePaths) {
fs.unlink(file.path, (err) => {
if (err) {
console.error(err);
return err;
}
});
}
response.status(status).json(exception.getResponse());
}
}

Problem trying to show error message with prism and node

I'm doing the backend of a ToDo's project, and for that I'm using the prism orm, the node framework, the typescript language and to test the project I'm using insomnia. i already did all the methods i wanted (create ToDo, get ToDo's, delete ToDo, change ToDo task, change ToDo title) and they are all working, but when i went to test the error message that would occur when i tried to create a ToDo that already exists, in insomia it gives the following message:
Error: Server returned nothing (no headers, no data)
And in VsCode:
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "#<AppError>".] {
code: 'ERR_UNHANDLED_REJECTION'
}
I made the code as follows:
export class AppError {
public readonly message: string;
public readonly statusCode: number;
constructor(message: string, statusCode = 400) {
this.message = message
this.statusCode = statusCode
}
}
import { AppError } from "../../errors/AppError";
import { prisma } from "../../client/prismaClient";
import { CreateToDoType } from "../Types/CreateToDoType";
export class createToDoUseCase {
async execute({title, task}: CreateToDoType) {
const todoAlreadyExists = await prisma.toDo.findUnique({
where: {
title: title
}
})
if (!todoAlreadyExists) {
const newToDo = await prisma.toDo.create({
data: {
title: title,
task: task
}
})
return newToDo
} else {
console.log("01 4")
throw new AppError("Error! ToDo already exists") //aqui está o erro
}
}
}
import {Request, Response} from "express"
import { createToDoUseCase } from "../UseCases/CreateToDoUseCases"
export class createToDoController {
async handle(req: Request, res: Response) {
const { title, task } = req.body
const CreateToDoUseCases = new createToDoUseCase()
const result = await CreateToDoUseCases.execute({title, task}) // aqui está o erro
return res.status(201).json(result)
}
}

Is it possible to override the DEFAULT_TEARDOWN function inside exceptions-zone.ts?

I'm trying to create an application with NestJS framework and I'd like to check if a specific Service is in the application context but, the framework default behavior is exiting the process when it doesn't find the Service inside the context.
I've surrounded the get method with try...catch but it doesn't work because of the process.exit(1) execution.
private async loadConfigService(server: INestApplication): Promise<void> {
try {
this.configService = await server.get<ConfigService>(ConfigService, { strict: false });
} catch (error) {
this.logger.debug('Server does not have a config module loaded. Loading defaults...');
}
this.port = this.configService ? this.configService.port : DEFAULT_PORT;
this.environment = this.configService ? this.configService.environment : Environment[process.env.NODE_ENV] || Environment.DEVELOPMENT;
this.isProduction = this.configService ? this.configService.isProduction : false;
}
I'd like to catch the exception to manage the result instead of exiting the process.
Here's what I came up with:
import { NestFactory } from '#nestjs/core';
export class CustomNestFactory {
constructor() {}
public static create(module, serverOrOptions, options) {
const ob = NestFactory as any;
ob.__proto__.createExceptionZone = (receiver, prop) => {
return (...args) => {
const result = receiver[prop](...args);
return result;
};
};
return NestFactory.create(module, serverOrOptions, options);
}
}
Now, instead of using the default NestFactory I can use my CustomNestFactory
Dirty, but it works
Here is my code to solve same issue:
import { ExceptiinsZone } from '#nestjs/core/errors/exceptions-zone';
ExceptionsZone.run = callback => {
try {
callback();
} catch (e) {
ExceptionsZone.exceptionHandler.handle(e);
throw e;
}
};
You may need to override asyncRun() also for other method.

Resources