DEV Community

Stanislav Karol
Stanislav Karol

Posted on

Загрузка изображений в cloudinary- модуль для NestJS

В работе над проектом потребовалось сделать загрузку картинок. Когда я писал "Кулинарную книгу", то использовал сервис Сloudinary. Но там был express, как дела обстоят в Nest? Готового модуля нет, но зато есть документация по модулям: Стартовая страница как написать свой модуль.
Постараюсь по шагам описать свои действия как написал модуль.

Подготовка настроек

Ну, конечно, сперва нужно в недоступном для случайных зрителей месте сохранить параметры доступа к сервису. Вот что в файле .env:

CLOUDINARY_CLOUD_NAME=cloudinaryCloudName
CLOUDINARY_API_KEY=1029384756
CLOUDINARY_API_SECRET=secrectapi
Enter fullscreen mode Exit fullscreen mode

Написание провайдера

Модуль будет хранится в каталоге src/cloudinary .

Для модуля необходим провайдер. В моём случае всё, что нужно от провайдера- это получение вышенаписанных параметров и передача их сервису. Чуть далее, мы узнаем как такой провайдер можно создать, поэтому пишем вот такой файл src/cloudinary/cloudinary.provider.ts:

import { Provider } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

export const cloudinaryProvider: Provider = {
  provide: 'CLOUDINARY_MODULE_OPTIONS',
  inject: [ConfigService],
  useFactory: async (configService: ConfigService) => {
    return {
      cloudinaryCloudName: configService.get<string>('CLOUDINARY_CLOUD_NAME'),
      cloudinaryApiKey: configService.get<string>('CLOUDINARY_API_KEY'),
      cloudinaryApiSecret: configService.get<string>('CLOUDINARY_API_SECRET'),
    };
  },
};
Enter fullscreen mode Exit fullscreen mode

Содержимое из этого файла должно попасть в сервис, который будет отвечать за загрузку файлов.
Начало этого сервиса будет таким:
src/cloudinary/cloudinary.service.ts

import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { UploadApiErrorResponse, UploadApiResponse, v2 } from 'cloudinary';

import { CloudinarySettings } from './types/types';


@Injectable()
export class CloudinaryService {
  constructor(@Inject('CLOUDINARY_MODULE_OPTIONS') options: CloudinarySettings) {
    this.cloudinary = v2;
    this.cloudinary.config({
      cloud_name: options.cloudinaryCloudName,
      api_key: options.cloudinaryApiKey,
      api_secret: options.cloudinaryApiSecret,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

CloudinarySettings из src/cloudinary/types/types.ts описывает известные нам опции, которые приходят от провайдера:

export type CloudinarySettings = {
  cloudinaryCloudName: string;
  cloudinaryApiKey: string;
  cloudinaryApiSecret: string;
};
Enter fullscreen mode Exit fullscreen mode

И как говорит документация cloudinary для загрузки используется объект поток (Stream), но через upload приходят файлы в виде буфера. Вопросом о переводе буфера в поток будет заниматься пакет buffer-to-stream, который нужно будет поставить. Естественно нужно поставить ещё пакет cloudinary и в dev-зависимость пакет @types/buffer-to-stream.
А дальше дело за малым: Написать метод, который будет загружать картинки в облако. У меня для тэгов к изображениям используется тип TypeUpload:

export type TypeUpload = 'avatar' | 'content';
Enter fullscreen mode Exit fullscreen mode

В нём я в тэгах обозначаю, что за картинки загружены: аватар или содержимое. Вот этот сервис, который загружает картинки: src/cloudinary/cloudinary.service.ts

import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { UploadApiErrorResponse, UploadApiResponse, v2 } from 'cloudinary';
import toStream = require('buffer-to-stream');

import { CloudinarySettings, TypeUpload } from './types/types';
import { CLOUDINARY_MODULE_OPTIONS } from './cloudinary.constants';
import { TAG_BASE_NAME } from './constants/tagFiles';

@Injectable()
export class CloudinaryService {
  constructor(@Inject(CLOUDINARY_MODULE_OPTIONS) options: CloudinarySettings) {
    v2.config({
      cloud_name: options.cloudinaryCloudName,
      api_key: options.cloudinaryApiKey,
      api_secret: options.cloudinaryApiSecret,
    });
  }

  /**
   * Метод загрузки изображений
   */
  async uploadImage({
    file,
    type = 'avatar',
  }: {
    file: Express.Multer.File;
    type: TypeUpload;
  }): Promise<UploadApiResponse> {
    // Загрузка картинки в облако
    const uploadResponse = await this.uploadFile(file);
    if (this.isUploadApiErrorResponse(uploadResponse)) {
      throw new HttpException(
        'Upload avatar error',
        HttpStatus.UNPROCESSABLE_ENTITY,
      );
    }
    // После загрузки картинки назначить ей тэг:
    const tag = `${TAG_BASE_NAME}_${type}`;
    const { public_id } = uploadResponse;
    await v2.uploader.add_tag(tag, [public_id]);
    return uploadResponse;
  }

  /**
   * Загрузка файла в сервис cloudinary
   */
  private async uploadFile(
    file: Express.Multer.File,
  ): Promise<UploadApiResponse | UploadApiErrorResponse> {
    if (!file) {
      throw new HttpException(
        'File not present!',
        HttpStatus.UNPROCESSABLE_ENTITY,
      );
    }
    return new Promise((resolve, reject) => {
      const upload = v2.uploader.upload_stream((error, result) => {
        if (error) return reject(error);
        resolve(result);
      });
      // Буфер в виде потока отправить в функцию upload
      toStream(file.buffer).pipe(upload);
    });
  }

  /**
   * Проверка результата загрузки на наличие ошибок
   * Т.е. - проверка типа: Это UploadApiErrorResponse ?
   */
  private isUploadApiErrorResponse(
    response: UploadApiErrorResponse | UploadApiResponse,
  ): response is UploadApiErrorResponse {
    return (<UploadApiErrorResponse>response).http_code !== undefined;
  }
}
Enter fullscreen mode Exit fullscreen mode

Ну и модуль, который который всё сводит в единую сущность:

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

import { cloudinaryProvider } from './cloudinary.provider';
import { CloudinaryService } from './cloudinary.service';

@Module({
  imports: [ConfigModule],
  providers: [CloudinaryService, cloudinaryProvider],
  exports: [CloudinaryService],
})
export class CloudinaryModule {}
Enter fullscreen mode Exit fullscreen mode

Исходный код можно увидеть на гитхаб - в этом репозитории есть этот самый модуль, он подключен и работает.

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay