NestJS and React - How to send back a HTTP error response - node.js

I am building an app with React and NestJS.
In NestJS, I have an API endpoint to upload a photo like so:
#Controller('api/upload')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
#Post()
#UseInterceptors(
FileInterceptor('image', {
storage: diskStorage({
destination: './myphoto',
filename: (req, file, cb) => {
if (file.mimetype !== 'image/png')
// how do I return an Http error message here?
return cb(null, 'myphoto.png');
},
}),
}),
)
uploadPhoto(#UploadedFile() image) {
}
}
In React, I am using axios and redux-thunk to hit this endpoint like so:
export const uploadPhotoAsync = (data: FormData) => {
return (dispatch: ThunkDispatch<{}, {}, AnyAction>) => {
return axios.post('http://localhost:8080/api/upload', data)
.then(() => {
dispatch({ type: UPLOAD_PHOTO_COMPLETE, error: null });
})
.catch((e) => {
dispatch({ type: UPLOAD_PHOTO_COMPLETE, error: e.message });
});
};
};
As you can see, if there is an error message, we want to catch it and store it in state to display to the user.
My question is: how do I use NestJS to return an error message so that it can be caught by my front-end error handler? In the if statement in my controller, I tried just doing something like throw new UnsupportedMediaException("My error message here") but all that appears to do is throw the error on the server-side and nothing gets sent to the client.
Any help would be greatly appreciated.

Related

How I can use sendFile in fastify?

