In this article I will tell about how to upload files to server using Node.js and multer package from npm, filter upload files by extension and validate file by check magic numbers.

First, install all dependencies

npm install --save express ejs multer

and create simple express server with render index.ejs

server.js

var express = require("express")
var ejs = require('ejs')
var app = express()

app.set('view engine', 'ejs')

app.get('/api/file', function(req, res) {
	res.render('index')
})

var port = process.env.PORT || 8080
app.listen(port, function() {
	console.log('Node.js listening on port ' + port)
})

views/index.ejs

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title></title>
</head>
<body>
	<form id="uploadForm" enctype="multipart/form-data" method="post">
		<input type="file" name="userFile" />
		<input type="submit" value="Upload File" name="submit">
	</form>
</body>
</html>

Now you can run server, open url http://localhost:8080/api/file in your browser and see form to choose uploaded file.

Add to server.js file new dependencies: path and multer

var path = require('path')
var multer = require('multer')

and add config object

var storage = multer.diskStorage({
	destination: function(req, file, callback) {
		callback(null, './uploads')
	},
	filename: function(req, file, callback) {
		console.log(file)
		callback(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
	}
})

There are two options available, destination and filename.

destination is to specify the path to the directory where uploaded files will be stored.

filename is used to determine what the file should be named inside the folder. In this case, we upload the file using a return number value from Date.now() instead of the original name and add a original file extension using the built-in path Node.js library.

path.extname(file.originalname)

Thus, the file will be uploaded to the server with original extension.

Now we need to create a POST route that will send the file to the server. Add following code snippet after GET route.

app.post('/api/file', function(req, res) {
	var upload = multer({
		storage: storage
	}).single('userFile')
	upload(req, res, function(err) {
		res.end('File is uploaded')
	})
})

Here multer used settings from storage object we was written above and send the file to the server. If all goes well, you will see the message File is uploaded. You can try it.

Filter upload by file extension

At the moment, we can upload the file to the server with any extension. But what if we want to allow to upload only image files?

To control which files should be uploaded and which should be skipped in multer module provides fileFilter function.

Let’s add this feature.

Add this code snippet

fileFilter: function(req, file, callback) {
	var ext = path.extname(file.originalname)
	if (ext !== '.png' && ext !== '.jpg' && ext !== '.gif' && ext !== '.jpeg') {
		return callback(res.end('Only images are allowed'), null)
	}
	callback(null, true)
}

to POST route after storage property like this

app.post('/api/file', function(req, res) {
	var upload = multer({
		storage: storage,
		fileFilter: function(req, file, callback) {
			var ext = path.extname(file.originalname)
			if (ext !== '.png' && ext !== '.jpg' && ext !== '.gif' && ext !== '.jpeg') {
				return callback(res.end('Only images are allowed'), null)
			}
			callback(null, true)
		}
	}).single('userFile')
	upload(req, res, function(err) {
		res.end('File is uploaded')
	})
})

Now you can upload files only with png, jpg, gif and jpeg extensions.

Full code:

server.js

var express = require("express")
var multer = require('multer')
var app = express()
var path = require('path')

var ejs = require('ejs')
app.set('view engine', 'ejs')

app.get('/api/file', function(req, res) {
	res.render('index')
})

var storage = multer.diskStorage({
	destination: function(req, file, callback) {
		callback(null, './uploads')
	},
	filename: function(req, file, callback) {
		callback(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
	}
})

app.post('/api/file', function(req, res) {
	var upload = multer({
		storage: storage,
		fileFilter: function(req, file, callback) {
			var ext = path.extname(file.originalname)
			if (ext !== '.png' && ext !== '.jpg' && ext !== '.gif' && ext !== '.jpeg') {
				return callback(res.end('Only images are allowed'), null)
			}
			callback(null, true)
		}
	}).single('userFile');
	upload(req, res, function(err) {
		res.end('File is uploaded')
	})
})

var port = process.env.PORT || 8080
app.listen(port, function() {
	console.log('Node.js listening on port ' + port)
})

views/index.ejs

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title></title>
</head>
<body>
	<form id="uploadForm" enctype="multipart/form-data" method="post">
		<input type="file" name="userFile" />
		<input type="submit" value="Upload File" name="submit">
	</form>
</body>
</html>

Edited: April 23, 2017. Validate Files by Check Magic Numbers.

Comment by Araik Martirosyan: Hey Mikhail, I have a question. How I can validate uploade file, if i change file name and make this myscript.js.jpeg. I dont want this file save on my server side.

Answer

To determine if a file is an valid we can read the first bytes of the stream and compare it with magic numbers https://en.wikipedia.org/wiki/Magic_number_(programming). Since multer does not provide file data (we need bitmap in this case) as a solution we can check the magic numbers after uploading the file to the server and if it does not match then delete this file from filesystem.

Since In this article I give an example about the validation image files then the following example I will give about images.

First, declare an object that will contain magic numbers for the file types of interest to us:

var MAGIC_NUMBERS = {
    jpg: 'ffd8ffe0',
    jpg1: 'ffd8ffe1',
    png: '89504e47',
    gif: '47494638'
}

then write simple function that we will use to check magic numbers:

function checkMagicNumbers(magic) {
    if (magic == MAGIC_NUMBERS.jpg || magic == MAGIC_NUMBERS.jpg1 || magic == MAGIC_NUMBERS.png || magic == MAGIC_NUMBERS.gif) return true
}

add new fs dependency:

var fs = require('fs')

and replace:

upload(req, res, function(err) {
	res.end('File is uploaded')
})

with:

upload(req, res, function(err) {
	var bitmap = fs.readFileSync('./uploads/' + req.file.filename).toString('hex', 0, 4)
	if (!checkMagicNumbers(bitmap)) {
		fs.unlinkSync('./uploads/' + req.file.filename)
		res.end('File is no valid')
	}
	res.end('File is uploaded')
})

at the moment we do not need fileFilter option and you can delete it.

Full code for this example:

server.js

var express = require("express")
var multer = require('multer')
var app = express()
var path = require('path')
var fs = require('fs')

var ejs = require('ejs')
app.set('view engine', 'ejs')

var MAGIC_NUMBERS = {
	jpg: 'ffd8ffe0',
	jpg1: 'ffd8ffe1',
	png: '89504e47',
	gif: '47494638'
}

function checkMagicNumbers(magic) {
	if (magic == MAGIC_NUMBERS.jpg || magic == MAGIC_NUMBERS.jpg1 || magic == MAGIC_NUMBERS.png || magic == MAGIC_NUMBERS.gif) return true
}

app.get('/api/file', function(req, res) {
	res.render('index')
})

var storage = multer.diskStorage({
	destination: function(req, file, callback) {
		callback(null, './uploads')
	},
	filename: function(req, file, callback) {
		callback(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
	}
})

app.post('/api/file', function(req, res) {
	var upload = multer({
		storage: storage
	}).single('userFile')
	upload(req, res, function(err) {
		var bitmap = fs.readFileSync('./uploads/' + req.file.filename).toString('hex', 0, 4)
		if (!checkMagicNumbers(bitmap)) {
			fs.unlinkSync('./uploads/' + req.file.filename)
			res.end('File is no valid')
		}
		res.end('File is uploaded')
	})
})

var port = process.env.PORT || 8080
app.listen(port, function() {
	console.log('Node.js listening on port ' + port)
})

views/index.ejs

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title></title>
</head>
<body>
	<form id="uploadForm" enctype="multipart/form-data" method="post">
		<input type="file" name="userFile" />
		<input type="submit" value="Upload File" name="submit">
	</form>
</body>
</html>

Edited: August 1, 2017. Validate Files by Check Magic Numbers Before Write to File System.

In the previous example, we checked the file after it uploaded to the file system and if file is not valid deleted it.

Quote from previous chapter:

Since multer does not provide file data as a solution we can check the magic numbers after uploading the file to the server and if it does not match then delete this file from filesystem.

Multer does not provide file data (we need buffer) if as storage we are using multer.diskStorage(). In this chapter I will tell how to check file signatures (magic numbers) before write file to the filesystem using multer.memoryStorage().

Remove this part of code:

var storage = multer.diskStorage({
    destination: function(req, file, callback) {
        callback(null, './uploads')
    },
    filename: function(req, file, callback) {
        callback(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
    }
})

and replace:

var upload = multer({
    storage: storage
}).single('userFile')

with:

var upload = multer({
    storage: multer.memoryStorage()
}).single('userFile')

Now every time we upload a file we will have buffer of entire file in req.

We will check first bytes of buffer before write file to the filesystem and if the buffer is no valid the file will not be written to the file system.

Replace:

upload(req, res, function(err) {
    var bitmap = fs.readFileSync('./uploads/' + req.file.filename).toString('hex', 0, 4)
    if (!checkMagicNumbers(bitmap)) {
        fs.unlinkSync('./uploads/' + req.file.filename)
        res.end('File is no valid')
    }
    res.end('File is uploaded')
})

with:

upload(req, res, function (err) {
    var buffer = req.file.buffer
    var magic = buffer.toString('hex', 0, 4)
    var filename = req.file.fieldname + '-' + Date.now() + path.extname(req.file.originalname)
    if (checkMagicNumbers(magic)) {
        fs.writeFile('./uploads/' + filename, buffer, 'binary', function (err) {
            if (err) throw err
                res.end('File is uploaded')
        })
    } else {
        res.end('File is no valid')
    }
})

buffer variable contains buffer of the entire file, magic variable contains first bytes to check and filename contains name for write file in filesystem.

If buffer is valid if (checkMagicNumbers(magic)) file will be written to the filesystem using fs.writeFile. Otherwise user will have File is no valid message.

Full code for this example:

server.js

var express = require("express")
var multer = require('multer')
var app = express()
var path = require('path')
var fs = require('fs')

var ejs = require('ejs')
app.set('view engine', 'ejs')

var MAGIC_NUMBERS = {
    jpg: 'ffd8ffe0',
    jpg1: 'ffd8ffe1',
    png: '89504e47',
    gif: '47494638'
}

function checkMagicNumbers(magic) {
    if (magic == MAGIC_NUMBERS.jpg || magic == MAGIC_NUMBERS.jpg1 || magic == MAGIC_NUMBERS.png || magic == MAGIC_NUMBERS.gif) return true
}

app.get('/api/file', function(req, res) {
    res.render('index')
})

app.post('/api/file', function(req, res) {
    var upload = multer({
        storage: multer.memoryStorage()
    }).single('userFile')
    upload(req, res, function(err) {
        var buffer = req.file.buffer
        var magic = buffer.toString('hex', 0, 4)
        var filename = req.file.fieldname + '-' + Date.now() + path.extname(req.file.originalname)
        if (checkMagicNumbers(magic)) {
            fs.writeFile('./uploads/' + filename, buffer, 'binary', function(err) {
                if (err) throw err
                res.end('File is uploaded')
            })
        } else {
            res.end('File is no valid')
        }
    })
})

var port = process.env.PORT || 8080
app.listen(port, function() {
    console.log('Node.js listening on port ' + port)
})

views/index.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <form id="uploadForm" enctype="multipart/form-data" method="post">
        <input type="file" name="userFile" />
        <input type="submit" value="Upload File" name="submit">
    </form>
</body>
</html>

Mikhail

I am Mikhail Evdokimov, a Web Developer and Perfectionist from Russia, Far East, Vladivostok. Enthusiast performance, responsive design and usability, always looking for the best project and good coffee. Love JavaScript and JavaScript Full Stack Development. MongoDB, ExpressJS, AngularJS, NodeJS. Also I can use a few Ruby and Lua for Corona SDK. In my blog, I plan to write not only about development but also about my other hobbies and interests.