Why formData does not work with multiple files? - node.js

I'm facing a problem with a React project I'm working on: I'm trying to upload multiple images to a Node Express API. I'm using a formData object and I used the append() method to append the form fields from the component State.
In the Express code I'm using multer, all the attributes in the req.body are there but req.files is empty.
I changed the code to upload a single image also using formData() and it works; the problem seems to be only when I try with multiple files using the formData object. I also tested using a regular form (not react) and that also worked!
I'm wondering if there is something I'm missing when I use formData with a file input with multiple files?
import React, { Component } from "react";
import axios from "axios";
class Form extends Component {
constructor() {
super();
this.state = { images: {} };
}
onChangeImages = e => {
this.setState({ images: e.target.files })
};
onSubmit = e => {
e.preventDefault();
const { images } = this.state;
const formData = new FormData();
formData.append("images", images);
axios
.post("/api/post/create", formData)
.then(res => console.log(res.data))
.catch(err => console.error(err));
};
render() {
return (
<form onSubmit={this.onSubmit}>
<input
onChange={this.onChangeImages}
type="file"
name="images"
multiple
accept="image/png, image/jpeg, image/jpg"
/>
<br />
<input type="submit" value="Send" />
</form>
);
}
}
export default Form;
Express code
const express = require('express');
const router = express.Router();
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
router.post('/create', upload.array('images', 2), (req, res) => {
console.log(req.files);
console.log(req.body);
res.status(200).json(req.body);
});
module.exports = router;

formData.append("images", images);
You need to append each file in turn. FormData doesn't support a FileList object.
for (let i = 0 ; i < images.length ; i++) {
formData.append("images", images[i]);
}

there are a lot of other alternatives than using the backend. You can use cloudinary or firebase to upload all images and get their urls and let your backend store those URLs in the database.

Related

Uploading files from React to Node - cannot access File object

