One thing we miss now in the new house form, and when editing an existing house, is being able to upload images.
We are currently limited by uploading an image somewhere, and pasting the URL in the form. Not very practical for our users!
Let’s add this functionality.
Before we start, we must add an npm package, called express-fileupload
:
npm install express-fileupload
and we add it as a middleware to Express, in server.js
:
const fileupload = require('express-fileupload')
server.use(
//...
fileupload()
)
This is needed because otherwise the server can’t parse file uploads.
Next, in components/HouseForm.js
, change the current input field that we used:
<p>
<label>House picture URL</label>
<input
required
onChange={event => setPicture(event.target.value)}
type='text'
placeholder='House picture URL'
value={picture}
/>
</p>
to this combo of a file upload and an image visualizer:
<p>
<label>House picture</label>
<input
type="file" id="fileUpload"
/>
{picture ? <img src={picture} width="200" alt="House image" /> : ''}
</p>
If you try to reload the page, the file picker should be there! Let’s add input[type=file]
to the CSS styling for forms we already have at the bottom:
<style jsx>{`
input[type='number'],
input[type='file'],
select,
textarea {
/*... */
}
}
Let’s also limit the file input to only accept images:
<input type="file" id="fileUpload" accept="image/*" />
See https://flaviocopes.com/how-to-accept-images-file-input/
Now we must handle the change
event on this input field:
<input
type='file'
id='fileUpload'
accept='image/*'
onChange={async event => {
const files = event.target.files
const formData = new FormData()
formData.append('image', files[0])
const response = await axios.post('/api/host/image', formData)
setPicture('http://localhost:3000' + response.data.path)
}}
/>
This is invoked when the file input changes (an image had been selected). In there, we get the image from event.target.files
and we POST it to /host/image
, a new endpoint we’re going to make next.
We expect a path
property coming back, which will be the URL of our image, and we assign it using the setPicture
hook update function.
Let’s now make the POST /api/host/image
endpoint in server.js
.
We first check if the user is logged in, and we get the image from the request:
server.js
server.post('/api/host/image', (req, res) => {
if (!req.session.passport) {
res.writeHead(403, {
'Content-Type': 'application/json'
})
res.end(
JSON.stringify({
status: 'error',
message: 'Unauthorized'
})
)
return
}
const image = req.files.image
})
Next we run npm install randomstring
and we import that module:
const randomstring = require('randomstring')
we need it to generate a random string for our image, since users might submit images with the same name. I’m just going to prepend a random string to the image original name, but in the real world you might want to completely randomize it, and also check if you don’t already have that name before overwriting the file:
const fileName = randomstring.generate(7) + image.name.replace(/\s/g, '')
const path = __dirname + '/public/img/houses/' + fileName
Then we call the mv
property of the uploaded image. That is provided to us by the express-fileupload
module. We move it to path
and then we communicate the success (or an error!) back to the client:
image.mv(path, (error) => {
if (error) {
console.error(error)
res.writeHead(500, {
'Content-Type': 'application/json'
})
res.end(JSON.stringify({ status: 'error', message: error }))
return
}
res.writeHead(200, {
'Content-Type': 'application/json'
})
res.end(JSON.stringify({ status: 'success', path: '/img/houses/' + fileName }))
})
This is the complete code for our /api/host/image
endpoint:
server.post('/api/host/image', (req, res) => {
if (!req.session.passport) {
res.writeHead(403, {
'Content-Type': 'application/json'
})
res.end(
JSON.stringify({
status: 'error',
message: 'Unauthorized'
})
)
return
}
const image = req.files.image
const fileName = randomstring.generate(7) + image.name.replace(/\s/g, '')
const path = __dirname + '/public/img/houses/' + fileName
image.mv(path, error => {
if (error) {
console.error(error)
res.writeHead(500, {
'Content-Type': 'application/json'
})
res.end(JSON.stringify({ status: 'error', message: error }))
return
}
res.writeHead(200, {
'Content-Type': 'application/json'
})
res.end(
JSON.stringify({ status: 'success', path: '/img/houses/' + fileName })
)
})
})
Now you should be able to successfully submit a new image for the house, and also update existing houses images!
The code for this lesson is available at https://github.com/flaviocopes/airbnb-clone-react-nextjs/tree/module-6-lesson-5