Skip to content

Signed Image Uploading to Cloudinary with Angular and NestJS

In previous posts, I explained how to upload images, and render them in the browser using a Drag & Drop component written in Angular and TypeScript. That project has considered a secure cloud storage system for the uploaded files, provided by Cloudinary.

However, that solution didn’t consider Authenticated Uploads, which means that every API call was unsigned.

In this MediaJam we’ll add an additional layer (server-side code) using NestJS to provide signed uploads to the cloud.

You’ll need to have installed the following tools in your local environment:

  • The latest LTS version of Node.js version available is recommended.
  • Either NPM or Yarn as a package manager
  • The Nest CLI tool(Command-Line interface for NestJS)
  • The Angular CLI tool (Command-line interface for Angular)

Also, just make sure you have your Cloudinary account ready to use (you must verify your email if it’s your first time with the platform).

Let’s create a brand-new project using the Nest CLI tool following this syntax.

nest new <project-name>
Code language: HTML, XML (xml)

In this case, we’ll use image-server as the name, and the project will be implemented as the backend for the existing frontend application.

nest new image-server
Code language: JavaScript (javascript)

The previous command should create a project ready-to-go, and it will install all the initial dependencies required by the project.

Now, open the image-server project in your favorite code editor, and pay attention to the autogenerated files and directories.

|- image-server/
  |- src/
      |- main.ts
      |- app.module.ts
      |- app.controller.ts
      |- app.service.ts

By default, you can run the initial project (just to make sure it is working fine) using npm run start:dev. This command will run the project in a watch mode and it means the project will be rebuilt after every change you apply in the source code. Useful, right?

The Cloudinary platform provides a variety of options for customizing how the files can be uploaded from your application. For example, you can upload the files using the REST API or using a client library for your programming language.

In this case, we’ll proceed to install the Node.js SDK using the following command.

npm install cloudinary

The Cloudinary’s Node.js SDK provides a simple way for image, and video uploading as we are going to see later.

In the Angular world, we usually create a module to group components, directives, services, pipes, etc. In the same way, we can create a module to group content in a NestJS project.

The NestJS CLI tool provides also a way to generate different files for us. Let’s proceed to create a module, a service, and a provider:

nest generate module cloudinary
nest generate service cloudinary
nest generate provider cloudinary/cloudinary
Code language: JavaScript (javascript)

In order to use the Node.js SDK, you must configure the cloud_name, api_key, and the api_secret. These values are account-specific and can be found on the Dashboard page of your account console.

In other words, this is a useful way to configure the SDK globally instead of configuring each call.

Let’s open the cloudinary.provider.ts file, and add the following code.

// cloudinary.provider.ts

import { v2 as cloudinary } from 'cloudinary';

export const CloudinaryProvider = {
  provide: 'CLOUDINARY',
  useFactory: () => {
    return cloudinary.config({
        cloud_name: process.env.CLOUDINARY_NAME,
        api_key: process.env.CLOUDINARY_API_KEY,
        api_secret:
          process.env.CLOUDINARY_API_SECRET,
    });
  },
};
Code language: JavaScript (javascript)
  • The first line imports the v2 of the available API and assigns an alias to it as cloudinary.
  • Next, a Custom Provider is defined using the useFactory syntax for creating a provider dynamically.
  • The factory function takes the cloudinary API and performs the configuration using environment variables, which is a suggested method for storing the keys.

Remember to never expose your API keys in the source code!

One of the good things about the Node.js SDK is the addition of the response models: UploadApiResponse and UploadApiErrorResponse.

Let’s create a union type with both of them in a new file cloudinary/cloudinary-response.ts:

// cloudinary-response.ts
import { UploadApiErrorResponse, UploadApiResponse } from 'cloudinary';

export type CloudinaryResponse = UploadApiResponse | UploadApiErrorResponse;
Code language: JavaScript (javascript)

We’ll use the union type for the service implementation in the next section.

As in the Angular world, we can create a Service to have an additional layer to handle the upload operation. Also, the service can be reused from different controllers if that’s needed.

Let’s update the cloudinary.service.ts file, and create the uploadFile method as follows.

// cloudinary.service.ts

import { Injectable } from '@nestjs/common';
import { v2 as cloudinary } from 'cloudinary';
import { CloudinaryResponse } from './cloudinary-response';
const streamifier = require('streamifier');

@Injectable()
export class CloudinaryService {
  uploadFile(file: Express.Multer.File): Promise<CloudinaryResponse> {
    return new Promise<CloudinaryResponse>((resolve, reject) => {
      const uploadStream = cloudinary.uploader.upload_stream(
        (error, result) => {
          if (error) return reject(error);
          resolve(result);
        },
      );

      streamifier.createReadStream(file.buffer).pipe(uploadStream);
    });
  }
}
Code language: JavaScript (javascript)

