Pass Multer validation error to React Component - node.js

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:

Related

Axios - API returning HTML instead of JSON

I'm developing an application using the MERN stack. As I deployed my demo version on render.com, I faced some issues. Sometimes the Axios request is returning HTML, instead of a JSON file.
ItemController.js
const getItem = async (req, res) => {
const { type } = req.params;
if (!type) {
throw new BadRequestError("Please provide all values");
}
const items = await Item.find({ type: type });
res.status(StatusCodes.OK).json({ items });
};
Request
const authFetch = axios.create({
baseURL: "/api/v1",
});
//Request
const findItems = async () => {
dispatch({ type: LOADING_ITEMS_BEGIN });
let url = "/items";
if (state.currentCategory) {
url = url + "/" + state.currentCategory;
}
try {
const { data } = await authFetch.get(url);
console.log(data);
dispatch({ type: LOADING_ITEMS_SUCCESS, payload: { data } });
} catch (error) {
console.log(error);
dispatch({
type: LOGIN_USER_ERROR,
});
}
};
I've checked the request URL, it's always fine. But it seems like the requests do not even reach the server when it sends back HTML, and I get 200 as a status code.
I tried to trace the request, but couldn't find any clue
Edit: It seems fine using Postman, so something is definitely wrong with the request.

Node.js, Typescript - How to get file path from multer.js?

everyone.
I am trying to get filelocation from multer.js so that I can save it inside a database.
What I am trying to do, I have an app, the user can write a post - he can write a title, description and attach a photo. Now when the user creates a new post the post gets created and saved into the database, the photo gets uploaded to the server under the /uploads directory.
However I can't figure out how to save the location of this photo into the database.
The tutorial I am folowing is using not using TypeScript (I am using TypeScript), and he is getting the file path with this
image: req.file.path,
,but I can't do it as I getting getting data from request like so.
const { title, description, creator_id, creator_name } = req.body;
This is my Multer middleware saved in middleware_file_upload.ts
import multer from "multer";
import { v1 } from 'uuid';
const MIME_TYPE_MAP: any = {
'image/png' : 'png',
'image/jpg' : 'jpg',
'image/jpeg' : 'jpeg',
}
export const fileUpload = multer({
limits: {fileSize: 500000},
storage: multer.diskStorage({
destination: (req: any, file: any, callback: any) => {
callback(null, 'uploads')
},
filename: (req: any, file, callback: any) => {
const extension = MIME_TYPE_MAP[file.mimetype];
callback(null, v1() + '.' + extension );
}
}),
fileFilter: (req: any, file: any, callback: any) => {
const isValid = !!MIME_TYPE_MAP[file.mimetype];
let error = isValid ? null : new Error('Invalid mime type !');
callback(error, isValid);
}
});
this is my route that is saved inside the routes_POSTS.ts file
import express from "express";
import { createNewPost, deletePost, getPostByID, getPostsByUserId, getPosts } from '../controllers/controller_POSTS';
import { check } from 'express-validator';
import { fileUpload } from "../middleware/middleware_file_upload";
export const route = express.Router();
route.get('/:postID', getPostByID);
route.get('/user/:userID', getPostsByUserId);
route.post('/',
fileUpload.single('image'),
[
check('title').not().isEmpty(),
check('description').not().isEmpty(),
check('creator_id').not().isEmpty()
],
createNewPost);
route.delete('/:postID', deletePost)
and this is my createNewPost function saved inside controller_POSTS.ts
export const createNewPost = async (req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if(!errors.isEmpty()){
const error = {
message: "Error invalid POST request",
code: 422
}
throw next(error);
}
const { title, description, creator_id, creator_name } = req.body;
const newPost = new POST({
title: title,
description: description,
image: req.file.path, //THIS WHER I WANT TO SAVE THE PHOTO PATH LOCATION
creator_id: creator_id,
creator_name: creator_name,
});
let user: any;
try {
user = await USER.findById(creator_id);
} catch (err) {
const error = {
message: "Creating new post failed, try again !",
code: 500
}
return next(error);
}
if(!user){
const error = {
message: "User couldn't be found by id !",
code: 404
};
return next(error);
}
try{
//FOR WEB BASED MONGO DB
/*
const session = await mongoose.startSession();
session.startTransaction();
await newPost.save({session: session});
user.posts.push(newPost); //SAVE POST INTO USER
await user.save({session: session});
await session.commitTransaction();
*/
//FOR LOCAL MONGODB
await newPost.save();
user.posts.push(newPost); //SAVE POST INTO USER
await user.save();
}catch (err){
const error = {
message: "Creating new post failed in sessions, try again !",
code: 500
}
console.log(err);
return next(error);
}
res.status(201).json({post: newPost});
};

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

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.

TestCafe Triggering Test By POST Request In Express

