Загрузка файлов
Файлы отправляют не в JSON, а в формате multipart/form-data — так же, как браузер отправляет форму с полем выбора файла. NestJS разбирает такие запросы с помощью библиотеки Multer, которая уже входит в пакет @nestjs/platform-express. Добавим к статьям обложку.
Подготовка
npm i -D @types/multerПакет добавляет TypeScript-тип Express.Multer.File, которым описывается загруженный файл. Создайте в корне проекта папку uploads и добавьте её в .gitignore.
Маршрут загрузки
// src/posts/posts.controller.ts
import { Controller, Param, ParseIntPipe, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { extname } from 'node:path';
import { diskStorage } from 'multer';
@Controller('posts')
export class PostsController {
@Post(':id/image')
@UseInterceptors(
FileInterceptor('image', {
storage: diskStorage({
destination: 'uploads',
filename: (req, file, callback) => {
callback(null, `${Date.now()}${extname(file.originalname)}`);
},
}),
}),
)
uploadImage(
@Param('id', ParseIntPipe) id: number,
@UploadedFile() file: Express.Multer.File,
) {
return { postId: id, filename: file.filename, size: file.size };
}
}FileInterceptor('image', ...) берёт из формы поле с именем image и сохраняет файл. diskStorage() задаёт папку и имя файла: текущее время плюс исходное расширение. Декоратор @UploadedFile() отдаёт методу сведения о файле.
Проверьте загрузку через curl — флаг -F отправляет форму, а @ перед именем означает «содержимое файла»:
curl -X POST http://localhost:3000/posts/1/image -F "image=@cover.jpg"Сервер ответит {"postId":1,"filename":"1767225600000.jpg","size":48213}, а в папке uploads появится файл. Имя файла сохраняют в новом столбце статьи, например @Column({ nullable: true }) image: string.
Проверка размера и типа
Без проверки можно загрузить что угодно и любого размера. Пайп ParseFilePipe принимает список проверок:
// src/posts/posts.controller.ts
import { FileTypeValidator, MaxFileSizeValidator, ParseFilePipe, UploadedFile } from '@nestjs/common';
uploadImage(
@UploadedFile(
new ParseFilePipe({
validators: [
new MaxFileSizeValidator({ maxSize: 2 * 1024 * 1024 }),
new FileTypeValidator({ fileType: /image\/(jpeg|png|webp)/ }),
],
}),
)
file: Express.Multer.File,
) {
return { filename: file.filename };
}Файл больше 2 МБ или не картинка вернёт ошибку 400. Если файл не прислан совсем, пайп тоже ответит 400.
Раздача загруженных файлов
Чтобы картинки открывались по адресу, включите статические файлы в src/main.ts:
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'node:path';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useStaticAssets(join(process.cwd(), 'uploads'), { prefix: '/uploads/' });
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();Теперь файл открывается по адресу http://localhost:3000/uploads/1767225600000.jpg.
Частая ошибка
Имя поля в FileInterceptor('image') не совпадает с именем поля формы на клиенте, например клиент отправляет file. Multer тогда отвечает ошибкой Unexpected field, а без интерцептора file просто равен undefined. Имя в интерцепторе и в форме должно быть одинаковым.