Upload An Image With Node.js

This short and concise guide shows how to set up image uploading with Node.js and Express.

If you’re in a hurry you can jump to the complete code snippet else let’s set it up step by step together.

Set Up The Server

We’ll start with a basic Express server.

Create a new folder and run npm init to generate a package.json file, then install Express.

npm install express

We’ll create an index.js file in the root of our folder and we’ll copy the Express Hello world example to this file as a starting point.

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
    res.send('Hello World!');
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`);
});

Let’s start the server.

node index.js

Open a browser and navigate to http://localhost:3000, it should show “Hello World!”

Adding A Form

Let’s create a folder called public and add a file called index.html with the following HTML.

<!DOCTYPE html>
<form action="/upload" method="POST" enctype="multipart/form-data">
    <input type="file" name="image" />
    <button type="submit">Upload</button>
</form>

We’ve added the enctype form attribute and set it to "multipart/form-data". This is needed to correctly post the file data contained in the file input field.

Note that we’ve set the name attribute of our file input element to "image", this is the name we’ll use to read the image data on the server.

Our form will POST to the post upload route, which we’ll add soon. First we have to tell our server to serve the index.html page.

const express = require('express');
const app = express();
const port = 3000;

// Add this line to serve our index.html page
app.use(express.static('public'));

app.get('/', (req, res) => {
    res.send('Hello World!');
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`);
});

Next up we’ll add the upload route.

app.get('/', (req, res) => {
    res.send('Hello World!');
});

app.post('/upload', (req, res) => {
    // We'll handle the image upload here
});

To handle file uploads we need the express-fileupload middleware, this will read the multipart/form-data and add a files property to the request object.

Let’s install it now.

npm install express-fileupload

Now we’ll update the index.js file to use express-fileupload

const express = require('express');
const fileUpload = require('express-fileupload');
const app = express();
const port = 3000;

// Use the express-fileupload middleware
app.use(fileUpload());

app.use(express.static('public'));

app.get('/', (req, res) => {
    res.send('Hello World!');
});

app.post('/upload', (req, res) => {
    // Log the files to the console
    console.log(req.files);

    // All good
    res.sendStatus(200);
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`);
});

Restart the server. When we refresh the page it now shows our form. When we upload a file, it logs the file to the server console.

It’ll look something like this

[Object: null prototype] {
  image: {
    name: 'test.jpeg',
    data: <Buffer ff d8 ff e0 00 10 4a 46 49 46 00 01 01 01 00 48 00 48 00 00 ff e2 0c 58 49 43 43 5f 50 52 4f 46 49 4c 45 00 01 01 00 00 0c 48 4c 69 6e 6f 02 10 00 00 ... 1234567 more bytes>,
    size: 234732,
    encoding: '7bit',
    tempFilePath: '',
    truncated: false,
    mimetype: 'image/jpeg',
    md5: 'c76564e19a5e8cd647a2d60478ad94b3',
    mv: [Function: mv]
  }
}

The final step is to move the file to an upload folder.

First let’s create a folder called upload in our project directory.

Then we’ll adjust the app.post route to look like this.

app.post('/upload', (req, res) => {
    // Get the file that was set to our field named "image"
    const { image } = req.files;

    // If no image submitted, exit
    if (!image) return res.sendStatus(400);

    // Move the uploaded image to our upload folder
    image.mv(__dirname + '/upload/' + image.name);

    res.sendStatus(200);
});

You can now find the uploaded file in the upload folder.

Securing The Upload

While this works it’s not super secure, our users can now upload anything to our server, let’s restrict them a bit so our server is more secure.

Only Allowing Images

Let’s first block all uploaded files that aren’t images.

app.post('/upload', (req, res) => {
    const { image } = req.files;

    if (!image) return res.sendStatus(400);

    // If does not have image mime type prevent from uploading
    if (/^image/.test(image.mimetype)) return res.sendStatus(400);

    image.mv(__dirname + '/upload/' + image.name);

    res.sendStatus(200);
});

This would cover most file uploads, it’s possible malicious actors use a different fake a different mimetype so to be absolutely sure a file is of a certain type we have to inspect the file contents itself.

Limit Upload Size

We can tell express-fileupload to limit the file upload size. This prevents users from uploading huge files in an attempt to DoS attack our server.

app.use(
    fileUpload({
        limits: {
            fileSize: 10000000, // Around 10MB
        },
        abortOnLimit: true,
    })
);

Now our server returns an HTTP 413 statuscode when the file is too big.

Preventing Malicious File Names

Malicious users can try to upload an image named ‘…/index.js’ but express-fileupload will automatically strip those dots and the file will still end up in the correct folder.

The Final Node.js Image Upload Script

This the complete Node.js script ready for copy pasting.

const express = require('express');
const fileUpload = require('express-fileupload');
const app = express();
const port = 3000;

app.use(
    fileUpload({
        limits: {
            fileSize: 10000000,
        },
        abortOnLimit: true,
    })
);

// Add this line to serve our index.html page
app.use(express.static('public'));

app.get('/', (req, res) => {
    res.send('Hello World!');
});

app.post('/upload', (req, res) => {
    // Get the file that was set to our field named "image"
    const { image } = req.files;

    // If no image submitted, exit
    if (!image) return res.sendStatus(400);

    // If does not have image mime type prevent from uploading
    if (/^image/.test(image.mimetype)) return res.sendStatus(400);

    // Move the uploaded image to our upload folder
    image.mv(__dirname + '/upload/' + image.name);

    // All good
    res.sendStatus(200);
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`);
});

Conclusion

We’ve written a server script that can handle image uploads, we secured our code, and finally we’ve set up a form that allows users to select and upload images.

We could take this a step further and enable our users to edit the images before upload. This saves server bandwidth and improves the quality of the images uploaded, for example by always forcing a square image.

The <pintura-input> element makes this straight forward. This Pintura powered web component automatically opens a powerful image editor when an image is added to the file input field and enables your users to edit images before upload.

I share web dev tips on Twitter, if you found this interesting and want to learn more, follow me there

More articles More articles