Server.ts
import fastify from "fastify";
import cookie from 'fastify-cookie';
import apiRoute from './routes/api';
import jwtPlugin from "./plugins/jwtPlugin";
import closePlugin from "./plugins/closePlugin";
import path from "path";
const PORT = parseInt(process.env.PORT!, 10)
export default class Server {
app = fastify({ logger: true })
constructor() {
this.setup()
}
setup() {
this.app.get('/', (request, reply) => {
reply.send({ hello: 'world' })
})
this.app.register(apiRoute, { prefix: '/api' })
this.app.register(cookie)
this.app.register(require('fastify-url-data'))
this.app.register(jwtPlugin)
this.app.register(closePlugin)
this.app.setErrorHandler((error, request, reply) => {
reply.send({
statusCode: error.statusCode,
name: error.name,
message: error.message,
validation: error.validation,
stack: error.stack,
})
})
this.app.register(require('fastify-rate-limit'), {
max: 100,
timeWindow: '1 minute'
})
this.app.register(require('fastify-static'), {
root: path.join(__dirname, 'public')
})
}
version/index.ts
const versionRoute: FastifyPluginCallback = (fastify, opts, done) => {
//Todo 1. get version of app
//Define request body to fastify
fastify.post(
//Route
'/version_info',
async (request, reply) => {
try {
const result: VersionBody[] = await Version.getVersionInfo("testServer")
reply.send(result[0])
} catch (error) {
reply.status(500)
reply.send({
code: 500,
error: "Version Error",
message: error
})
}
}
)
//Todo 2. get update file
//Define request body to fastify
fastify.get('/update_file', function (req, reply) {
reply.sendFile(...)
})
done()
}
export default versionRoute
Hi, I have a question.
I want to send the file, when request to specific url.
So, I install fastify-specific and register.
But, it show error message like,
'FastifyReply<Server, IncomingMessage, ServerResponse, RouteGenericInterface, unknown>' type does not have property 'sendFile'
How I can register reply.sendFile in fastify?
or Is there any way to send file in fastify?
If you know about it, please help me.

How to send buffer in a HTTP request

Im using NodeJS/Angular.
I wanted to render a document using Carbone in NodeJS, and send the result in a http request.
Myfile.js:
router.get('/executeFusion/', async (req, res) => {
try {
// Data to inject
const data = {
firstname: 'BLB',
lastname: 'MAR'
};
carbone.render('./node_modules/carbone/examples/simple.odt', data, async function(err, result) {
if (err) {
return console.log(err);
}
});
const file = './result.odt';
res.download(file);
}
catch (err) {
errorDbHandler.sendErrorHttp(err, res);
}
});
MyComponent.ts:
this.fusionService.executeFusion()
.subscribe(data => {console.log(data)},
(error) => console.log(error));
MyService.ts :
export class FusionService {
constructor(private httpClient: HttpClient, private configService: ConfigService) { }
executeFusion() {
return this.httpClient.get<any>(`${this.configService.getUrlApi()}/api/Fusion/executeFusion/`);
}
}
But the, I got this error:
The main idea here is, generating a document in NodeJS, send it to Angular in order to download it.
The error is related to promise which is mentioned in the question:
this.fusionService.executeFusion().toPromise().then(res => {
console.log(res);
}).catch(e) {
console.log(e);
}
But when you request the Http request in angular, you should use httpClient to make it and it returns the observable like this:
this.fusionService.executeFusion()
.subscribe(data => {console.log(data)},
(error) =>. console.log(error));
And, to make the express server send the file as a downloaded one:
res.download(file);
xport class FusionService {
constructor(private httpClient: HttpClient, private configService: ConfigService) { }
executeFusion() {
return this.httpClient.get<any>(`${this.configService.getUrlApi()}/api/Fusion/executeFusion/`, {response: 'application/txt'}); // type should match the type you are sending from API
}
}
for more details check here.

Firebase https function return "internal error" when called

I'm trying to call an https firebase function from my code.
The issue is that when called my function return an error with code internal the error message is also internal. I tried to search online for this issue the one thing i found on the firebase doc describing this error code is that something is very wrong. So i'm pretty confused.
This is how the function is called :
export const getPlanDetails = (plan: string) => async (dispatch: Dispatch) => {
try {
dispatch({
type: ERROR,
payload: null,
});
dispatch({
type: IS_FETCHING,
payload: true,
});
const request = functions.httpsCallable("getPlanDetails");
const response = await request({ plan: plan });
dispatch({
type: GET_PLAN_DETAIL,
payload: {
currency: response.data["currency"],
name: response.data["name"],
interval: response.data["interval"],
amount: response.data["amount"],
},
});
} catch (error) {
console.log(error);
dispatch({
type: ERROR,
payload: "An error occured getting the plan details",
});
} finally {
dispatch({
type: IS_FETCHING,
payload: false,
});
}
};
This is the screen of the error message that is returned:
I can confirm that the issue do not come from my function beacause when i call it on another project it works just fine. Thus i also checked that the function is uploaded to google could from my firebase console. It also could not be my function thaty is failing beacause i setup custom error messages when it happens.
This is the function code :
exports.getPlanDetails = functions.https.onCall(async (data, context) => {
try {
const plan = await stripe.plans.retrieve(data.plan);
return {
currency: plan["currency"],
name: plan["nickname"],
interval: plan["interval"],
amount: plan["amount"],
};
} catch (error) {
console.log(error);
await reportError(error, { user: context.auth.uid });
throw new functions.https.HttpsError("something went wrong ...", error);
}
});
This is how i init the functions var :
import * as firebase from "firebase";
import { store } from "../redux";
import { AUTH_STATE_CHANGE } from "../redux/types/auth.type";
const firebaseConfig = {
//secret
};
firebase.initializeApp(firebaseConfig);
firebase.analytics();
const auth = firebase.auth();
const firestore = firebase.firestore();
const functions = firebase.functions();
export { auth, firestore, functions };
I think firebase is well initialized beaucause other services like auth or firestore are working fine
Thank you for your help

How to show node js api error in Angular app

In my Node.js app I return an error like this:
app.get('/api/login', (req, res, next) => {
//...
return res.status(400).send({
isSuccess: false,
errors: ["error 1", "error 2"]
})
})
In Angular, how can I get the error?
login() {
const headers = new HttpHeaders().set('Accept', 'application/json').set('Content-Type', 'application/json');
this.http.post('http://localhost:3000/api/login', { username: 'arwels', password: '24899632' }, { headers: headers }).subscribe(response => {
// ok
}, (err) => {
console.log(err) // Bad Reqeust
});
}
When I print err in the error section, it prints Bad Reqeust. Where is the object that is sent by the server?
You can use an HttpInterceptor to capture error responses from your API.
Ref: https://angular.io/api/common/http/HttpInterceptor
Here's an Example:
export class MyHttpInterceptor implements HttpInterceptor {
constructor() {
}
intercept( req: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError(async (_err: HttpErrorResponse, _caught: any) => {
switch (_err.status) {
case 401:
...
break;
case 500:
...
break;
default:
...
break;
}
return of(_err);
})
) as any;
}
}
Since you have full control over how you are returning your errors in your API, you can tailor the HttpInterceptor to work with any error object you want to create on your backend.
Unfavorable Option
If you just want the entire response so you can sniff out the statusCode, you can also just {observe: 'response'} in the HttpHeaders.
this.http.get<HttpResponse<any>>(<url>, {observe: 'response'}).pipe(
tap(resp => console.log('response', resp))
);

Pass Multer validation error to React Component

