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.
