A common requirement in web apps and APIs is handling file uploads from end users. In this tutorial you will learn how to work with uploaded files using Node.js and Express
NOTE
To follow this tutorial, you will need the following:
Create a web page with a form that allows the user to select a file to upload
Create Express route handlers to work with uploaded files
Of course, you will also want to do something with each uploaded file! In this tutorial, weβre going to write JavaScript code to display some information about the file, and also to scan it for malware using Verisys Antivirus API.
NOTE
Verisys Antivirus API is a language-agnostic REST API that allows you to easily add malware scanning to mobile apps, web apps and backend processing.
By scanning user-generated content and file uploads, Verisys Antivirus API can stop dangerous malware at the edge, before it reaches your servers, applications - or end users.
Project Scaffold/Setup
The first step is to create and initialize a new Express project.
Open a terminal or command prompt, navigate to the directory where you want to store the project, and run the following commands:
1
2
3
npx express-generator --view=pug myapp
cd myapp
npm install
The generated app should have the following directory structure:
Before we move on, make sure you can run the app and view it in a browser
On MacOS, Linux or Git Bash on Windows, run the app with this command:
1
DEBUG=myapp:* npm start
Or use this command for Windows:
1
setDEBUG=myapp:* & npm start
Or this command for Windows PowerShell:
1
$env:DEBUG='myapp:*';npmstart
Then navigate to http://localhost:3000 in your browser to access the app - you should see a page that looks like this:
Go ahead and stop the server by hitting CTRL-C at the command prompt
Next, weβre going to add a few NPM packages:
Weβll add a package to deal with file uploads easier. There are several popular choices here, including Multer, Formidable and express-fileupload - they are all fairly similar, and for this tutorial, weβll use express-fileupload
For this tutorial, weβre going to scan the file for malware using Verisys Antivirus API, and so weβll add a package to make it easier to make external HTTP requests. Popular choices include Axios and node-fetch - for this article, weβll use node-fetch
Weβll also add the form-data package to allow working with multipart form data, which is used to perform file uploads
Weβve added two forms to the page. When the File Upload form is submitted, the file will be sent to a route at /upload - the next step is to create the route and route handler. Weβll get back to the Malware Scan form later.
Backend - Simple File Upload
Now weβre going to add a route handler to process uploaded files, and then weβll wire up the handler to the /upload route.
Create file myapp/routes/upload.js with the following content:
constexpress=require('express');constfileUpload=require('express-fileupload');constrouter=express.Router();router.use(fileUpload({// Configure file uploads with maximum file size 10MB
limits:{fileSize:10*1024*1024},// Temporarily store uploaded files to disk, rather than buffering in memory
useTempFiles:true,tempFileDir:'/tmp/'}));router.post('/',asyncfunction(req,res,next){// Was a file submitted?
if(!req.files||!req.files.file){returnres.status(422).send('No files were uploaded');}constuploadedFile=req.files.file;// Print information about the file to the console
console.log(`File Name: ${uploadedFile.name}`);console.log(`File Size: ${uploadedFile.size}`);console.log(`File MD5 Hash: ${uploadedFile.md5}`);console.log(`File Mime Type: ${uploadedFile.mimetype}`);// Return a web page showing information about the file
res.send(`File Name: ${uploadedFile.name}<br>
File Size: ${uploadedFile.size}<br>
File MD5 Hash: ${uploadedFile.md5}<br>
File Mime Type: ${uploadedFile.mimetype}`);});module.exports=router;
This simple handler will both print information about the file to the console and return it in as a web page, so you can see what has been received by your router.
Next, we need to wire up this code to the /upload route.
Update the content of myapp/app.js to contain the following:
varcreateError=require('http-errors');varexpress=require('express');varpath=require('path');varcookieParser=require('cookie-parser');varlogger=require('morgan');varindexRouter=require('./routes/index');varusersRouter=require('./routes/users');varuploadRouter=require('./routes/upload');varapp=express();// view engine setup
app.set('views',path.join(__dirname,'views'));app.set('view engine','pug');app.use(logger('dev'));app.use(express.json());app.use(express.urlencoded({extended:false}));app.use(cookieParser());app.use(express.static(path.join(__dirname,'public')));app.use('/',indexRouter);app.use('/users',usersRouter);app.use('/upload',uploadRouter);// catch 404 and forward to error handler
app.use(function(req,res,next){next(createError(404));});// error handler
app.use(function(err,req,res,next){// set locals, only providing error in development
res.locals.message=err.message;res.locals.error=req.app.get('env')==='development'?err:{};// render the error page
res.status(err.status||500);res.render('error');});module.exports=app;
Weβve only added two lines to the default code provided by the Express generator (lines #9 and #25 above), telling Express to use our upload.js router for the /upload route.
constexpress=require('express');constfetch=require('node-fetch');constfileUpload=require('express-fileupload');constFormData=require('form-data');constfs=require('fs');constrouter=express.Router();router.use(fileUpload({// Configure file uploads with maximum file size 10MB
limits:{fileSize:10*1024*1024},// Temporarily store uploaded files to disk, rather than buffering in memory
useTempFiles:true,tempFileDir:'/tmp/'}));router.post('/',asyncfunction(req,res,next){// Was a file submitted?
if(!req.files||!req.files.file){returnres.status(422).send('No files were uploaded');}constuploadedFile=req.files.file;// Print information about the file to the console
console.log(`File Name: ${uploadedFile.name}`);console.log(`File Size: ${uploadedFile.size}`);console.log(`File MD5 Hash: ${uploadedFile.md5}`);console.log(`File Mime Type: ${uploadedFile.mimetype}`);// Scan the file for malware using the Verisys Antivirus API - the same concepts can be
// used to work with the uploaded file in different ways
try{// Attach the uploaded file to a FormData instance
varform=newFormData();form.append('file',fs.createReadStream(uploadedFile.tempFilePath),uploadedFile.name);constheaders={'X-API-Key':'<YOUR API KEY HERE>','Accept':'*/*'};// Send the file to the Verisys Antivirus API
constresponse=awaitfetch('https://eu1.api.av.ionxsolutions.com/v1/malware/scan/file',{method:"POST",body:form,headers:headers});// Did we get a response from the API?
if(response.ok){constresult=awaitresponse.json();// Did the file contain a virus/malware?
if(result.status==='clean'){returnres.send('Uploaded file is clean!');}else{returnres.status(500).send(`Uploaded file contained malware: <b>${result.signals[0]}</b>`);}}else{thrownewError('Unable to scan file: '+response.statusText);}}catch(error){// Forward the error to the Express error handler
returnnext(error);}finally{// Remove the uploaded temp file
fs.rm(uploadedFile.tempFilePath,()=>{});}});module.exports=router;
As with the /upload handler, this new /scan handler will print information about the file to the console, so you can see what has been received by your route handler. It then uploads the file to Verisys Antivirus API to scan it for malware.
NOTE
The X-API-Key in the above code will need to be replaced with a real API key to scan files for real. Donβt have an API key? Subscribe now!
Next, we need to wire up this code to the /scan route.
Update the content of myapp/app.js to contain the following:
varcreateError=require('http-errors');varexpress=require('express');varpath=require('path');varcookieParser=require('cookie-parser');varlogger=require('morgan');varindexRouter=require('./routes/index');varusersRouter=require('./routes/users');varuploadRouter=require('./routes/upload');varscanRouter=require('./routes/scan');varapp=express();// view engine setup
app.set('views',path.join(__dirname,'views'));app.set('view engine','pug');app.use(logger('dev'));app.use(express.json());app.use(express.urlencoded({extended:false}));app.use(cookieParser());app.use(express.static(path.join(__dirname,'public')));app.use('/',indexRouter);app.use('/users',usersRouter);app.use('/upload',uploadRouter);app.use('/scan',scanRouter);// catch 404 and forward to error handler
app.use(function(req,res,next){next(createError(404));});// error handler
app.use(function(err,req,res,next){// set locals, only providing error in development
res.locals.message=err.message;res.locals.error=req.app.get('env')==='development'?err:{};// render the error page
res.status(err.status||500);res.render('error');});module.exports=app;
Weβve only added two lines to the previous code (lines #10 and #27 above), telling Express to use our scan.js router for the /scan route.
Open your browser and navigate to http://localhost:3000 - you should see a page that looks like this:
Use the Malware Scan form, Browse to select a file and press the Scan File button
If everything was set up correctly, as before you should see information about the file being printed to the console, but you should also see a response from Verisys Antivirus API: