NestJS: handle external API call (success or fail ) in a controller - service

The controller method:
/** Create a comment in database */
#ApiOperation({ summary: 'Create a comment in database' })
#Post()
async createComment(
#Query('callId', ParseIntPipe) callId: number,
#Body() dto: CreateCommentDto,
) {
const foundCall = await this.callService.getCall(callId);
if(!foundCall)
throw new NotFoundException('Call not found for this id');
if(!foundCall.crmActivityId)
throw new PreconditionFailedException('crmActivityId must exist for this operation.');
const activityNote = utilsFinalActivityNote(foundCall, dto.message);
// handle PUT service call method if fails
await this.pipeDriveService.putActivity(foundCall.crmActivityId, activityNote);
const comment: Partial<Comment> = {
callId: callId,
message: dto.message
};
// comment we still be saving even if putActivity fails
await this.commentService.createComment(comment);
}
The service method:
async putActivity(id: string, body) {
try {
await this.http.put(
`${process.env.PIPE_DRIVE_BASE_URL}/activities/${id}?api_token=${process.env.PIPE_DRIVE_API_KEY}`,
{ note: body}
).toPromise();
} catch (e) {
throw new PreconditionFailedException(e.response.data.message);
}
}
If the external API call fail it will still save the comment in the database.
How to handle error if my external API call fail ?

Related

How to use refetch method to send different queries using useQuery in react-query

I'm working with react-query in ReactJS to build a website.
I have the following code, where I fetch some data using useQuery():
const { error, data: movie, status, refetch } = useQuery({
queryKey: 'key1',
queryFn: async () => {
return await Promise.all([
axios.get(`API-1.com/get-some-data`), /*getting some data from api-1 */
axios.get(`API-2.com/get-some-data`), /*getting some data from api-2 */
]).then(([api1, ap2]) => {
return { data1: api1, data2: api2 }
})
}
})
The problem I'm facing is getting somtimes a 404 response from one of the apis, so I need to refetch the data just from the other api that doesn't cause the error.
I tried to use the refetch method inside onError, but it can't be used with parameters.
onError: (error) => {
refetch() /* refetch can't be used with parameters */
},
How can I handle this problem?
I ended up doing the following:
Writing the fetching part in an external function (so I can call the function with parameters)
Adding a try-catch block inside the queryFn to handle the errors
/********* this is the function where I fetch the data. *********/.
/********* with a param that tells me if i'll call API-2 or not *********/
const fetchData = async ({ isDataFoundInApi2 }) => {
return await Promise.all([
axios.get(`API-1.com/get-some-data`),
(!isDataFoundInApi2 && axios.get(`API-2.com/get-some-data`)),
]).then(([api1, api2]) => {
return { data1: api1, data2: api2 }
})
/********* the useQuery where the above method is called *********/
/********* first it sends true as a bool value, then if no *********/
/********* data was found in API-2, it sends false *********/
/********* from `catch` to prevent the fetching from API-2 *********/
const { error, data: movie, status } = useQuery({
queryKey: 'key1',
queryFn: async () => {
try {
return await fetchData({})
} catch (error) {
if (error.response?.status === 404)
return await fetchData({ isDataFoundInApi2: true }) // true means call only the first api
}
},
})

Express and MongoDB, how to manualy throw the error in the route controllers?

I'm new to the Express, and I'm trying to apply some error handling at the top level.
In my controllers file, I have a controller to get all tours.
exports.getAllTours = async (req: Request, res: Response) => {
//Execute query
const features = new APIFeatures(Tour.find(), req.query)
.filter()
.sort()
.limitFields()
.paginate();
// Endpoint: http://localhost:8000/api/v1/tours
// Enter a wrong URL here will not even trigger the console.log function.
// But I want to throw the error right here, not in the app.all('*')
console.log("features", features);
if (!features) {
throw new NotFoundError("Tours Not Found");
}
//same problem here.
const tours = await features.query;
console.log("tours", tours.length);
if (!tours) {
throw new NotFoundError("Tours Not Found");
}
res.status(200).json({
status: "success",
result: tours.length,
data: {
tours,
},
});
};
I have a CustomError class that extends the Error class like this.
const httpStatusCode = require("./httpStatusCode");
class CustomError extends Error {
constructor(message: string, statusCode: number, description: string) {
super(description);
//Object.setPrototypeOf(this, new.target.prototype);
this.message = message;
this.statusCode = statusCode;
}
}
module.exports = CustomError;
class NotFoundError extends CustomError {
constructor(message, statusCode) {
super(message, statusCode);
this.message = message;
this.statusCode = httpStatusCode.NOT_FOUND;
}
}
module.exports = NotFoundError;
Also an error handling middleware:
import { NextFunction, Request, Response, ErrorRequestHandler } from "express";
module.exports = (
err: Error,
req: Request,
res: Response,
next: NextFunction
) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || "error";
res.status(err.statusCode).json({
status: err.status,
message: err.message,
});
};
In the end, I use the errorHandler middleware in the app to catch all the errors.
However, the problem is all the errors in the getAllTours controller will not be thrown, instead, they will be thrown in the app.all():
app.use("/api/v1/tours", tourRouter);
app.all("*", (req: Request, res: Response) => {
throw new NotFoundError("Page Not Found");
//next(new AppError(`Can't find ${req.originalUrl} on this server`, 404));
});
app.use(errorHandler);
I know since the endpoint has been changed and thrown in the app.all() make sense. But how can I manually throw an error in the getAllTours controller?
I use express-async-error so I could use the throw keyword in the async function.
I figure it out.
Handle Express async error
I had no idea Express version 4 could not handle the async errors by simply throwing a new error. I'm still not sure if Express Version 5 as it now could handle it.
But I use ExpressJS Async Errors to solve this issue in the end.