I am learning Multer along with Redux and React.
My express router is like
router.post('/upload', addressController.uploadImage);
My Multer code is like below
const uploadImage = (req, res, next) => {
const storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, './uploads/');
},
filename: function(req, file, cb) {
cb(null, Date.now() + '-' + file.originalname);
}
});
const fileFilter = (req, file, cb) => {
if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
cb(null, true);
} else {
cb(new Error('Try to upload .jpeg or .png file.'), false);
}
};
const upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 5
},
fileFilter: fileFilter
}).single('addressImage');
upload(req, res, function(error) {
if (error) {
// An error occurred when uploading
res.status(500).json({
message: error // I would like to send error from Here.
});
console.log(error);
} else {
if (req.file.filename === res.req.res.req.file.filename) {
res.status(200).json({
message: 'File uploaded',
file: req.file.filename
});
}
return;
}
});
}
My Action is like below
export const uploadImage = (formData, id, config) => dispatch => {
return Axios.post('/api/address/upload', formData, config)
.then(response => {
dispatch({
type: 'uploadImage',
payload: response.data
});
})
.catch(error => {
dispatch({
type: 'uploadImage',
payload: error // I would like to pass error through here.
});
return false;
});
};
My Reducer is like below
const addressReducer = (state = initialState, action) => {
switch (action.type) {
case 'getAddresses': {
return {
...state,
controlModal: action.payload.valueModal,
address: action.payload.addressData
};
}
case 'uploadImage': {
return {
...state,
uploadImage: action.payload
};
}
default:
return state;
}
};
I would like to get error in my component is like below
render() {
console.log(this.props.uploadImage);
}
const mapStateToProps = state => ( {
uploadImage: state.addressReducer.uploadImage
} );
export default connect(mapStateToProps)(ModalElement);
My console output is like below
How can I get Try to upload .jpeg or .png file. error in my React component while I try to upload file without .jpeg and .png extension ?
you don't have to send 500 status code instead you should send 400
res.status(400).json({
message: error // I would like to send error from Here.
});
The Error does not resolve to a valid json when it is passed through the res.json() and thus, it is stripped out.
So, to access the message "Try to upload .jpeg or .png file.", you should update the Multer code like this:
if (error) {
// An error occurred when uploading
res.status(500).json({
/** error.message => "Try to upload .jpeg or .png file." */
message: error.message // I would like to send error from Here.
});
console.log(error);
}
If you try to upload the file using Postman, you would have the following API response:
{
"message": "Try to upload .jpeg or .png file."
}
Once you have that, you can change your dispatch() like:
.catch(error => {
dispatch({
type: "uploadImage",
/** error.data is the response. We want the `message` property from it */
payload: error.data.message // I would like to pass error through here.
});
return false;
});
Here's how I was able to accomplish it for an avatar microservice I created to work alongside my main application.
WARNING: This explanation goes through the entire flow, so it may be lengthy and redundant if you already understand it.
Create an axios configuration.
First, you must create an axios configuration. By default axios won't show the err returned by the server, instead, it'll just display a generic Error object. You'll need to set up an interceptor.
utils/axiosConfig.js
import get from 'lodash/get';
import axios from 'axios';
export const avatarAPI = axios.create({
baseURL: 'http://localhost:4000/api/', // this makes it easier so that any request will be prepended with this baseURL
});
avatarAPI.interceptors.response.use(
response => response, // returns the server response
error => {
const err = get(error, ['response', 'data', 'err']); // this checks if "error.response.data.err" is present (this is the error returned from the server); VERY IMPORTANT: this "err" property is specified in our express middlewares/controllers, so please pay attention to the naming convention.
return err ? Promise.reject(err) : Promise.reject(error.message); // if the above is present, return the server error, else return a generic Error object
},
);
The flow from client to server back to client.
Client
A user submits a form with formData and this triggers an action creator:
uploadAvatar thunk action creator (which is a promise waiting for a response or error from our server):
import { avatarAPI } from '../utils/axiosConfig'; // import the custom axios configuration that was created above
import * as types from 'types';
const uploadAvatar = formData => dispatch =>
avatarAPI
.post(`avatar/create`, formData) // this makes a POST request to our server -- this also uses the baseURL from the custom axios configuration, which is the same as "http://localhost:4000/api/avatar/create"
.then(({ data }) => {
dispatch({ type: types.SET_CURRENT_AVATAR, payload: data.avatarurl });
})
.catch(err => // this will return our server "err" string if present, otherwise it'll return a generic Error object. IMPORTANT: Just in case we get a generic Error object, we'll want to convert it to a string (otherwise, if it passes the generic Error object to our reducer, stores it to redux state, passes it to our connected component, which then tries to display it... it'll cause our app to crash, as React can't display objects)
dispatch({ type: types.SERVER_ERROR, payload: err.toString() }),
);
Server
The POST request gets picked up by our express route:
app.post('/api/avatar/create', saveImage, create);
The request hits this route: '/api/avatar/create', passes through a middleware function (see below) before passing through another saveImage middleware function, before finally passing through to a create controller.
Client
The server sends a response back to the client. The response from our server passes through the axios configuration interceptor, which determines how to handle the response or the error that was returned from our server. It'll then pass the response or error to the .then() or .catch() of the action creator. The action creator hands it off to the reducer, which updates redux state, which then updates the connected component.
Server (microservice) setup.
Wherever you're defining your express middlewares (ex: bodyParser, cors or passport etc.), you'll want to create a multer middleware function (any time a file is uploaded, it passes through this function first):
middlewares/index.js
app.use(cors({ origin: "http://localhost:3000" }));
app.use(bodyParser.json());
app.use(
multer({
limits: {
fileSize: 10240000,
files: 1,
fields: 1
},
fileFilter: (req, file, next) => {
if (!/\.(jpe?g|png|gif|bmp)$/i.test(file.originalname)) {
req.err = "That file extension is not accepted!"; // this part is important, I'm attaching the err to req (which gets passed to the next middleware function => saveImage)
next(null, false);
}
next(null, true);
}
}).single("file")
);
...etc
services/saveImage.js (after passing through the middleware function above, the result gets passed to this saveImage middleware function)
const fs = require("fs");
const sharp = require("sharp");
const { createRandomString } = require('../../utils/helpers');
module.exports = (req, res, next) => {
// if the file failed to pass the middleware function above, we'll return the "req.err" as "err" or return a string if "req.file" is undefined. In short, this returns an "error.response.data.err" to the client.
if (req.err || !req.file) {
return res.status(400).json({ err: req.err || "Unable to process file." });
}
const randomString = createRandomString();
const filename = `${Date.now()}-${randomString}-${req.file.originalname}`;
const filepath = `uploads/${filename}`;
const setFile = () => {
req.file.path = filepath;
return next();
};
/\.(gif|bmp)$/i.test(req.file.originalname)
? fs.writeFile(filepath, req.file.buffer, (err) => {
if (err) return res.status(400).json({ "Unable to process file." });
setFile();
})
: sharp(req.file.buffer)
.resize(256, 256)
.max()
.withoutEnlargement()
.toFile(filepath)
.then(() => setFile());
};
If the above passes, it then passes req (which contains req.file and all its properties) to the create controller, which in my case, stores a path to the file (/uploads/name-of-file.ext), and a string to retrieve the image (http://localhost:4000/uploads/name-of-file.ext) to my database. In my case, that string is then sent back to the client to be stored to redux state and then updated as the user's avatar (when passing a string into an <img src={avatarurl} alt="avatarurl.png" />, it makes a GET request back to the microservice).
The validation fails.
Lets say a user tried to upload a .tiff image. It passes through our express multer middleware function, which triggers the "That file extension is not accepted!" error, this error is returned via req.err to saveImage, which returns the req.err as: return res.status(400).json({ err: req.err });
On our client-side, that err flows through our axios interceptor:
avatarAPI.interceptors.response.use(
response => response,
error => {
const err = get(error, ['response', 'data', 'err']); // this checks if "error.response.data.err" is present; which it is, and is now "That file extension is not accepted!"
return err ? Promise.reject(err) : Promise.reject(error.message); // that err string gets returned to our uploadAvatar action creator's "catch" block
},
);
The uploadAvatar action creator's catch block is triggered:
.catch(err => // our server "err" is passed to here from the interceptor
dispatch({ type: types.SERVER_ERROR, payload: err.toString() }), // then that "err" is passed to a reducer
);
The reducer picks up the server err and stores it to state:
import * as types from 'types';
const serverInitialState = {
error: '',
message: '',
};
const ServerReducer = (state = serverInitialState, { payload, type }) => {
switch (type) {
case types.RESET_SERVER_MESSAGES:
return { ...state, error: '' };
case types.SERVER_ERROR:
return { ...state, error: payload }; // the server err is stored to redux state as "state.server.error"
case types.SERVER_MESSAGE:
return { ...state, message: payload };
default:
return state;
}
};
export default ServerReducer;
A connected component retrieves this state.server.error and displays it (don't worry too much about the logic here, just that it's a connected component displaying the state.server.error as serverError):
class RenderMessages extends Component {
shouldComponentUpdate = nextProps =>
this.props.serverError !== '' ||
nextProps.serverError !== '' ||
this.props.serverMessage !== '' ||
nextProps.serverMessage !== '';
componentDidUpdate = () => {
const { serverError, serverMessage } = this.props;
if (serverError || serverMessage) {
const notification = serverError
? serverErrorMessage(serverError)
: serverSuccessMessage(serverMessage);
this.renderNotification(...notification);
}
};
renderNotification = ({ noteType, description }) => {
notification[noteType]({
message: noteType === 'error' ? 'Error' : 'Update',
description,
icon: descriptionLayout(noteType),
});
setTimeout(() => this.props.resetServerMessages(), 3000);
};
render = () => null;
}
export default connect(
state => ({
serverError: state.server.error, // retrieving the error from redux state
serverMessage: state.server.message,
}),
{ resetServerMessages },
)(RenderMessages);
The final result is That file extension is not accepted! err is being displayed to the user:

Resources