Nestjs file uploading using Multer

Nestjs file uploading using Multer

This guide will show you how you can implement file uploading into your Nestjs application and the things you should keep in mind when doing so. You will develop an application with three endpoints that will to the following:

  • Upload an image
  • Upload multiple images
  • Get the image using the image path

You will also add custom utilities to edit the file name and validate the file upload for images.

So without wasting any further time let's get into it.

Setup

The first thing you need to do is create a Nestjs project that will hold our Fileserver. For that, you need to open your terminal and run the following command:

nest new nest-file-uploading && cd nest-file-uploading

This creates a new directory called nest-file-uploading and initializes it with a standard Nestjs configuration.

With the directory in place, you can go ahead and install the needed dependencies for the application using the following commands:

npm install @nestjs/platform-express --save
npm install @types/express -D

The last thing you need to do before jumping into coding is creating the files and folders needed for the project. This is very simple because the application only needs one more file.

mkdir src/utils
touch src/utils/file-uploading.utils.ts

To start the app, you can now execute npm run start:dev in your terminal.

Uploading files

Now that you have completed the setup process, you can go ahead and start implementing the actual functionality. Let's start by importing the MulterModule in your AppModule so you can use Multer in your other files.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { MulterModule } from '@nestjs/platform-express';

@Module({
  imports: [MulterModule.register({
    dest: './files',
  })],
  controllers: [AppController],
})
export class AppModule {}

Here you import the MulterModule from @nestjs/platform-express and add it to your imports statement. You also define the destination in which the files will be saved when an upload occurs.

Note: This destination starts at the root path of the project, not the src folder.

The next step is to implement the actual uploading functionality which is rather simple. You simply need to add a FileInterceptor() to a normal post request handler and then pull out the file from the request using the @UploadedFile() decorator.

@Post()
@UseInterceptors(
	FileInterceptor('image'),
)
async uploadedFile(@UploadedFile() file) {
    const response = {
    	originalname: file.originalname,
    	filename: file.filename,
    };
    return response;
}

The FileInterceptor takes two arguments, a fieldName and an optional options object which you will use later to check for the correct file types and give the file a custom name in the directory.

Uploading multiple files at once is almost the same, you just need to use the FilesInterceptor instead and pass an extra argument of the maximum number of files.

@Post('multiple')
@UseInterceptors(
  FilesInterceptor('image', 20, {
    storage: diskStorage({
      destination: './files',
      filename: editFileName,
    }),
    fileFilter: imageFileFilter,
  }),
)
async uploadMultipleFiles(@UploadedFiles() files) {
  const response = [];
  files.forEach(file => {
    const fileReponse = {
      originalname: file.originalname,
      filename: file.filename,
    };
    response.push(fileReponse);
  });
  return response;
}

That was simple, the only problem here is that the user can upload every filetype regardless of the file extention which doesn't suit all projects and that the filename is just some random number.

Let's fix that by implementing some utilities in your file-upload.utils.ts file.

First, let's implement and file type filter which only allows images to be uploaded.

export const imageFileFilter = (req, file, callback) => {
  if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
    return callback(new Error('Only image files are allowed!'), false);
  }
  callback(null, true);
};

Here you create a middleware function which checks if the file type is an image. If so it returns true and the image will be uploaded and if not you throw an error and return false for the callback.

The editFileName function has the same structure but creates a custom filename using the original name, the file extension and four random numbers.

export const editFileName = (req, file, callback) => {
  const name = file.originalname.split('.')[0];
  const fileExtName = extname(file.originalname);
  const randomName = Array(4)
    .fill(null)
    .map(() => Math.round(Math.random() * 16).toString(16))
    .join('');
  callback(null, `${name}-${randomName}${fileExtName}`);
};

Now that you have created these two middleware functions it's time to use them in your app.controller.ts file. For that you just need to add an extra configuration object to the FileInterceptor which then looks like this:

  @Post()
  @UseInterceptors(
    FileInterceptor('image', {
      storage: diskStorage({
        destination: './files',
        filename: editFileName,
      }),
      fileFilter: imageFileFilter,
    }),
  )
  async uploadedFile(@UploadedFile() file) {
    const response = {
      originalname: file.originalname,
      filename: file.filename,
    };
    return response;
  }

  @Post('multiple')
  @UseInterceptors(
    FilesInterceptor('image', 20, {
      storage: diskStorage({
        destination: './files',
        filename: editFileName,
      }),
      fileFilter: imageFileFilter,
    }),
  )
  async uploadMultipleFiles(@UploadedFiles() files) {
    const response = [];
    files.forEach(file => {
      const fileReponse = {
        originalname: file.originalname,
        filename: file.filename,
      };
      response.push(fileReponse);
    });
    return response;
  }

Lastly you will add a get route which will take the imagepath as an argument and return the image using the sendFile method.

@Get(':imgpath')
seeUploadedFile(@Param('imgpath') image, @Res() res) {
  return res.sendFile(image, { root: './files' });
}

Testing the application

Now that you have finished your application it's time to test it by sending an HTTP request to your endpoint. This can be done using the curl command in the terminal or by using an HTTP client software like Postman or Insomnia. I personally use Insomnia but it should be almost the same in Postman.

Start the server using the following command:

npm run start

As indicated by the terminal output, the server is now running on http://localhost:3000. To test the API you now only need to create a new HTTP request and change the request body to multipart so you can upload files.

Upload images
Upload images

Here you set the destination to http://localhost:3000 and add an image to the request body. This can be done by clicking on the arrow next to the checkbox and selecting file.

On the right, you can see the response with the original file name and the new filename on the server.

Note: The new filename is later used to get the images from the server.

Upload multiple images
Upload multiple images

Here you do the same but upload multiple files on the /multiple endpoint.

Get uploaded image
Get uploaded image

Getting files is also a simple process. You just need to send a get request to the API with the filename you got from the post request as a parameter.

The full code for the project can also be found on my Github.

Conclusion

You  made it all the way until the end! Hope that this article helped you understand file uploading in Nestjs.

If  you have found this useful, please consider recommending and sharing it  with other fellow developers. If you have any questions or feedback,  let me know on twitter or use my contact form.

Read these next: