Handling Multiple File Uploads with NestJS

Handling Multiple File Uploads with NestJS

Follow Josh Morony on

In previous tutorials, we have covered how to POST data to a NestJS server, but when it comes to uploading files to a NestJS (or any) server, things get just a little bit tricker.

In this tutorial, we are going to walk through setting up a controller in NestJS that can handle receiving file uploads from a front-end application. I will also give a basic example of how to upload from the client-side as well (e.g. sending the files from an Ionic application) to actually make use of the server. In a future tutorial, I will likely create a more in-depth example that includes creating a custom Ionic component that can handle multi-file uploads nicely.

We will need to cover some new NestJS concepts in this tutorial in order to complete it - we will start off by covering those, and then we will get into the code.

Before we Get Started

This tutorial assumes that you already have a basic working knowledge of NestJS. If you do not already feel comfortable with creating a basic NestJS server, creating routes, and POSTing data I would recommend completing these tutorials first:

Most of my tutorials do have a focus on using Ionic with NestJS, but it does not really matter if you aren’t using Ionic on the front-end.

@UseInterceptors, FilesInterceptor, and @UploadedFiles

Before we step through the code required to handle file uploads in a NestJS controller, we are going to talk through some concepts we will be using that I haven’t covered in previous NestJS tutorials.

The @UseInterceptors decorator allows us to “intercept” and change requests to and from the server. The basic use of our server involves the user making requests from the client-side to our controllers that implement GET or POST routes, and the server sending a response back to the user. An “interceptor” sits in the middle of this process and can listen to/modify the messages being sent.

We can create our own interceptors if we wish to do whatever we like, but in this case, we are going to use the built-in FilesInterceptor which the @nestjs/common package provides for us. The role of the FilesInterceptor is to extract files from the incoming request and to provide it to us through the @UploadedFiles decorator (similarly to how we extract the body of a POST request using @Body and a DTO).

If you were just uploading a single file you would use FileInterceptor instead of FilesInterceptor. If you were uploading multiple files with multiple different field names, you could also use the FileFieldsInterceptor.

Implementing the Controller

Now that we have a little bit of knowledge about these new concepts, let’s see how we would go about implementing them in a controller.

import { Controller, Post, UseInterceptors, FilesInterceptor, UploadedFiles, Body} from '@nestjs/common';
import { FileDto } from './dto/file.dto';
import * as path from 'path';

const pngFileFilter = (req, file, callback) => {

    let ext = path.extname(file.originalname);

    if(ext !== '.png'){
        req.fileValidationError = 'Invalid file type';
        return callback(new Error('Invalid file type'), false);
    }

    return callback(null, true);

}

@Controller('upload')
export class UploadController {

    constructor(){

    }

    @Post()
    @UseInterceptors(FilesInterceptor('files[]', 20, {
        fileFilter: pngFileFilter
    }))
    logFiles(@UploadedFiles() images, @Body() fileDto: FileDto){

        console.log(images);
        console.log(fileDto);

        return 'Done';

    }

}

This controller creates a POST route at /upload which will accept up to 20 uploaded .png files and log out the image data and the body of the POST request.

As you can see, we are making use of @UseInterceptors and the FilesInterceptor here. The first parameter supplied to the interceptor is the field name from the multipart/form-data sent to the server that contains the array of files (this is so the interceptor knows where to grab the files from, your field name might be different). The second parameter allows us to supply the maximum number of files that can be uploaded, and the third parameter allows us to supply a MulterOptions object.

NestJS uses the popular multer behind the scenes, and you can configure this using the same options you can with multer regularly. In this case, we are supplying a pngFileFilter function which will only allow file names with an extension of .png to be uploaded.

The fileDto is not important here, this is just regular data being posted to the server along with the files.

Sending Files to the Server

To get files from the client to the server, we will need to POST the files as multipart/form-data from a file input to our /upload route. I already have an example of sending files as multipart/form-data from an Ionic application in a previous tutorial I have written about displaying upload/download progress in Ionic.

However, the basic idea is that you would assemble the file data for sending like this:

    // Create a form data object
    let formData = new FormData();

    // Optional, if you want to use a DTO on your server to grab this data
    formData.append('title', data.title);

    // Append each of the files
    files.forEach((file) => {
      formData.append('files[]', file.rawFile, file.name);
    });

NOTE: The field name we use for the files matches up to the name given to the FilesInterceptor.

You would then just POST that formData object to your NestJS server. Once you do this, you should see the server log out something like the following:

[ { fieldname: 'files[]',
    originalname: 'image1.png',
    encoding: '7bit',
    mimetype: 'image/png',
    buffer:
     <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 01 52 00 00 02 58 08 02 00 00 00 bf c4 f9 43 00 00 00 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 ... >,
    size: 109693 },
  { fieldname: 'files[]',
    originalname: 'image2.png',
    encoding: '7bit',
    mimetype: 'image/png',
    buffer:
     <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 01 2c 00 00 00 fa 08 02 00 00 00 94 0f ed a4 00 00 00 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 ... >,
    size: 20538 } ]

As you can see above, I’ve uploaded two files: image1.png and image2.png. I could then do whatever I like with these files or their metadata on the server. Perhaps you would want to use the filesystem to store those files somewhere, maybe you want to run some image compression on those images and then return them to the user, or maybe you want to do something else entirely.

Summary

Although there is more to the story than just sending the file data to the server (obviously we are sending it there for some reason, so presumably you’ll want to do something with it), NestJS provides a rather simple implementation for grabbing that data.

As I mentioned earlier, I will likely create another tutorial in the future that focuses on nicely handling multi-file uploads from the client side with Ionic & Angular.

If there is a particular type of tutorial that you would like to see that involves uploading files to a server, let me know in the comments!

Check out my latest videos: