【nest.js】创建一个上传api
下面详细介绍如何在 NestJS 中创建一个上传文件的功能。
Handling file uploads in NestJS is straightforward thanks to its integration with multer
, a popular Node.js middleware for handling multipart/form-data
.
Here’s a comprehensive example demonstrating how to set up an upload API in NestJS, covering single file, multiple files, and validation.
1. Project Setup (if you haven’t already)
# Create a new NestJS project
nest new nestjs-upload-example
cd nestjs-upload-example# Install multer and the NestJS platform-express package
npm install --save @nestjs/platform-express multer
# or
yarn add @nestjs/platform-express multer
2. Create the Uploads Directory
Create a folder where your uploaded files will be stored. For example, at the root of your project:
mkdir uploads
3. Implement the Upload Controller
This will contain your API endpoints for file uploads.
src/upload/upload.controller.ts
(create a new folder src/upload
)
import {Controller,Post,UploadedFile,UploadedFiles,UseInterceptors,ParseFilePipe,MaxFileSizeValidator,FileTypeValidator,HttpStatus,HttpException,
} from '@nestjs/common';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname, join } from 'path'; // Import 'join' from 'path'
import * as fs from 'fs'; // Import 'fs' for directory creation// Helper function to ensure the upload directory exists
const ensureDirExists = (dir: string) => {if (!fs.existsSync(dir)) {fs.mkdirSync(dir, { recursive: true });}
};const UPLOAD_DIR = './uploads'; // Define your upload directory@Controller('upload')
export class UploadController {constructor() {// Ensure the upload directory exists when the controller is initializedensureDirExists(UPLOAD_DIR);}// --- Single File Upload (without custom name, simple dest) ---@Post('single')@UseInterceptors(FileInterceptor('file', {// destination: UPLOAD_DIR, // Using 'dest' generates a random filename// For more control, use storage as shown belowstorage: diskStorage({destination: UPLOAD_DIR,filename: (req, file, cb) => {// Generating a unique filenameconst randomName = Array(32).fill(null).map(() => Math.round(Math.random() * 16).toString(16)).join('');cb(null, `${randomName}${extname(file.originalname)}`);},}),limits: {fileSize: 1024 * 1024 * 5, // 5 MB limit},fileFilter: (req, file, cb) => {if (!file.originalname.match(/\.(jpg|jpeg|png|gif|pdf)$/)) {return cb(new HttpException('Only image and PDF files are allowed!',HttpStatus.BAD_REQUEST,),false,);}cb(null, true);},}),)uploadSingleFile(@UploadedFile()file: Express.Multer.File, // 'Express.Multer.File' is the type for the file object) {if (!file) {throw new HttpException('File not provided', HttpStatus.BAD_REQUEST);}console.log('Uploaded file:', file);return {message: 'File uploaded successfully',filename: file.filename,path: file.path,};}// --- Single File Upload with ParseFilePipe (NestJS 9+ recommended for validation) ---@Post('single-validated')@UseInterceptors(FileInterceptor('file', {storage: diskStorage({destination: UPLOAD_DIR,filename: (req, file, cb) => {const randomName = Array(32).fill(null).map(() => Math.round(Math.random() * 16).toString(16)).join('');cb(null, `${randomName}${extname(file.originalname)}`);},}),// Note: Multer's fileFilter and limits can still be used for initial checks,// but ParseFilePipe offers more granular control and better error messages.}),)uploadValidatedFile(@UploadedFile(new ParseFilePipe({validators: [new MaxFileSizeValidator({ maxSize: 1024 * 1024 * 2 }), // 2 MBnew FileTypeValidator({ fileType: 'image/(jpeg|png|gif)' }), // Only images// Or for multiple types: new FileTypeValidator({ fileType: '(jpg|jpeg|png|gif|pdf)$' })],errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, // Custom error code}),)file: Express.Multer.File,) {if (!file) {throw new HttpException('File not provided', HttpStatus.BAD_REQUEST);}console.log('Uploaded validated file:', file);return {message: 'Validated file uploaded successfully',filename: file.filename,path: file.path,};}// --- Multiple Files Upload ---@Post('multiple')@UseInterceptors(FilesInterceptor('files', 10, {// 'files' is the field name from the form, 10 is max countstorage: diskStorage({destination: UPLOAD_DIR,filename: (req, file, cb) => {const randomName = Array(32).fill(null).map(() => Math.round(Math.random() * 16).toString(16)).join('');cb(null, `${randomName}${extname(file.originalname)}`);},}),limits: {fileSize: 1024 * 1024 * 5, // 5 MB per file},fileFilter: (req, file, cb) => {if (!file.originalname.match(/\.(jpg|jpeg|png|gif|pdf)$/)) {return cb(new HttpException('Only image and PDF files are allowed for multiple uploads!',HttpStatus.BAD_REQUEST,),false,);}cb(null, true);},}),)uploadMultipleFiles(@UploadedFiles()files: Array<Express.Multer.File>,) {if (!files || files.length === 0) {throw new HttpException('No files provided', HttpStatus.BAD_REQUEST);}console.log('Uploaded files:', files);return {message: 'Files uploaded successfully',count: files.length,filenames: files.map((f) => f.filename),paths: files.map((f) => f.path),};}
}
4. Register the Controller in your Module
You need to add UploadController
to the controllers
array in your app.module.ts
or a feature module.
src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UploadController } from './upload/upload.controller'; // Import your new controller@Module({imports: [],controllers: [AppController, UploadController], // Add UploadController hereproviders: [AppService],
})
export class AppModule {}
5. Running the Application
npm run start:dev
# or
yarn start:dev
Your server will typically run on http://localhost:3000
.
6. Testing the Endpoints
You can use curl
, Postman, Insomnia, or a simple HTML form to test these endpoints.
A. Using curl
(Recommended for quick tests)
Single File Upload:
curl -X POST \http://localhost:3000/upload/single \-H 'Content-Type: multipart/form-data' \-F 'file=@/path/to/your/image.jpg' # Replace with actual path to an image or PDF
Single File Upload with Validation:
curl -X POST \http://localhost:3000/upload/single-validated \-H 'Content-Type: multipart/form-data' \-F 'file=@/path/to/your/image.png' # Must be image and <= 2MB
Multiple Files Upload:
curl -X POST \http://localhost:3000/upload/multiple \-H 'Content-Type: multipart/form-data' \-F 'files=@/path/to/your/image1.jpg' \-F 'files=@/path/to/your/document.pdf' \-F 'files=@/path/to/your/image2.png'
(Note: Each -F 'files=@...'
needs to use the same field name files
)
B. Using a simple HTML Form
Create an index.html
file in your project root for testing:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>NestJS Upload Test</title><style>body {font-family: sans-serif;padding: 20px;}form {margin-bottom: 30px;padding: 20px;border: 1px solid #ccc;border-radius: 5px;}input[type='file'] {display: block;margin-bottom: 10px;}button {padding: 8px 15px;background-color: #007bff;color: white;border: none;border-radius: 4px;cursor: pointer;}button:hover {background-color: #0056b3;}h2 {margin-top: 0;}</style></head><body><h1>NestJS File Upload Examples</h1><formaction="http://localhost:3000/upload/single"method="post"enctype="multipart/form-data"><h2>Single File Upload (Basic)</h2><input type="file" name="file" /><button type="submit">Upload Single File</button></form><formaction="http://localhost:3000/upload/single-validated"method="post"enctype="multipart/form-data"><h2>Single File Upload (Validated, Max 2MB, Image Only)</h2><input type="file" name="file" /><button type="submit">Upload Validated File</button></form><formaction="http://localhost:3000/upload/multiple"method="post"enctype="multipart/form-data"><h2>Multiple Files Upload (Max 10 files, up to 5MB each, Image/PDF)</h2><input type="file" name="files" multiple /><button type="submit">Upload Multiple Files</button></form></body>
</html>
Open this index.html
file in your browser to test the uploads.
Key Concepts and Notes:
@nestjs/platform-express
: This package provides theFileInterceptor
andFilesInterceptor
decorators, which are wrappers aroundmulter
.multer
: The underlying library for handlingmultipart/form-data
.FileInterceptor(fieldName, options)
: For a single file.fieldName
is the name of the input field in your form (e.g.,<input type="file" name="file">
).options
are Multer options.FilesInterceptor(fieldName, maxCount, options)
: For multiple files.maxCount
is the maximum number of files allowed.
@UploadedFile()
/@UploadedFiles()
: Decorators to inject the uploaded file(s) into your route handler. The type isExpress.Multer.File
for a single file, andArray<Express.Multer.File>
for multiple files.diskStorage
: Thismulter
option gives you fine-grained control over where files are stored and how they are named.destination
: A function or string that determines the folder where the files should be stored.filename
: A function that determines the name of the file inside thedestination
folder.
limits
: Multer options to restrict file size, number of files, etc.fileFilter
: A function that lets you control which files should be uploaded and which should be skipped. It’s useful for checking file types based on extensions or MIME types. If you returnfalse
or throw anHttpException
, the file will be rejected.ParseFilePipe
(NestJS 9+): This is the recommended NestJS-native way for more robust and descriptive file validation. It works with@UploadedFile()
and@UploadedFiles()
(though the latter requires an additional pipe for array validation). It provides:MaxFileSizeValidator
: To check the file size.FileTypeValidator
: To check the file’s MIME type.- Better error messages and custom
errorHttpStatusCode
.
- Error Handling: Multer errors (e.g., file size limits, invalid file type from
fileFilter
) are automatically caught by NestJS’s exception filters and converted into appropriate HTTP responses.ParseFilePipe
also throwsBadRequestException
by default, which NestJS handles. - Security: Always validate file types, sizes, and potentially scan for malicious content. Storing user-uploaded files directly in your web server’s public directory without sanitization can be a security risk. It’s often better to serve them through a dedicated route or store them in a CDN/cloud storage.
- Directory Creation: The
ensureDirExists
helper makes sure youruploads
folder exists before Multer tries to save files there.
This example provides a solid foundation for building file upload functionality in your NestJS applications.