Streamline Your File Uploads with Cloudinary and NestJS

Streamline Your File Uploads with Cloudinary and NestJS

Cloudinary is a powerful serverless cloud-based storage infrastructure that allows easy file uploading for NestJS projects. In this article, we will explore the various methods of file uploading to Cloudinary and how to set it up in your NestJS project. Whether you are uploading file buffers, file URLs, or Base64 encoded strings, the Cloudinary NodeJS SDK and the steps outlined in this article make it easy to incorporate advanced file uploading into your NestJS project.

If you don't know how to handle file upload in NestJs, this article explains how to efficiently incorporate file upload and validation into your NestJS project.

Creating a Cloudinary Module

One way to easily set it up in your NestJs projects is by creating a module for it. This allows you to use Dependency Injection design to use it in other providers or controllers.

Installation

Install Cloudinary NodeJs SDK

npm install cloudinary

Create a new module, cloudinary.

Create file constants.ts.

export const CLOUDINARY = 'Cloudinary';

Add file cloudinary.provider.ts, paste the code below into the file.


import { ConfigService } from '@nestjs/config';
import { v2 } from 'cloudinary';
import { CLOUDINARY } from './constants';

export const CloudinaryProvider = {
  provide: CLOUDINARY,
  useFactory: (config: ConfigService) => {
    return v2.config({
      cloud_name: config.get('CLOUDINARY_CLOUD_NAME'),
      api_key: config.get('CLOUDINARY_API_KEY'),
      api_secret: config.get('CLOUDINARY_API_SECRET'),
    });
  },
  inject: [ConfigService],
};

ConfigService is used to assess keys in your secret file. For example, a .env file.

Don't know how to set up env configurations in NestJs? Read more.

Don't know how to create factory providers in NestJs? Read more.

The CloudinaryProvider provider is a Factory Provider. The useFactory allows you to create providers dynamically. You can also inject other module-imported services into the useFactory function.

Create the Cloudinary Service Injectable

Add a new file, cloudinary.service.ts

import { Injectable } from '@nestjs/common';


@Injectable()
export class CloudinaryService {   
}

This service will contain methods for uploading files to Cloudinary.

Create the module file

Add a new file, cloudinary.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { CloudinaryProvider } from './cloudinary.provider';
import { CloudinaryService } from './cloudinary.service';

@Module({
    imports: [ConfigModule],
    providers: [CloudinaryProvider, CloudinaryService],
    exports: [CloudinaryProvider, CloudinaryService],
})
export class CloudinaryModule {}

The ConfigModule is added to the module imports because ConfigService in the CloudinaryProvider.

Setup .env file

Create a .env in your root folder. Depending on how you structure your project, a general root folder could be the folder containing your package.json file or where you run the command to start your server.

CLOUDINARY_CLOUD_NAME=test
CLOUDINARY_API_KEY=test
CLOUDINARY_API_SECRET=test

Now you need to create a free Cloudinary account. After creating your account, you can follow this guide to get your API key, secret and cloud name.

Setup ConfigModule

In your app.module.ts you will need to configure the ConfigModule. This makes sure it finds the .env file and populate itself with the keys and values, thereby making you access them with ConfigService.

import {
  Module,
  NestModule,
} from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot(),
  ],
  controllers: [AppController],
  providers: [
    AppService,
  ],
})
export class AppModule implements NestModule {
}

Using Form data - File Buffers

Single File Upload

Now that you have your CloudinaryService. When you create a route that accepts file buffers using Multer. You can upload this file using the service. You just have to integrate the upload method from the Cloudinary package.

Add the method below to your service.

async uploadFile(
  file: Express.Multer.File,
): Promise<UploadApiResponse | UploadApiErrorResponse> {
  return new Promise((resolve, reject) => {
    v2.uploader.upload_stream(
      {
        resource_type: 'auto',
      },
      (error, result) => {
        if (error) return reject(error);
        resolve(result);
      }
    ).end(file.buffer)
  })
}

This simple method uses v2.uploader.upload_stream to upload your file to Cloudinary in chunks, which is good for uploading large files in chunks instead of one long request.

An UploadApiResponse or UploadApiErrorResponse is returned by the function. You can use this to know if the file was successfully uploaded or not.

To access your uploaded file URL, use the example of a route for single file upload below to understand the process.

import { UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';

@Post('upload/single')
@UseInterceptors(FileInterceptor('file'))
@ApiOkResponse({ description: 'Upload image', type: String })
public async uploadSingleImage(@UploadedFile() file: Express.Multer.File) {
  const result = await this.cloudinaryService.uploadFile(file);
  return result.secure_url;
}

The secure_url property is the URL for the file uploaded.

Uploading multiple file buffers

Loop through all the files and upload each one after the other. The response is the list of secure URLs.

async uploadFiles(
  files: Express.Multer.File[],
) {
  const urls = await Promise.all(files.map(async (file): Promise<string> => {
    const { secure_url } = await this.uploadFile(file);
    return secure_url;
  }));
  return urls
}

The files were mapped over and uploaded, and their URLs were returned by the map callback function.

Uploading File with URLs

What if you have a file URL and not a file buffer? It's simpler to upload that using Cloudinary.

async uploadFileFromUrl(
  url: string,
): Promise<UploadApiResponse | UploadApiErrorResponse> {
  return v2.uploader.upload(url)
}

The URL passed to this would be uploaded to Cloudinary, which will then return a response containing the secure_url if successful.

Uploading multiple file urls

By following the same idea from uploading multiple file buffers. You can map through the urls, upload each and return the secure_url for each. Then the function will return a list of URLs.

async uploadFilesFromUrl(
  urls: string[],
) {
  return Promise.all(urls.map(async (url: string): Promise<string> => {
    const { secure_url } = await this.uploadFileFromUrl(url);
    return secure_url;
  }));
}

Uploading Base64 encoded strings

Base64 is a binary-to-text encoding scheme that represents binary data in an American Standard Code for Information Interchange (ASCII) string format. An example of binary data is an image. Base64 can be uploaded too as a file, it's a string and not a buffer, which makes it easier to parse and upload to Cloudinary than file buffers.

By utilizing the upload function from the Cloudinary package, a base64 string can be uploaded to Cloudinary and get a URL for the uploaded file.

async uploadFileFromBase64(
  data: string,
): Promise<UploadApiResponse | UploadApiErrorResponse> {
  return v2.uploader.upload(data)
}

async uploadManyBase64(
  files: string[],
) {
  const urls = await Promise.all(files.map(async (file): Promise<string> => {
    const { secure_url } = await this.uploadFileFromBase64(file);
    return secure_url;
  }));
  return urls
}

Conclusion

In conclusion, Cloudinary is a powerful tool for file uploading and storage in NestJS projects. With the help of the Cloudinary NodeJS SDK and the steps outlined in this article, developers can easily set up a module for Cloudinary and upload files using various available methods including file buffers, file URLs, and Base64 encoded strings. While Cloudinary is free to set up initially, developers may need to pay for used services once they start using it past some limit.

I'd love to connect ❤️ with you on Twitter | LinkedIn | GitHub | Portfolio

See you in my next blog article. Take care!!!