How to Implement Image Upload using Express and Multer (+ PostgreSQL)

Yunseo Hwang
The Startup
Published in
9 min readFeb 25, 2021

--

Introduction

Recently, I had to implement image upload to show the profile image. It was very simple I think, but I don’t know if it will be easy in the future. I decided to make it into a tutorial because image upload implementation is frequently required.

In this article, you will learn how to implement images api using Express and Multer. In addition, here we use PostgreSQL to store information about image files.

Prerequisites

  • Understanding of node.js and package manager is recommended
  • Understanding of express and postgres is suggested.
  • This tutorial is based on the Linux(Ubuntu 20.04)

STEP 1 — Project Settings

1 — 1. Set up postgreSQL(postgres)

If you encounter postgres for the first time, refer to How to Use PostgreSQL on Linux and install it.

After the installation is complete, we create image_upload database using createdb command.

$ createdb image_upload # Create image_upload postgres database.

Use the following command to access the database we created.

$ psql image_upload

We will store information about image files in this database. Specifically, it will be stored in a table called image_files.

Run the following in your terminal to create the table:

CREATE TABLE image_files(
id SERIAL NOT NULL PRIMARY KEY,
filename TEXT UNIQUE NOT NULL,
filepath TEXT NOT NULL,
mimetype TEXT NOT NULL,
size BIGINT NOT NULL,
);

If you have created it, make sure it is done normally:

image_upload=# \d
...
public | image_files | table | user
public | image_files_id_seq | sequence | user
...

1 — 2. Install Postman

This tutorial used Postman for testing. So you need to install it. You can install it on the website or through the command below:

$ sudo snap install postman

1 — 3. Create Project

Make sure Node.js is installed and run the following in your terminal:

  1. Create a new directory for this tutorial:
$ mkdir image-upload-multer
$ cd image-upload-multer

2. Initialize a new Node.js project with defaults:

$ npm init

3. Create entry file:

$ touch index.js

4. Install dependencies:

$ npm install express multer morgan knex pg --save

STEP 2— Create Express Server

2 — 1. Write Basic Code

We need to write some code for the express server. Open your editor and write the code below:

const morgan = require('morgan');
const express = require('express');
// Create express object
const app = express();
// Set middlewares
app.use(express.json());
app.use(morgan('dev'));
// @TODO Add routes// Run express server
const PORT = process.env.PORT;
app.listen(PORT, () => {
console.log(`app is running on port ${PORT}`);
});

express.json() and morgan() are express middleware modules.

express.json() is for parsing JSON request body, and morgan() is the middleware for logging HTTP requests.

These middleware are available through the .use() method. Once middleware is set up, the server can be run using the .listen() method.

When written to this point, add PORT=5000 node index.js scripts to package.json. You can run the server through the command below:

$ npm start # Run express server on port 5000

2 — 2. Add Routes to Express Server

We should add a routes for image upload and getting image. The route for image upload is /image and the route for getting image is /image/:filename . let’s add this routes:

...// @TODO Add routes
// Image Upload Routes
app.post('/image', (req, res) => {
res.json('/image api');
});
// Image Get Routes
app.get('/image/:filename', (req, res) => {
res.json('/image/:filename api');
});
...

If you have added routes, run the server again.

$ npm start # Run express server on port 5000

2 — 3. Test Express Server

The server should be tested for normal operation. We will use Postman for API testing.

First, create a new collection for this tutorial. Requests to be created from now on will be stored in this collection.

Second, generate two requests below in the collection we create. To test /image/:filename, the browser will eventually be used, but Postman will be used during development.

Finally, click the SEND button to view the results. It is a success if the result of each request is as follows.

"/image api"
"/image/:filename api"

STEP 3 — Apply Multer

3 — 1. Create Multer Object

The setting for image upload should be made through the multer(). The dest attribute determines where the uploaded file will be stored. There are many other things besides the dest attribute, but we omit them here.

const multer = require('multer');// Create multer object
const imageUpload = multer({
dest: 'images',
});

3 — 2. Modify Image Upload Route

It is time to apply the Multer middleware to /image route. There are two options: .single() and .array() . .single() is a method for receiving one file and .array() is a method for receiving multiple files. We will receive one file, so we will modify /image route using .single() . Multer saves files whose name attribute is image to a preset directory ( images ) and sets the relevant information to the file attribute of the req object.

...// Image Upload Routes
app.post('/image', imageUpload.single('image'), (req, res) => {
console.log(req.file);
res.json('/image api');
});
...

3 — 3. Test Image Upload Route

We modified /image route to store a file whose name attribute is images and output the information to the terminal. It’s time to test this. As before, we use Postman.

Set Body’s Type to ‘form-data’ and select the image file. Here, the key value should be set to the predefined image.

Then click SEND button and look at the changes in the server. If it worked normally, a file would have been created under the ‘images’ directory with the log as shown below:

app is running on port 5000
{
fieldname: 'image',
originalname: 'girl.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: 'images',
filename: '9cebe2fa3429036f272f21f3146762df',
path: 'images/9cebe2fa3429036f272f21f3146762df',
size: 259739
}
POST /image 200 111.917 ms - 12

3–4. Modify Image Get Route

Let’s modify /image/:filename route to get the created image file. The :filename can be taken from req.params. It then responds to that file using the .sendFile method.

// Image Get Routes
app.get('/image/:filename', (req, res) => {
const { filename } = req.params;
const dirname = path.resolve();
const fullfilepath = path.join(dirname, 'images/' + filename);
return res.sendFile(fullfilepath);
});

