The past few days I needed to implement image upload for a project, something which I never had done before so it was time to learn. Here I share what I learned.
First let's describe the workflow, a user clicks on a button which prompts
him to choose a file to upload then the user clicks on a submit button
which sends the file to the server where it is saved in the filesystem and
the path is saved to a database. We are going to be using
multer, a node.js
middleware that hanldes uploaded files so npm install
it.
For the front-end you can use 2 approaches, a form or no form. The traditional approach with multer is a form, however I needed to implement it without using a form so I'll show both solutions.
const FileUpload = () => {
const [file, setFile] = useState(null);
const handleFileChange = e => {
setFile(e.target.files[0]);
}
const handleSubmit = () => {
// Simulate a form
const myForm = new FormData()
// All mimetypes I want
const types = ['image/png', 'image/jpeg', 'image/jpg']
let errs = [];
// Check mimetypes
if (file && types.every(type => file?.type !== type)) {
errs.push({ message: 'Invalid file, only png, jpeg and jpg is supported.' });
}
// Check file size
if (file && file?.size > 2000000) {
errs.push({ message: 'File is too large, pick something smaller than 2MB' });
}
/*
Insert code here for handling errors
*/
// If errors or no file, stop
if(errs.length > 0 || !file) {
return;
}
// Append file to form and give it the key of image
myForm.append('image', file)
fetch('/api/upload-image', {
method: 'POST',
body: myForm
})
.catch(err => console.log(err));
}
return (
<div>
<-- inputs with type file can not be controlled components -->
<input type="file" onChange={handleFileChange} />
<button onClick={handleSubmit}>
Upload
</button>
</div>
);
}
const FileUpload = () => {
return (
<form action="/api/upload-image" method="post" enctype="multipart/form-data">
<-- inputs with type file can not be controlled components -->
<input type="file" name="image" />
<input type="submit" />
</form>
);
}
const bodyParser = require('body-parser');
const express = require('express');
const path = require('path');
const multer = require('multer');
const upload = multer({
// Destination of images
dest: './public/assets',
// Max filesize = 2MB, 1 file, 0 extra fields
limits: { fileSize: 2000000, fields: 0, files: 1}
});
const app = express();
app.use(cors());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Serve images from public folder
const dir = path.join(__dirname, 'public');
app.use(express.static(dir));
// Handle image upload
app.post(
'/upload-image',
// This is how multer knows to get the file with the key 'image'
upload.single('image'),
(req, res) => {
// Pseudocode where I add a new image with path '/public/assets/<filename>'
db.images.insertNew({ path: '/assets/' + req.file.filename });
res.json({ success: true });
}
);
const port = process.env.PORT || 5000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
You should now have a fully functional image upload utility. The images can be fetched from localhost:5000/assets/<filename>. You can learn more about multer right here.