Sling Academy
Home/Node.js/How to handle form-data in NestJS

How to handle form-data in NestJS

Last updated: December 31, 2023

Overview

NestJS provides a powerful module for handling form data with ease, taking advantage of TypeScript features for robustness and scalability in web application development.

Setting Up Your NestJS Project

Before handling form-data in NestJS, ensure you have Node.js installed. Create a new project using the Nest CLI:

nest new form-data-project

Once the installation is complete, navigate into your new project:

cd form-data-project

For handling form-data, we need to install additional packages. Run the following command to install them:

npm install @nestjs/platform-express multer

Basic Form Data Handling

To start handling simple form-data submissions, let’s create an endpoint that could receive a POST request with form-data.

import { Controller, Post, UseInterceptors, UploadedFile } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';

@Controller('upload')
export class UploadController {
  @Post()
  @UseInterceptors(FileInterceptor('file'))
  uploadFile(@UploadedFile() file: Express.Multer.File) {
    console.log(file);
    return 'File uploaded successfully!';
  }
}

Advanced File Uploads

For more complex scenarios such as uploading multiple files, we can use the FilesInterceptor.

import { Controller, Post, UseInterceptors, UploadedFiles } from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express';

@Controller('multi-upload')
export class MultiUploadController {
  @Post()
  @UseInterceptors(FilesInterceptor('files'))
  uploadMultipleFiles(@UploadedFiles() files: Array<Express.Multer.File>) {
    console.log(files);
    return 'Multiple files uploaded successfully!';
  }
}

Custom Storage Engine

We can define a custom storage engine to control how files are stored. Let’s customize the storage using Multer.

import { diskStorage } from 'multer';
import { extname } from 'path';

export const customStorage = diskStorage({
  destination: './uploads',
  filename: (req, file, callback) => {
    const randomName = Array(32)
                          .fill(null)
                          .map(() => Math.round(Math.random() * 16).toString(16))
                          .join('');
    callback(null, `${randomName}${extname(file.originalname)}`);
  },
});

Handling Form Fields

Beyond files, we often need to handle text fields. Here’s how you can access form fields along with the file upload:

import { Controller, Post, UseInterceptors, UploadedFile, Body } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';

@Controller('field-upload')
export class FieldUploadController {
  @Post()
  @UseInterceptors(FileInterceptor('file'))
  uploadFileAndFields(@UploadedFile() file: Express.Multer.File, @Body() body: any) {
    console.log(file);
    console.log(body);
    return 'File and field data uploaded successfully!';
  }
}

Validating Form Data

Validation ensures the integrity of the data. Use class-validator and class-transformer with DTOs (Data Transfer Objects) to validate form submissions:

import { IsNotEmpty, IsString } from 'class-validator';

export class CreateItemDto {
  @IsNotEmpty()
  @IsString()
  readonly name: string;
}

In the controller:

import { Controller, Post, UsePipes, ValidationPipe, UseInterceptors, UploadedFile, Body } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { CreateItemDto } from './create-item.dto';

@Controller('validated-upload')
export class ValidatedUploadController {
  @Post()
  @UsePipes(new ValidationPipe({ whitelist: true }))
  @UseInterceptors(FileInterceptor('file'))
  uploadFileAndValidate(@UploadedFile() file: Express.Multer.File, @Body() createItemDto: CreateItemDto) {
    console.log(file);
    console.log(createItemDto);
    return 'File and validated data uploaded successfully!';
  }
}

Handling Async File Uploads

For asynchronous operations, such as interacting with a database when uploading files, you can return a promise:

import { Injectable } from '@nestjs/common';

@Injectable()
export class DatabaseService {
  async saveFileData(file: Express.Multer.File): Promise<string> {
    // Assume we save file to a database...
    return 'Saved in DB successfully!';
  }
}

Then in the controller:

import { Controller, Post, UseInterceptors, UploadedFile, Inject } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { DatabaseService } from './database.service';

@Controller('async-upload')
export class AsyncUploadController {
  constructor(@Inject(DatabaseService) private databaseService: DatabaseService) {}

  @Post()
  @UseInterceptors(FileInterceptor('file'))
  async uploadAsync(@UploadedFile() file: Express.Multer.File) {
    return this.databaseService.saveFileData(file);
  }
}

File Filtering and Size Limiting

To control the file type and size during uploads, NestJS offers file filtering and size limiting options:

import { Controller, Post, UseInterceptors, UploadedFile } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { multerOptions } from './multer-options';

@Controller('filter-upload')
export class FilterUploadController {
  @Post()
  @UseInterceptors(FileInterceptor('file', multerOptions))
  uploadFilteredFile(@UploadedFile() file: Express.Multer.File) {
    return 'Filtered file uploaded successfully!';
  }
}

And in the multer options:

import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';

export const multerOptions: MulterOptions = {
  fileFilter: (req, file, callback) => {
    if (file.mimetype.match(/\/png|jpeg|jpg|gif$/)) {
      callback(null, true);
    } else {
      callback(new Error('Unsupported file type'), false);
    }
  },
  limits: { fileSize: 1024 * 1024 * 5 }, // Limit to 5MB
};

Conclusion

Throughout this tutorial, we’ve explored multiple facets of handling form-data in NestJS, showing how the framework’s modern TypeScript support can simplify this critical web development task. Whether it’s basic file uploads, validation, or custom configurations, NestJS provides the tools necessary for efficient and secure file handling.

Next Article: How to Dockerize a NestJS Application for Production

Previous Article: NestJS: How to Use ConfigService with TypeOrmModule

Series: Nest.js Tutorials: From Basics to Advanced

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates