How to get error line using Nestjs avoiding duplicate code - javascript

I made a controller on nestjs as below.
#Post()
public async addConfig(#Res() res, #Body() createConfigDto: CreateConfigDto) {
let config = null;
try {
config = await this.configService.create(createConfigDto);
} catch (err) {
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
status: 500,
message: 'Error: Config not created!',
});
}
if (!config) {
return res.status(HttpStatus.NOT_FOUND).json({
status: 404,
message: 'Not Found',
});
}
return res.status(HttpStatus.OK).json({
message: 'Config has been created successfully',
config,
});
}
And there is a service in other file.
public async create(createConfigDto: CreateConfigDto): Promise<IConfig> {
do something 1
do something 2
do something 3
return result;
}
If an internal server error occurs among "do somehting 1,2 or 3" when I request to this api, it will gives me response 500.
But I want to know in which line the error happend.
Therefore I made catch line function.
const getStackTrace = () => {
const obj = {};
Error.captureStackTrace(obj, getStackTrace);
return obj.stack;
};
And I wrap the code with this function, like this
public async create(createConfigDto: CreateConfigDto): Promise<IConfig> {
try{
do something 1
}catch(error){
console.log(error)
getTrace()
}
try{
do something 2
}catch(error){
console.log(error)
getTrace()
}
try{
do something 3
}catch(error){
console.log(error)
getTrace()
}
return result;
}
But the problem is the service code will grow a lot with this trace function.
I want to know whether there is more efficient way avoiding duplicate code using nestJs for this case.
I think interceptor or execption filter help this.
But I don't know how to code for this case , since I 'm about to start using nestjs.
Thank you for reading my question.

Related

Jest test that simulates throwing an Axios exception fails with empty error message

I am trying to write a Jest test to cover a scenario whereby an axios.post (in the code I am trying to test) throws and handles an exception. The test successfully throws an error for a mocked axios instance and the appropriate catch block in the code I am wishing to test is reached. However, the error/exception message is empty. This then causes some assertions that I am trying to do in the test to fail.
The relevant section of the code to test looks as follows:
try {
// Call the token endpoint with the client/user credentials and check the response.
const { status, data } = axios.post(authenticationConfig.tokenEndpoint,
'grant_type=client_credentials', { headers });
if (status === StatusCodes.OK) {
...
}
}
catch(err) {
console.log(JSON.stringify(err));
res.status(StatusCodes.UNAUTHORIZED);
res.json(err.response.data.error);
}
The corresponding test looks like:
it('cannot get access token', async () => {
const response = {
response: {
data: {
error: 'My error'
}
}
};
const req = {
headers: {
'authorization': 'Basic client_id:client_secret'
}
};
mockedAxios.mockImplementation(() => {
throw new Error(response);
});
const provide = await authenticationMiddleware.provide(req, res, next);
await provide(req, res, next);
expect(mockedAxios).toBeCalledTimes(1);
expect(res.status).toHaveBeenCalledTimes(1);
expect(res.status).toHaveBeenCalledWith(StatusCodes.UNAUTHORIZED);
});
The err object in the catch block is logged out as an empty object even though from the test I'm throwing an error with a fully populated object. The test passes if I remove the 'res.json' statement from the catch block.
● authentication middleware › cannot get access token
TypeError: Cannot read property 'data' of undefined
89 | console.log(JSON.stringify(err));
90 | res.status(StatusCodes.UNAUTHORIZED);
> 91 | res.json(err.response.data.error);
Any ideas most welcome please. No doubt the way that I'm mocking Axios and causing it to throw an exception is wrong. The code does enter the catch block but the 'err' object is empty for some reason.
Many thanks.

nodejs app crash on openai dall-e 2 api rejected request