3–5. Test Image Get Route

Because it is transferring image files, It is inconvenient to use Postman here (It is also possible to use postman). We will use a browser. The generated file name is used instead of :filename. Access the route from a browser:

What we want is to show the image as below:

But strangely, the file is downloaded. Why is that?

The important thing here is that there is no extension to the file. This is why Content-Type could not be set and the browser did not think the file was an image.

All we have to do is set up Content-Type. But what should we do? This will be explained at the next step.

STEP 4— Set Up Content-Type

From here, it is divided into two paths. The first method is to change the storage method and the second method is to store file information separately in the database.

4 — 1. First Method : Change the storage method

Now the file name is strange, unlike the original name. This is intended for security reasons, but Content-Type is not automatically specified because there is no file extension.

However, you can change the name of the saved file by changing the settings in Multer.

The file was named as timestamp + original file name below. You can change the corresponding part as follows:

// BEFORE : Create multer object
const imageUpload = multer({
dest: 'images',
});
// AFTER : Create multer object
const imageUpload = multer({
storage: multer.diskStorage(
{
destination: function (req, file, cb) {
cb(null, 'images/');
},
filename: function (req, file, cb) {
cb(
null,
new Date().valueOf() +
'_' +
file.originalname
);
}
}
),
});

If the above changes are made, the extension must be added when the image is requested( ex : /image/1614253066044_girl.jpg )

Changes to routes are not required because the storage method has been changed. This method is the easiest. So I will skip the test on this part.

4 — 2. Second Method : Store file information in the database.

First, save information about files such as MIME type, size, and so on to the database. The MIME type is then imported from the database and set to the content type. This is the second way.

Let’s get started:

First, we need to create knex for the database connection:

const knex = require('knex');// Create database object
const db = knex(
{
client: 'pg',
connection: {
host: '127.0.0.1',
user: 'YOUR_PG_USER',
password: 'YOUR_PG_USER_PASSWORD',
database: 'image_upload',
},
}
);

Second, whenever you upload an image, modify the /image route to store information about that image file in the database :

// Image Upload Routes
app.post('/image', imageUpload.single('image'), (req, res) => {
const { filename, mimetype, size } = req.file;
const filepath = req.file.path;
db
.insert({
filename,
filepath,
mimetype,
size,
})
.into('image_files')
.then(() => res.json({ success: true, filename }))
.catch(err => res
.json(
{
success: false,
message: 'upload failed',
stack: err.stack,
}
)
);
});

Finally, when the image is requested, the mime type is taken from the database and set to content type:

// Image Get Routes
app.get('/image/:filename', (req, res) => {
const { filename } = req.params;
db
.select('*')
.from('image_files')
.where({ filename })
.then(images => {
if (images[0]) {
const dirname = path.resolve();
const fullfilepath = path.join(
dirname,
images[0].filepath);
return res
.type(images[0].mimetype)
.sendFile(fullfilepath);
}
return Promise.reject(
new Error('Image does not exist')
);
})
.catch(err => res
.status(404)
.json(
{
success: false,
message: 'not found',
stack: err.stack,
}
),
);
});

4 — 3. Test Second Method

Image Upload is a success if a request is sent using Postman and the file name is returned as shown below.

{
"success": true,
"filename": "0e7c54a5a4b0b6860153cf4a9eb9422b"
}

The SELECT * FROM image_files command lets you view the columns in the image_files table. After the image upload, you can see that the data has been added as follows.

Once the image upload is successful, the filename can be used instead of :filename to get the image(ex : /image/0e7c54a5a4b... )

When a browser requests an image with the url, the image appears as shown below, and the operation was successful.

Conclusion

We handled HTTP requests using express and stored images on the server using Multer. In addition, two methods were used to provide images to users, one of which used a database to store file information.

We finally implemented a server to upload and get images.

Congratulations 🔥

The complete code written in this tutorial is as follows(with database):

const morgan = require('morgan');
const express = require('express');
const multer = require('multer');
const path = require('path');
const knex = require('knex');
// Create database object
const db = knex(
{
client: 'pg',
connection: {
host: '127.0.0.1',
user: 'YOUR_PG_USER',
password: 'YOUR_PG_USER_PASSWORD',
database: 'image_upload',
},
}
);
// Create multer object
const imageUpload = multer({
dest: 'images',
});
// Create express object
const app = express();
// Set middlewares
app.use(express.json());
app.use(morgan('dev'));
// Image Upload Routes
app.post('/image', imageUpload.single('image'), (req, res) => {
const { filename, mimetype, size } = req.file;
const filepath = req.file.path;
db
.insert({
filename,
filepath,
mimetype,
size,
})
.into('image_files')
.then(() => res.json({ success: true, filename }))
.catch(err => res.json({ success: false, message: 'upload failed', stack: err.stack }));
});
// Image Get Routes
app.get('/image/:filename', (req, res) => {
const { filename } = req.params;
db.select('*')
.from('image_files')
.where({ filename })
.then(images => {
if (images[0]) {
const dirname = path.resolve();
const fullfilepath = path.join(dirname, images[0].filepath);
return res.type(images[0].mimetype).sendFile(fullfilepath);
}
return Promise.reject(new Error('Image does not exist'));
})
.catch(err => res.status(404).json({success: false, message: 'not found', stack: err.stack}),
);
});
// Run express server
const PORT = process.env.PORT;
app.listen(PORT, () => {
console.log(`app is running on port ${PORT}`);
});

Github Repository : Click

Thank you for your reading! :)

--

--