Cloudinary Storage Provider

This provider adds Cloudinary SDK configuration, Zod-validated environment variables, and a Multer middleware that accepts in-memory uploads (images, video, PDF) with type and size limits.


Installation Guide

npx servercn-cli add pr cloudinary-storage

Basic Implementation

src/configs/env.ts
import "dotenv-flow/config";
import { z } from "zod";
 
export const envSchema = z.object({
  CLOUDINARY_CLOUD_NAME: z.string(),
  CLOUDINARY_API_KEY: z.string(),
  CLOUDINARY_API_SECRET: z.string()
});
 
export type Env = z.infer<typeof envSchema>;
 
const result = envSchema.safeParse(process.env);
 
if (!result.success) {
  console.error("❌ Invalid environment configuration");
  console.error(z.prettifyError(result.error));
  process.exit(1);
}
 
export const env: Readonly<Env> = Object.freeze(result.data);
 
export default env;

src/configs/cloudinary.ts
import { v2 as cloudinary } from "cloudinary";
import env from "./env.ts";
 
cloudinary.config({
  cloud_name: env.CLOUDINARY_CLOUD_NAME,
  api_key: env.CLOUDINARY_API_KEY,
  api_secret: env.CLOUDINARY_API_SECRET
});
 
export default cloudinary;

The template also includes a commented uploadToCloudinary / deleteFileFromCloudinary example in the same file—copy it into a service or util when you wire routes.


src/middlewares/upload-file.ts
import multer from "multer";
 
export const ALLOWED_FILE_TYPES = [
  "image/jpeg",
  "image/png",
  "image/webp",
  "video/mp4",
  "video/mpeg",
  "video/quicktime",
  "application/pdf"
];
 
export const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
 
const storage = multer.memoryStorage();
 
const fileFilter: multer.Options["fileFilter"] = (_req, file, cb) => {
  if (!ALLOWED_FILE_TYPES.includes(file.mimetype)) {
    return cb(null, false);
  }
  cb(null, true);
};
 
const upload = multer({
  storage,
  limits: { fileSize: MAX_FILE_SIZE },
  fileFilter
});
 
export default upload;

Usage

Attach the middleware to a route, then pass req.file?.buffer to an upload helper that streams to Cloudinary (see the commented block in cloudinary.ts in the template).

src/services/cloudinary.service.ts
import { DeleteApiResponse } from "cloudinary";
import cloudinary from "../configs/cloudinary";
 
export interface UploadOptions {
    folder: string;
    resource_type?: "image" | "video" | "raw" | "auto";
}
 
export interface CloudinaryUploadResult {
    url: string;
    public_id: string;
    size: number;
}
 
export const uploadToCloudinary = (
    buffer: Buffer,
    options: UploadOptions
): Promise<CloudinaryUploadResult> => {
    return new Promise((resolve, reject) => {
        const stream = cloudinary.uploader.upload_stream(
            {
                folder: options.folder || "uploads",
                resource_type: options.resource_type || "auto"
            },
            (error, result) => {
                if (error || !result) {
                    return reject(error);
                }
                resolve({
                    url: result.secure_url,
                    public_id: result.public_id,
                    size: result.bytes
                });
            }
        );
 
        stream.end(buffer);
    });
};
 
export const deleteFileFromCloudinary = (
    publicIds: string[]
): Promise<DeleteApiResponse> => {
    return new Promise((resolve, reject) => {
        cloudinary.api.delete_resources(publicIds, (error, result) => {
            if (error || !result) {
                return reject(error);
            }
            resolve(result);
        });
    });
};

File & Folder Structure

Loading files...

Installation

npx servercn-cli add pr cloudinary-storage