I'm surely dumb, but I'm not able to figure out how to handle openai api rejected requests
( for the context, dall-e 2 is an image generator )
when user tries to generate forbidden images, my nodejs app just exits
async function start(arg) {
try{
// generate image
const response = openai.createImage({
prompt: arg,
n: 1,
size: "1024x1024",
});
// on success response
response.then(res =>{
console.log("ok");
})
response.catch(err =>{
console.log(err);
});
} catch(e){
console.log(e);
}
}
it gives me something like that on the exit :
data: {
error: {
code: null,
message: 'Your request was rejected as a result of our safety system. Your prompt may contain text that is not allowed by our safety system.',
param: null,
type: 'invalid_request_error'
}
}
tried using response.catch and try catch without success, the app just exits everytime
I at least want to ignore this error in the first place
in a second hand, I would like to console.log the given message (data.error.message)
I don't know what to do to by honest, don't even understand why try catch isn't working
With the details given, my guess would be that the Promise returned by getImages is being rejected. You could debug this a bit by adding some additional logs into your .catch callback and catch statement.
How to do this really depends on what you're trying to do with this api, the code as it's currently written would log something and exit no matter what happens.
There's a couple ways to handle this
Use your .catch to handle the error. Utilizing promise chainability you can get something like this
openai.createImage({
prompt: arg,
n: 1,
size: "1024x1024",
user: msg.author.id,
})
.catch((e) => {
if (e.data.error.message.includes('safety system')) {
return 'something'
}
console.error(e)
})
If you need the response object, the asnwer might be different. Looks like the openai package is built on axios and you can pass axios options into it. See https://axios-http.com/docs/handling_errors and the Request Options section of https://npmjs.com/package/openai
EDIT
I found my solution thanks to #JacksonChristoffersen
Basically I was getting http status 400
I just added request options from axios to validate http status smaller than 500
Here's the solution:
async function start(arg) {
try{
// generate image
const response = openai.createImage({
prompt: arg,
n: 1,
size: "1024x1024",
},{
validateStatus: function (status) {
return status < 500; // Resolve only if the status code is less than 500
}
});
// on success response
response.then(res =>{
console.log("ok");
})
response.catch(err =>{
console.log(err);
});
} catch(e){
console.log(e);
}
}

Express.js - Cannot Set Headers with exported function

Learning how to do testing with Express with using Mocha, Chai, Chai-HTTP plugin, and MongoDB with Mongoose. I have a test to purposely detect if MongoDB will send back an error from trying to find a document using a faulty _id value (too short).
I noticed that part of my code is repeating around my other Express routes, and want to reuse it for other routes so I exported it from another module, but now I get this:
Uncaught Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Not sure why I am getting this error. If I have the same code, as the exported function, inside the route code it works fine, but exported it just complains.
Here is the code:
test/route/example.test.js Snippit
it('Bad request with a too short ID string (12 characters minimum)', () => {
// /api/v1/example is the endpoint
// /blah is the param
chai.request(app).get('/api/v1/example/blah').end((err, res) => {
// Insert all the nice assert stuff. :)
});
});
route/example.js Snippit
// Packages
const router = require('express').Router();
// Models (Mongoose Schemas)
const Example = require('../models/example.model');
// Helpers
const { foundMongoError } = require('../helpers/routes');
// -----Snipped-----
router.route('/:exampleId').get((req, res) => {
// Retrieve the exampleId parameter.
const exampleId = req.params.exampleId;
Example.findById(exampleId, (mongoError, mongoResponse) => {
foundMongoError(mongoError, res); // Having an issue
// If I have the same code that makes up foundMongoError inside here, no issues,
// but it will no longer be DRY.
// Check if any responses from MongoDB
if(mongoResponse) {
res.status(200).json(mongoResponse);
} else {
return res.status(404).json({
errorCode: 404,
errorCodeMessage: 'Not Found',
errorMessage: `Unable to find example with id: ${exampleId}.`
});
}
});
});
helpers/routes.js
const foundMongoError = (mongoError, res) => {
if(mongoError) {
return res.status(400).json({
errorCode: 400,
errorCodeMessage: 'Bad Request',
errorMessage: mongoError.message
});
}
};
module.exports = {
foundMongoError
};
That just means you send and response res twice back. The first time you send it back at here:
if(mongoError) {
return res.status(400).json({
errorCode: 400,
errorCodeMessage: 'Bad Request',
errorMessage: mongoError.message
});
}
You sended an response back but the function still continue its work, that means the function moves on till here then:
if(mongoResponse) {
res.status(200).json(mongoResponse);
} else {
return res.status(404).json({
errorCode: 404,
errorCodeMessage: 'Not Found',
errorMessage: `Unable to find example with id: ${exampleId}.`
});
}
Here happens the second response, and here you get the error.
I would rewrite the code like this:
Instead of returning the response, you return an true that means there is an error, otherwise false :
const foundMongoError = (mongoError, res) => {
if(mongoError) {
res.status(400).json({
errorCode: 400,
errorCodeMessage: 'Bad Request',
errorMessage: mongoError.message
});
return true;
}
return false;
};
module.exports = {
foundMongoError
};
Then you can write it like this:
if(foundMongoError(mongoError, res)) return;
The return will stop the function to execute the rest of the code

