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

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

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.

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.

$ createdb image_upload # Create image_upload postgres database.

$ psql image_upload

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,
);

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:

$ mkdir image-upload-multer
$ cd image-upload-multer

$ npm init

$ touch index.js

$ 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}`);
});

$ 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');
});

...

$ 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.

"/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.

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:

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.

// 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
);
}
}
),
});

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.

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',
},
}
);

// 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,
}
),
);
});

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"
}

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.

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}`);
});