Post Api not return any response in nest js

I use nestjs and psql and I want upload files and save the url in the database . when I run the api , data save on db but it doesn’t return any response .
this is my service:
async uploadFiles(files){
if (!files) {
throw new HttpException(
{
errorCode: UploadApplyOppErrorEnum.FileIsNotValid,
message: UploadApplyOppMsgEnum.FileIsNotValid,
},
HttpStatus.UNPROCESSABLE_ENTITY,
);
}
const filedata = OrderFilesData(files);
return filedata.map(async(filePath) => {
let orderFile = new OrderFile();
orderFile.fileUrl = filePath.fileUrl;
orderFile.type = filePath.fileType;
try {
let result = await this.orderFileRepository.save(orderFile);
return await result
} catch (error) {
throw new BadRequestException(error.detail);
}
});
}
and this is my controller
#UploadOrderFilesDec()
#Post('upload')
uploadFiles(#UploadedFiles() files){
return this.ordersService.uploadFiles(files);
}
You can't return an array of async methods without using Promise.all(), otherwise the promises haven't resolved yet. You can either use return Promise.all(fileData.map(asyncFileMappingFunction)) or you can use a regular for loop and await over the results.

need to create a mongoose transaction wrapper which will pass session as parameter to functions that actually do query operations to create or update

So lets say i have a function that takes care of user creation which was previously available to us. Now we just want the function to work in the form of one transaction that creates all users together if success or fails completely when it encounters any error. I am assuming passing just the session to the existing function from the transaction wrapper should take care of this. Any number of such function can be passed together and the wrapper should handle them all as a single transaction.
const createUsers = async (users = []) => {
try {
return UserModel.create(users).then((res) => {
logger.info(`[Start-Up] Created ${res.length} users`);
}, (rej) => {
logger.error(`[Start-Up] Failed to create users - ${rej}`);
throw new Error(rej);
});
} catch (err) {
throw new Error('Failed to create Users', err);
}
};
Use https://www.npmjs.com/package/mongoose-transactions
https://mongoosejs.com/docs/transactions.html
const createUsers = async (users = []) => {
const session = await UserModel.startSession();
try {
await session.withTransaction(() => {
return UserModel.create(users, { session: session }).then((res) => {
logger.info(`[Start-Up] Created ${res.length} users`);
}, (rej) => {
logger.error(`[Start-Up] Failed to create users - ${rej}`);
throw new Error(rej);
});
});
} catch (err) {
throw new Error('Failed to create Users', err);
} finally {
session.endSession();
}
};

Flow(InferError): Cannot call await with 'axios.get(...)' bound to 'p'

I'm getting some Flow errors using axios.
Cannot call await with 'axios.get(...)' bound to 'p' because:
Either property 'error_message' is missing in 'AxiosXHR'.
Or property 'data' is missing in 'Promise'
Here is my code, with an attempted type annotation. (Same error without the AxiosPromise<Object> annotation.) The error is on axios.get(url).
async handleAddressChange(): AxiosPromise<Object> {
const url = `https://maps.googleapis.com/maps/api/place/autocomplete/json?key=${GoogleMapsApiKey}&input=${this.state.address}`;
try {
const { data, error_message } = await axios.get(url);
if (error_message) throw Error(error_message);
this.setState({
addressPredictions: data.predictions,
showPredictions: true
});
} catch (err) {
console.warn(err);
}
}
Funny thing is that in another file axios gives no Flow problems:
export async function loginWithApi(creds: AuthParams) {
const res = await axios.get(ApiUrls.login, { params: creds });
return res.data;
}
I have import type { AxiosPromise, $AxiosXHR } from "axios"; in my file.
Anyone know how to fix this?
In case of error there will be no error_message in returned payload, but the error goes into the catch block.
Also, the handleAddressChange does not returns AxiosPromise, instead it returns implicit promise, as it defined with async
So, something like this:
async handleAddressChange(): Promise<void> {
const url = `https://maps.googleapis.com/maps/api/place/autocomplete/json?key=${GoogleMapsApiKey}&input=${this.state.address}`;
try {
const { data } = await axios.get(url);
this.setState({
addressPredictions: data.predictions,
showPredictions: true
});
} catch (err: AxiosError) {
new Error(err);
}
}
Might work for you. Note the AxiosError definition.
One extra note is that you can add returned payload into the AxiosPromise generic, i.e.:
type TExpectedLoginResponse = {
ok: boolean,
token: string
}
export async function loginWithApi(creds: AuthParams): AxiosPromise<TExpectedLoginResponse> {
const res = await axios.get(ApiUrls.login, { params: creds });
return res.data; // so now flow knows that res.data is type of TExpectedLoginResponse
}
Hope it helps.