I'm struggling to access the FileList from an input type="file" submitted from a React frontend to a Node backend. From my browser console I can see that the desired list of files is being submitted as the form data.
Eventually worked out that the FileList looks like an array but isn't! So got the length of it using Object.keys (interestingly other answers recommended here didn't work for me).
But no matter what I try I can't access the individual File objects... I can now cycle through the items in the FileList but I need to be able to get the filename to upload/move it to a new location on my server. It looks like they're empty objects in node but the browser is showing that it's sending the information I need.
Why are the objects I am iterating through empty?
I'm stumped.
Thanks for your help!
Browser Console Output
Node.js
router.post("", (req, res) => {
var tempFiles = req.body.files;
var fileCount = Object.keys(tempFiles).length;
console.log("FileList Keys: " + Object.keys(tempFiles));
console.log("Length: " + Object.keys(tempFiles).length);
console.log("Object Length: " + Object.keys(tempFiles['0']).length);
// Loop through files
for (let i = 0; i < fileCount; i++) {
let file = tempFiles[i];
console.log(tempFiles[i]);
}
}
Node console output:
FileList Keys: 0,1
Length: 2
Object Length: 0
{}
{}
React Form
import React from 'react';
import axios from 'axios';
import {useState} from 'react';
import { useForm } from "react-hook-form";
import { Stack, Input ,InputRightElement, InputGroup, Button, FormControl, FormLabel, FormHelperText, Checkbox, Radio, RadioGroup } from '#chakra-ui/react';
import BasicBanner from '../../core/components/banners/BasicBanner';
export default function UploadEvidence(props) {
const [data, setData] = useState("");
const [formMessage, setFormMessage] = useState("");
const { register, formState: { errors }, handleSubmit } = useForm();
const onError = (errors, e) => console.log(errors, e);
const onSubmit = (data, e) => {
console.log(data);
axios.post('http://localhost:5000/api/uploads', data)
.then(function (response) {
console.log(response);
setFormMessage("Upload successful");
})
.catch(function (error) {
console.log(error);
setFormMessage("Error uploading");
});
}
return (
<form onSubmit={handleSubmit(onSubmit, onError)} enctype="multipart/form-data">
<Stack spacing="10">
{formMessage &&
<BasicBanner message={formMessage} />
}
<Input
type="file"
accept="pdf/*"
multiple
{...register("files", {required: true })}
aria-invalid={errors.active ? "true" : "false"}
/>
</Stack>
<FormControl mt={4}>
<Button type="submit" colorScheme='blue'>Save</Button>
</FormControl>
</form>
)
}
Node server.js
const express = require('express')
const PORT = process.env.PORT || 5000
const app = express()
const bodyParser = require("body-parser");
const fileupload = require("express-fileupload");
var cors = require('cors');
app.use(cors());
app.use(fileupload());
app.use(express.static("files"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
... Routes ...

How to show data in frontend after you send data in express js

In my backend I want to send the data in this code express.js
const Joi = require('joi')
const people = [
{id:1,username:"Youw"},
{id:2,username:"thuneer"}
]
app.get('/send',(req,res) => {
res.json({data:people})
})
app.post('/send',(req,res) => {
const {error} = validateCourse(req.body)
if (error) {
return res.status(400).json({data:"Username length should be atleast 5"})
}
res.status(200).json({data:people})
})
function validateCourse(params) {
const schema = Joi.object({
username:Joi.string().min(5).required(),
})
return schema.validate(params)
}
So here I know you guys understand it since this is the basic. The username supposed to be atleast 5 and then if not then error will be the data..but How I will show this in frontend? My friend suggested this code to me but I don't know how I will implemented or arrange this
const temp = await fetch("http://localhost:3939/send", { method: "POST", body: JSON.stringify(data) })
const res = await temp.json();
const data = JSON.parse(data);
if (res.errors) {
// handle errors here
if (res.errors.username) {
const usernameError = document.getElementById('username_error');
usernameError.innerText = res.errors.username;
}
}
<form action="/send" method="POST">
<input type="text" name="username" id="user" class="user-text">
<button type="submit">submit</button>
</form>
<div id="username_error"></div>
I don't wanna write this in ajax cause I will write sooner in my reactjs so I want the code to be more general so that I can write it in ReactJS.

Display an image from multer and a database of type uploads / files.jpg

I just configured Multer in Back with NodeJS and everything works perfectly. I can save an image in my database 'uploads/img.jpg' and this image is also displayed in a folder named "uploads" in Back.
Now I am trying to display images with ReactJS and the map method and I am having difficulty : I can't display an image due to "uploads/". This is my code :
const Users = () => {
const [users, setUsers] = useState([]);
const classes = useStyles();
useEffect(() => {
const getUserss = async () => {
const result = await axios.get("/users");
setUsers(result.data);
};
getusers();
}, []);
const tableHeader = columns.map((column) => (
<TableCell key={column.id} align={column.align} style={{ minWidth: column.minWidth }} >
{column.label}
</TableCell>
));
const mainContent = users
.map((user) => {
console.log(user);
return (
<TableRow hover role="checkbox" tabIndex={-1} key={customer.code}>
<TableCell align="center" className={classes.test}>
<Avatar className={classes.avatar} src= {?} /> {/* My problem is here */}
</TableCell>
<TableCell align="center">{user.name}</TableCell>
<TableCell align="center">{user.details}</TableCell>
</TableRow>
);
});
return (
<TableContainer className={classes.container}>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>{tableHeader}</TableRow>
</TableHead>
<TableBody>{mainContent}</TableBody>
</Table>
</TableContainer>
);
};
export default Users;
and the result of my console.log(user) :
I have also configured two files in NodeJS with the MVC model but I don't know if this can help me for the map method. A fist one in controllers :
class Images {
static sendImg = (req, res) => {
const { rep, img } = req.params;
res.sendFile(`uploads/${img}`, { root: "./" });
};
}
module.exports = Images;
And the second one in routes:
const imgCtrl = require("../controllers/images");
const images = require("express").Router();
images.get("/:img", imgCtrl.sendImg);
module.exports = images;
I have also an index in routes with :
const images = require('./images');
module.exports = (app) => {
app.use('/uploads', images);
};
Try to add this:
app.use('/uploads', express.static(path.join(__dirname, 'relative_path_to_uploads_folder')));
I finally found the answer to my question. Here is the code:
src={`http://xxx/${user.path}`}

Use react to store binary data to SQL Server

I'm working on a feature that allows the user to upload a pdf to a SQL Server database. The user clicks a button to open a file dialog and selects the pdf they want to upload. The SQL Server database takes a varbinary(max).
I'm using base64 to encode the file, however I am getting an error saying
RequestError: Implicit conversion from data type varchar to varbinary(max) is not allowed. Use the CONVERT function to run this query
Looks like I'm sending a string, not binary.
Here is the code I'm using:
React.js
import React, { Component } from 'react';
import './styles.css';
import base64 from 'base-64';
class SaveSDS extends Component {
constructor() {
super();
this.state = { user: {} };
this.chemNameField = React.createRef(); // create ref for first name field
}
state = {
file: null
}
handleFile(e) {
e.preventDefault();
let file = e.target.files[0];
this.setState({file: file});
}
handleUpload(e){
e.preventDefault();
const data = new FormData();
let file = this.state.file;
data.append('file', file)
var r = new FileReader();
fetch('http://localhost:5000/db', {
method: 'POST',
headers: { "Content-Type": "application/json" },
mode: 'no-cors',
body: JSON.stringify({
chemName: this.chemNameField.current.value,
chemPDF: base64.encode(data)
})
})
.then((res) =>{
console.log(res)
});
}
render() {
return (
<div className="submitForm">
<div style={{padding: "10px"}}>
<input className="fileButton" type="file" accept=".pdf" name="file" onChange={(e) => this.handleFile(e)} />
</div>
<input type="text" style={{width: "50%"}} placeholder="Enter chemical name" ref={this.chemNameField} />
<br />
<button type="button" onClick={(e) => this.handleUpload(e)}>Upload</button>
</div>
);
}
}
export default SaveSDS;
And here is what I'm using on the express side:
Server.js
const express = require('express');
const bodyParser = require('body-parser');
var sql = require("mssql");
const app = express();
app.use(bodyParser.json());
app.use(express.json({
type: ['application/json', 'text/plain']
}));
var config = {
user: 'user',
password: 'pass',
server: 'localhost',
database: 'Master'
};
app.post('/db', function(req, res) {
console.log(req.body)
res.set('Access-Control-Allow-Origin', '*');
let connection = new sql.ConnectionPool(config, function(err) {
let request = new sql.Request(connection);
request.query("insert into chemicals (chemName, chemPDF) values ('" + req.body.chemName + "', '" + req.body.chemPDF + "')");
});
});
const port = process.env.PORT || 5000; //get the process port
app.listen(port, () => console.log(`Server running on port ${port}`));
Is it even possible to send binary to the backend from react, or would it be easier to convert to binary on the backend? Maybe my approach is totally wrong.
Basically I want the user to be able to upload a file, then allow other users to open and view that file. I've done this in C# by uploading binary to SQL Server, then decoding it so people can view it. Not sure if that translates to node/react.
Thanks!

File upload with multer refreshes React app

Update
This is happening because of hot-reloading comes with Creact React App.
Related issues:
https://github.com/expressjs/multer/issues/566
https://github.com/facebook/create-react-app/issues/4095
I am trying to learn file upload with Nodejs, Express, Multer and React for frontend. I achieved to upload files. There is a problem I struggle, not always but most of the time the whole app refreshes after upload. Here is the relevant code.
My simple form
<form onSubmit={this.handleFormSubmit}>
<input
type="file"
id="file"
onChange={this.handleFileChange}
/>
<button type="submit">Submit</button>
</form>
handleFileChange and handleFormSubmit
handleFormSubmit = () => {
const formData = new FormData();
formData.append( "file", this.state.file );
axios.post( "/api/upload", formData );
}
handleFileChange = ( e ) => {
this.setState( { file: e.target.files[ 0 ] } );
}
Related express route code
const express = require( "express" );
const multer = require( "multer" );
const storage = multer.diskStorage( {
destination( req, file, cb ) {
cb( null, "client/public/images" );
},
filename( req, file, cb ) {
cb( null, `${ Date.now() }-${ file.originalname }` );
},
} );
const upload = multer( { storage } );
router.post( "/upload", upload.single( "file" ), ( req, res ) => {
res.send();
} );
I searched a little bit but not luck. I've seen this post. Before seeing this I had already tried event.preventDefault(). Also, I've tried many things like uploading directly with onChange() without setting a state then handling it with onSubmit(). Before simplifying the code (like posting directly in handleFormSubmit) I was trying to do this via Redux actions but for debugging purposes I moved it here.
It is the first example here.
handleFormSubmit = async (e) => {
e.preventDefault() // <-- missing this
const formData = new FormData();
formData.append( "file", this.state.file );
const response = await axios.post( "/api/upload", formData );
}
handleFileChange = ( e ) => {
this.setState( { file: e.target.files[ 0 ] } );
}
This is happening because of hot-reloading comes with Creact React App.
I ran into this issue too, but using plain webpack without CRA. I was uploading the files to a static dir served by webpack-dev-server. I fixed it by setting devServer.static.watch to false (webpack docs).
your are using
<button type="submit">Submit</button>
this is why refreshing
so do it like this
<button onClick={this.handleFormSubmit}>Submit</button>
remove this from multer
destination( req, file, cb ) {
cb( null, "client/public/images" );
},

Resources