I had a question that doesn't seem to be answered anywhere.
I am running tests from within my Express.js api. I set up a page that has a button and a field to enter a keyword intended to be used during a testcafe test. My endpoint I set up is /testcafe. But after sending a post request to /testcafe, there is a long delay while test runs and so my question is what is the best next step besides hanging?
Also, can my post request body, which contains the keyword, be directly used in a test like this? Keep in mind it's this pattern:
frontend -> POST request -> Express server -> /testcafe endpoint - test
My problem is after it reaches test, I currently have it attempting to call fetch from within the request logger. Is this right?
import { ClientFunction, Selector } from 'testcafe';
import { RequestLogger, RequestHook } from 'testcafe';
import zlib from 'zlib';
import fetch from 'isomorphic-unfetch';
const url = 'https://www.mysitetesturl.com/page';
class MyRequestHook extends RequestHook {
constructor (requestFilterRules, responseEventConfigureOpts) {
super(requestFilterRules, responseEventConfigureOpts);
}
onRequest (e) {
console.log('in onRequest!')
console.log('========================')
console.log('Request Body')
let buf = e._requestContext.reqBody
console.log(buf.toLocaleString())
}
onResponse (e) {
let buf = Buffer(e.body)
let unzippedBody = Buffer(zlib.gunzipSync(buf))
let payload = unzippedBody.toLocaleString()
fetch('http://myapiipaddress/api/testcafe',
method: 'PUT',
body: JSON.stringify(payload)
)
.then((err, doc) => {
if(err) {
console.log(err)
} else {
console.log(doc)
}
})
}
}
const myRequestHook = new MyRequestHook({
url: url,
method:'get'},
{
includeHeaders: true,
includeBody: true
}
);
fetch('http://myapiipaddress/api/testcafe',
method: 'GET'
)
.then((err, doc) => {
if(err) {
console.log(err)
} else {
fixture`myfixture`
.page(doc.url)
.requestHooks(myRequestHook);
test(`mytest`, async t => {
const inputField = Selector('input');
await t
await t
.wait(5000)
.typeText(inputField, doc.text)
.wait(5000)
}
);
}
})
According to your scheme, you need to organize your code in a different way:
const createTestCafe = require('testcafe');
....
// Choose the necessary body parser for express application
// https://github.com/expressjs/body-parser
app.use(bodyParser.urlencoded({ extended: true }));
...
app.post('/', function (req, res) {
createTestCafe('localhost', 1337, 1338, void 0, true)
.then(testcafe => {
const runner = testcafe.createRunner();
return runner
.src('/tests')
.browsers('chrome')
.run();
})
.then(failedCount => {
testcafe.close();
res.end();
});
})

Call server-side function from ReactJS component

I'm trying to implement a payments system in my ReactJS app that requires server-side code.
I have several questions:
How do you connect a ReactJS app so it can communicate with server-side code?
How would you set up a function in the server-side code?
How would you call that function from a component in a ReactJS app?
For reference, I'm trying to integrate Stripe subscriptions. They give server-side code examples for Node, PHP, etc.
FYI: I am not trying to set up server-side rendering. When you search for server-side code in reference to ReactJS, that's just about all that comes up.
EDIT: I'm particularly interested in a NodeJS solution. I'm also using Webpack.
Just in case, it is helpful to you... I have a React UI that triggers video processing on a Django backend (I mainly use GraphQL through Apollo Client to trigger my server side functions and REST framework when file transfers are involved).
Is REST an option for you?
The middleware I use for file transfers for example:
const SERVER_URL = process.env.SERVER_URL;
const fileTransferApi = (payload) => {
const { authenticated, token, endpoint, body, contentType, method } = payload;
let config = {};
if (authenticated) {
if (token) {
config = {
method,
headers: {
'Content-Type': contentType,
Authorization: `Bearer ${token}`
},
body
};
} else {
throw new Error('No token saved!');
}
}
return fetch(`${SERVER_URL}/api/rest/v1/${endpoint}`, config)
.then((response) =>
response.text().then((text) => ({ text, response }))
).then(({ text, response }) => {
if (!response.ok) {
return Promise.reject(text);
}
return text;
}).catch((err) => console.log(err));
};
export const FILE_TRANSFER_API = Symbol('FILE_TRANSFER_API');
export default () => (next) => (action) => {
const fileTransferApiAction = action[FILE_TRANSFER_API];
if (typeof fileTransferApiAction === 'undefined') {
return next(action);
}
const { payload, types } = fileTransferApiAction;
const [, successType, errorType] = types;
return fileTransferApi(payload).then(
(response) =>
next({
type: successType,
payload: {
text: response,
message: 'ok'
}
}),
(error) => next({
type: errorType,
payload: {
error: error.message || 'There was an error.'
}
})
);
};
My store (I use Redux):
import { createStore, compose, applyMiddleware } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import ReduxThunk from 'redux-thunk';
import ApolloClientSingleton from '../network/apollo-client-singleton';
import fileTransferApi from '../middlewares/fileTransferApi';
import reducer from './reducers';
export default class Store {
constructor(history, initialState = {}) {
this.data = createStore(
reducer,
initialState,
compose(
applyMiddleware(
fileTransferApi,
ReduxThunk.withExtraArgument(ApolloClientSingleton),
routerMiddleware(history),
ApolloClientSingleton.middleware()
),
typeof window === 'object' && typeof window.devToolsExtension !== 'undefined'
? window.devToolsExtension() : (f) => f
)
);
}
}
In my actions:
export const windowsDownload = (authenticated, token) => ({
[FILE_TRANSFER_API]: {
types: [WINDOW_DOWNLOAD_REQUEST, WINDOW_DOWNLOAD_SUCCESS, WINDOW_DOWNLOAD_FAILURE],
payload: {
endpoint: 'file_transfer/download/windows',
contentType: 'text/csv',
method: 'get',
body: null,
authenticated,
token
}
}
});
This REST setup enables me to send requests (POST video, GET csv...) from my React UI to my Django server. Can't you set up some REST calls between your app and your server?

Resources