Is my code the best way to use async await?

Am try to implement and learn async await functions in my login example, but I don't know if is the best, elegant and clean code. I have doubs meanly in catch errors, and if I need implement in a best way the const and functional programing. Can share your opinions?
app.post('/', async (req, res) => {
try {
const { email } = req.body.email; // destructuring
const usuarioEncontrado = await Usuario.findOne({email: email});
// Validate user exist
if (!usuarioEncontrado) { // when not exist return null
throw res.status(404).json({error: 'El usuario no existe'});
}
// validate user pass
if (!bcrypt.compareSync(req.body.password, usuarioEncontrado.password)) {
throw res.status(404).json({error: 'No match'});
}
const token = jwt.sign( // generate token
{
usuario: usuarioEncontrado
},
SEED,
{
expiresIn: (60 * 60)
}
);
res.status(200).json({ // send response
token: token,
usuario: usuarioEncontrado
});
} catch (e) { // send error
res.status(404).json(e);
}
}
THANKS
Your code shows a couple problems:
You're attempting to send double responses. First you do throw res.status(404).json(...). Then, you catch that exception and do res.status(404).json(e) again. That's not right. If you're going to send the response, then just return, don't throw. Or, just throw the exception without sending a response and send the actual error response from the catch handler.
Also, throw res.status(404).json({error: 'No match'}); sends the response and then throws whatever .json() returns which is probably not what you want. That won't be an error object of any kind.
I prefer to centralize the places I send an error response to one place in the request handler. That keeps you from ever attempting to send multiple responses and just makes the flow of the request handler easier to understand (in my opinion).
To do that, I just throw a custom error that may have a custom message/status associated with it and then catch all possible errors in one place. Here's one way to do that. The myError class can be used everywhere in your project, not specific to just one route. The idea is that often when you throw, you know in that context what you want the status and message to be so you set that in the custom Error object and can then use that info in the catch. The catch then has to determine whether it has your custom error or just a regular error. First, I have a reusable Error subclass that lets me throw, not only a message, but also a status value.
// reusable error class that contains a status in addition to the message
class MyError extends Error {
// this static method saves having to compare if it's a custom error object or not
// every time we use this
static sendError(res, e, status = 500) {
if (e instanceof MyError) {
e.sendError(res);
} else {
res.sendStatus(status);
}
}
constructor(msg, status = 500) {
// allow calling with or without new
if (!(this instanceof MyError)) {
return new MyError(msg, status);
}
super(msg);
this.status = status;
}
sendError(res) {
res.status(this.status).send(this.message);
}
}
And, then here's how you use that in your code and centralize the sending of the error status.
app.post('/', async (req, res) => {
try {
const { email } = req.body.email; // destructuring
const usuarioEncontrado = await Usuario.findOne({email: email});
// Validate user exist
if (!usuarioEncontrado) { // when not exist return null
throw MyError('El usuario no existe', 404);
}
// validate user pass
if (!bcrypt.compareSync(req.body.password, usuarioEncontrado.password)) {
throw MyError('No Match', 404);
}
const token = jwt.sign( // generate token
{
usuario: usuarioEncontrado
},
SEED,
{
expiresIn: (60 * 60)
}
);
res.status(200).json({ // send response
token: token,
usuario: usuarioEncontrado
});
} catch (e) { // log and send error response
// e may be either MyError or some other system generated Error
console.log(e);
MyError.sendError(res, e);
}
}

Can't use #Res() with FilesInterceptor()

I am trying to upload a file using builtin multer and after then sending the response back to the user for success or failure. It was all going good until today, when I try to upload the Response wont come. after digging a bit I find out that when i use #res with #UploadedFile it does not execute the controller. I am new to nest.js.
Working.
#Post('uploads/avatar')
async uploadFile(#Req() req, #UploadedFile() avatar) {
console.log(req.body);
if (!req.body.user_id) {
throw new Error('id params not found.');
}
try {
const resultUpload = await this.userService.uploadUserImage(
req.body.user_id,
avatar,
); // returns the url for the uploaded image
return resultUpload;
} catch (error) {
console.log(error);
return error;
}
}
Not Working.
#Post('uploads/avatar')
async uploadFile(#Req() req, #UploadedFile() avatar, #Res() res) {
console.log(req.body);
if (!req.body.user_id) {
throw new Error('id params not found.');
}
try {
const resultUpload = await this.userService.uploadUserImage(
req.body.user_id,
avatar,
); // returns the url for the uploaded image
return resultUpload;
res.send(resultUpload);
} catch (error) {
console.log(error);
res.send(error);
}
}
In nest, you should always avoid injecting #Res because then you lose a lot of things that make nest so great: interceptors, exception filters,...
And actually, in most cases you don't need #Res since nest will automatically handle sending the response correctly.
If you want to send data from a controller method, you can just return the data (Promises and Observables will be resolved automatically as well). If you want to send an error to the client, you can just throw the corresponding HttpException, e.g. 404 -> NotFoundException:
#Post('uploads/avatar')
async uploadFile(#Req() req, #UploadedFile() avatar) {
if (!req.body.user_id) {
// throw a 400
throw new BadRequestException('id params not found.');
}
try {
const resultUpload = await this.userService.uploadUserImage(
req.body.user_id,
avatar,
);
return resultUpload;
} catch (error) {
if (error.code === 'image_already_exists') {
// throw a 409
throw new ConflictException('image has already been uploaded');
} else {
// throw a 500
throw new InternalServerException();
}
}
}
If for some reason you have to inject #Res here, you cannot use the FilesInterceptor. Then you have to configure the multer middleware yourself.
Side note
You can create a custom decorator for accessing the userId:
import { createParamDecorator } from '#nestjs/common';
export const UserId = createParamDecorator((data, req) => {
if (!req.body || !req.body.user_id) {
throw new BadRequestException('No user id given.')
}
return req.body.user_id;
});
and then use it in your controller method like this:
#Post('uploads/avatar')
async uploadFile(#UserId() userId, #UploadedFile() avatar) {
look, when you are using an interceptor, you are handling (with using .handle()) the stream of response (observable) not a whole package of it, but using express #Res actually is somehow getting around the whole flow of response streaming.
this is also explicitly mentioned in nestjs official documents:
We already know that handle() returns an Observable. The stream
contains the value returned from the route handler, and thus we can
easily mutate it using RxJS's map() operator.
WARNING
The response mapping feature doesn't work with the
library-specific response strategy (using the #Res() object directly
is forbidden).

Categories

Resources