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 ascloudinary
. - 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 handlingmultipart/form-data
, which is used for uploading files. This method will return aPromise
with eitherUploadApiErrorResponse
orUploadApiResponse
. - 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: