--- type: article wp_id: 657 title: 'Uploading Images - React, Multer, and Express' date: '2021-03-01T04:05:42' slug: 'uploading-images-express-and-react' image: name: 'uploading-images-express-and-react.png' width: 2560 height: 1280 status: 'published' description: "In this article I'm going to show you how to upload an image from a react app to an express server using a library called multer. This will allow us to store the image in the server's file system so they can be uploaded and downloaded by any client." tags: ['javascript', 'node', 'react', 'multer', 'express'] --- In this article I'm going to show you how to upload an image from a react app to an express server using a library called multer. This will allow us to store the image in the server's file system so they can be uploaded and downloaded by any client. ## React Let's start with a basic react app. This app just contains a single form that allows the user to select an image (file), provide a description (string), and submit them. ```js import { useState } from 'react' export default function App() { const [file, setFile] = useState() const [description, setDescription] = useState("") const submit = async event => { event.preventDefault() // Send the file and description to the server } return (
setFile(e.target.files[0])} type="file" accept="image/*" > setDescription(e.target.value)} type="text" >
) } ``` So far this looks like a basic form, the only weird thing is that the file input has a type `file` and when the value changes, we selecting the `event.target.files[0]` and storing that with `useState`. Every time the user selects a file, we're storing the [File](https://developer.mozilla.org/en-US/docs/Web/API/File) object. This object contains all of the information about the selected file and we just need to send this data to the server when the form is submitted. If we're using `axios`, the submit file function will look like this: ```js const submit = async event => { event.preventDefault() const formData = new FormData() formData.append("image", file) formData.append("description", description) const result = await axios.post('/api/images', formData, { headers: {'Content-Type': 'multipart/form-data'}}) console.log(result.data) } ``` If you're used to just posting JSON data to a server, this can look a little bit weird. An image is not a valid JSON data type which means we would either have to try and convert it to a string, send it as a json object, then convert it back to image data on the server. Or we can send it to the server using this `FormData` object, which is a much better option. When we create a new FormData object, we just append all of the data that we want to send to this object. There's the image file, and then anything else you want to send, in this case a description. Then we send that data to the server, but we have to specify that the content type is `'multipart/form-data'` so the server knows what kind of data we might be sending. So that's it for the client side, what about the server? What does the server need to do when we make a post request to `'/images'` with this form data? ## Express Let's start with a basic express server: ```js const express = require('express') const app = express() app.post('/api/images', (req, res) => { res.send("🤗") }) app.listen(8080, () => console.log("listening on port 8080")) ``` So a basic express server that is listening for a post request to `/images`. Obviously this isn't going to do anything with the image data, but at least we get a nice response 🤗. The question now is, how do we handle the image data coming into this request and store the image as a file on the server? ## Upload Image Well luckily for us, there is a fantastic library called [multer](https://www.npmjs.com/package/multer) that will handle all of that for us. First install the library: ```shell npm i multer ``` Then use the library: ```js // 1 const multer = require('multer') // 2 const upload = multer({ dest: 'images/' }) // 3 app.post('/api/images', upload.single('image'), (req, res) => { // 4 const imageName = req.file.filename const description = req.body.description // Save this data to a database probably console.log(description, imageName) res.send({description, imageName}) }) ``` 1. Require the multer library. 2. Specify the folder that the files should be saved to. We're just going to save it to a folder called `images` on the server. 3. Add a middleware function that will accept the image data from the client side form data as long as it's called `image`. Which is exactly what we called it in the react app `formData.append("image", file)` 4. Once multer has successfully saved the image, we can access the image data from `req.file` and any other form data from `req.body`. Multer creates a unique name for the file, so the path will be something like `images/d54c8136cd238804b67e4a0c56427f8b` We probably want to save this data to a database so we can query it back later, but we don't care about that right now. This is just a post about uploading images. We're going to send the description and the path to the image back to the client so we can use it to view the uploaded image. ## Download Image The image is currently being stored in a directory called `images` on the server. That's great, but there's currently no way of getting the image back from the server. We want to allow the client to request the image in an img tag like this: ```html ``` Then the server needs to send the image to the client. This can be done in many ways, but the easiest way is to just let express serve these as static files: ```js app.use('/images', express.static('images')) ``` This allows anyone to view any image in the images directory. This might be exactly what you want, then yay, you're done. But you might want to keep images private and only allow authorized users to view images. In that case, you'll want to do something more like this: ```js const fs = require('fs') app.get('/images/:imageName', (req, res) => { // do a bunch of if statements to make sure the user is // authorized to view this image, then const imageName = req.params.imageName const readStream = fs.createReadStream(`images/${imageName}`) readStream.pipe(res) }) ``` ## Final Code Here's what the final code might look like for this app. The react app posts and image and then shows that image in an `img` tag. The server stores the image in its file system and serves the images when a request for an image is made: ### React ```js import { useState } from 'react' import axios from 'axios' export default function App() { const [file, setFile] = useState() const [description, setDescription] = useState("") const [imageName, setImageName] = useState() const submit = async event => { event.preventDefault() const formData = new FormData() formData.append("image", file) formData.append("description", description) const result = await axios.post('/api/images', formData, { headers: {'Content-Type': 'multipart/form-data'}}) setImageName(result.data.imageName) } return (
setFile(e.target.files[0])} type="file" accept="image/*" > setDescription(e.target.value)} type="text" >
{ image && }
) } ``` ### Express ```js const express = require('express') const fs = require('fs') const multer = require('multer') const upload = multer({ dest: 'images/' }) const app = express() // app.use('/images', express.static('images')) app.get('/images/:imageName', (req, res) => { // do a bunch of if statements to make sure the user is // authorized to view this image, then const imageName = req.params.imageName const readStream = fs.createReadStream(`images/${imageName}`) readStream.pipe(res) }) app.post('/api/images', upload.single('image'), (req, res) => { const imageName = req.file.filename const description = req.body.description // Save this data to a database probably console.log(description, imageName) res.send({description, imageName}) }) app.listen(8080, () => console.log("listening on port 8080")) ```