NestJS File Upload

File upload lets clients send binary files — images, PDFs, spreadsheets, videos — to your NestJS server. NestJS uses Multer under the hood (via Express) to handle multipart form-data requests. The @nestjs/platform-express package includes Multer, so no extra installation is needed for basic usage.

How File Upload Works

Client sends:
  POST /users/avatar
  Content-Type: multipart/form-data
  Body: [binary file data + optional text fields]

         |
         v
  Multer middleware processes the multipart body
         |
         v
  File saved to disk or held in memory
         |
         v
  @UploadedFile() decorator injects file info into controller
         |
         v
  Controller/Service handles the file (move, resize, upload to cloud)

Single File Upload

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

@Controller('users')
export class UsersController {

  @Post('avatar')
  @UseInterceptors(
    FileInterceptor('avatar', {
      storage: diskStorage({
        destination: './uploads/avatars',
        filename: (req, file, cb) => {
          const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
          cb(null, `${uniqueSuffix}${extname(file.originalname)}`);
        },
      }),
    })
  )
  uploadAvatar(
    @UploadedFile() file: Express.Multer.File,
    @Body('userId') userId: string,
  ) {
    return {
      filename: file.filename,
      path: file.path,
      size: file.size,
    };
  }
}

The Uploaded File Object

file: Express.Multer.File
{
  fieldname: 'avatar',          // form field name
  originalname: 'photo.jpg',    // original filename from client
  encoding: '7bit',
  mimetype: 'image/jpeg',       // file type
  destination: './uploads/avatars',
  filename: '1705000000-123456789.jpg',  // saved filename
  path: 'uploads/avatars/1705000000-123456789.jpg',
  size: 204800,                 // file size in bytes
}

Multiple File Upload

@Post('gallery')
@UseInterceptors(FilesInterceptor('photos', 10))  // up to 10 files
uploadGallery(@UploadedFiles() files: Express.Multer.File[]) {
  return files.map(file => ({ filename: file.filename, size: file.size }));
}

File Validation with Pipes

Validate the file type and size before processing it using a custom pipe or the built-in ParseFilePipe:

@Post('avatar')
@UseInterceptors(FileInterceptor('avatar'))
uploadAvatar(
  @UploadedFile(
    new ParseFilePipe({
      validators: [
        new MaxFileSizeValidator({ maxSize: 2 * 1024 * 1024 }), // 2MB
        new FileTypeValidator({ fileType: /jpeg|png|webp/ }),
      ],
    })
  )
  file: Express.Multer.File,
) {
  return { filename: file.originalname };
}

Memory Storage vs Disk Storage

Storage Type    | File Location    | Best For
----------------|-----------------|----------------------------
diskStorage     | Saved to disk   | Large files, permanent storage
memoryStorage   | Held in RAM     | Small files, temporary, cloud upload

// Memory storage (file available as buffer)
FileInterceptor('file', {
  storage: memoryStorage(),
})
// file.buffer contains raw bytes — pass to S3 or image processor

Uploading to Amazon S3

Most production apps store files in cloud storage rather than on the server disk. Using memory storage, pass the file buffer directly to the AWS SDK:

npm install @aws-sdk/client-s3

@Post('avatar')
@UseInterceptors(FileInterceptor('avatar', { storage: memoryStorage() }))
async uploadToS3(@UploadedFile() file: Express.Multer.File) {
  const key = `avatars/${Date.now()}-${file.originalname}`;

  await this.s3Client.send(new PutObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: key,
    Body: file.buffer,
    ContentType: file.mimetype,
  }));

  return { url: `https://${process.env.S3_BUCKET}.s3.amazonaws.com/${key}` };
}

Serving Static Files

When files are stored on disk, serve them as static assets so clients can access them via URL:

// main.ts
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';

const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useStaticAssets(join(__dirname, '..', 'uploads'), {
  prefix: '/uploads',
});
// Files at ./uploads/avatars/photo.jpg are accessible at GET /uploads/avatars/photo.jpg

File upload expands your API from text-based JSON exchange to handling real binary content. The same NestJS patterns — interceptors, pipes, decorators — handle the complexity of multipart parsing cleanly, and the validation pipe ensures clients cannot bypass file type or size restrictions.

Leave a Comment

Your email address will not be published. Required fields are marked *