So what’s happening in the above code snippet?

  • The uploadFile method is ready to receive a File using the Node.js middleware for handling multipart/form-data, which is used for uploading files. This method will return a Promise with either UploadApiErrorResponse or UploadApiResponse.
  • The cloudinary.uploader.upload_stream() writes down the uploader as a stream. There are different options for uploading to the cloud from Node.js or even the browser.

There’s an interesting note to take here: The Express.Multer.File is an object containing the file metadata, and it’s possible to access a Buffer containing the file itself using file.buffer.

However, before uploading this “File” to the cloud, it’s necessary to convert the Buffer into a Readable Stream for the SDK. There are several options to perform this operation, and this solution adds the streamifier package from NPM.

Install the package as part of the project:

npm install streamifier

And then you’ll be ready to convert any Buffer or string into a Readable Stream through the streamifier.createReadStream() method.

Read more about cloudinary.upload_stream method here.

Let’s update the cloudinary.module.ts file to ensure the right configuration of the Node.js SDK, and the Service:

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

@Module({
  providers: [CloudinaryProvider, CloudinaryService],
  exports: [CloudinaryProvider, CloudinaryService]
})
export class CloudinaryModule {}
Code language: JavaScript (javascript)

As you can see, it’s required to export both CloudinaryProvider and CloudinaryService from this module.

Let’s work with the app.controller.ts file to configure an appropriate route before writing the uploadImage method.

// app.controller.ts
import {
  Controller,
  Post,
} from '@nestjs/common';
import { CloudinaryService } from './cloudinary/cloudinary.service';

@Controller('image')
export class AppController {
  constructor(private readonly cloudinaryService: CloudinaryService) {}

  @Post('upload')
  uploadImage() {

  }
}
Code language: JavaScript (javascript)

The @Controller('image') decorator will define the base path as /image, and along with the @Post('upload') decorator, will define the POST /image/upload endpoint.

On other hand, an instance of the CloudinaryService class is injected into the controller as a private attribute: cloudinaryService.

NestJS has implemented File Upload capabilities to make it easier for us. It provides a built-in module based on the multer middleware package for Express. And, for better type safety, you must install the typings package:

npm install --save-dev @types/multer
Code language: CSS (css)

This middleware is enabled to handle multipart/form-data, which is primarily used for uploading files.

It’s time to implement the uploadImage method!

// app.controller.ts

import {
  Controller,
  Post,
  UploadedFile,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
// ... other imports

@Controller('image')
export class AppController {
  // ... Constructor

  @Post('upload')
  @UseInterceptors(FileInterceptor('file'))
  uploadImage(@UploadedFile() file: Express.Multer.File) {
    return this.cloudinaryService.uploadFile(file);
  }
}
Code language: JavaScript (javascript)

The previous method is meant to upload a single file. Then, a FileInterceptor is needed to “catch” the file, and it can be extracted from the request using the @UploadedFile decorator.

Finally, it performs a call to the CloudinaryService implemented above to delegate the upload behavior through the SDK. The service will return a Promise with either a UploadApiResponse or UploadApiErrorResponse.

Of course, it’s possible to process multiple files in a single method. This can be done using the @UploadedFiles() decorator instead:

  // app.controller.ts

  @Post('uploads')
  @UseInterceptors(FilesInterceptor('file[]', 5))
  uploadImages(@UploadedFiles() files: Express.Multer.File[]) {
    //... handle multiple files
  }
Code language: JavaScript (javascript)

You can see that we use the FilesInterceptor', and that it can be configured to support a maximum of 5` files to be uploaded at the same time.

As the documentation site says:

Cross-origin resource sharing (CORS) is a mechanism that allows resources to be requested from another domain.

You’ll find the following error in the browser’s console if you don’t have it enabled:

Access to XMLHttpRequest at 'http://localhost:3000/image/upload' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Code language: JavaScript (javascript)

NestJS makes use of the Express cors package and it can be customized for you. To enable CORS, just add a call to enableCors() method as follows in the main.ts file.

// main.ts
//... imports

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors(); // <- enable CORS
  await app.listen(3000);
}
Code language: JavaScript (javascript)

Find the source code available in GitHub both for the Backend (using NestJS) and the Frontend (using Angular).

If you prefer, you can play around with the project in CodeSandbox – Uploading Images to Cloudinary with Angular too, to see the image uploading in action:


Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work.

Back to top

Featured Post