Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Загрузка файлов

Файлы отправляют не в 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. Имя в интерцепторе и в форме должно быть одинаковым.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.