File Upload (Cloudinary)
The File Upload component provides a standardized way to handle file uploads in ServerCN using Cloudinary as the storage provider.
It abstracts common concerns such as multipart handling, Cloudinary configuration, and secure uploads, while integrating cleanly with the rest of the ServerCN backend utilities.
Features
- Cloudinary-backed file storage
- Supports images, videos, and raw files
- Secure server-side uploads
- Express-compatible middleware
- Works seamlessly with
ApiError,AsyncHandler, and middleware
Installation Guide
This component requires additional ServerCN components.
👉 Note: You do not need to install any servercn dependencies manually. Installing this component will automatically install all required servercn dependencies. Manual installation is optional if you prefer to manage dependencies yourself.
1. Install ServerCN dependencies(Optional)
- HTTP Status Codes:
npx servercn add http-status-codesDocumentation: HTTP Status Codes
- Api Response Handler:
npx servercn add response-formatterDocumentation: Api Response Handler
- Api Error Handler:
npx servercn add error-handlerDocumentation: Api Error Handler
- Logger:
npx servercn add loggerDocumentation: Logger
- Async Handler:
npx servercn add async-handlerDocumentation: Async Handler
- Global Error Handler:
npx servercn add global-error-handlerDocumentation: Global Error Handler
2. Install this component
npx servercn add file-uploadPrerequisites
You must have a Cloudinary account. Click here if you don't have.
Define the following environment variables:
PORT="8000"
NODE_ENV="development"
LOG_LEVEL="info"
CLOUDINARY_CLOUD_NAME="your-cloud-name"
CLOUDINARY_API_KEY="your-api-key"
CLOUDINARY_API_SECRET="your-api-secret"Ensure the following configuration are defined:
src/configs/env.ts
interface Config {
PORT: number;
NODE_ENV: string;
LOG_LEVEL: string;
CLOUDINARY_CLOUD_NAME: string;
CLOUDINARY_API_KEY: string;
CLOUDINARY_API_SECRET: string;
}
const env: Config = {
PORT: Number(process.env.PORT) || 3000,
NODE_ENV: process.env.NODE_ENV || "development",
LOG_LEVEL: process.env.LOG_LEVEL || "info",
CLOUDINARY_CLOUD_NAME: process.env.CLOUDINARY_CLOUD_NAME!,
CLOUDINARY_API_KEY: process.env.CLOUDINARY_API_KEY!,
CLOUDINARY_API_SECRET: process.env.CLOUDINARY_API_SECRET!
};
export default env;Basic Implementation
1. Cloudinary Configuration
Create a Cloudinary configuration file:
src/configs/cloudinary.ts
import { v2 as cloudinary } from "cloudinary";
import env from "./env";
cloudinary.config({
cloud_name: env.CLOUDINARY_CLOUD_NAME,
api_key: env.CLOUDINARY_API_KEY,
api_secret: env.CLOUDINARY_API_SECRET
});
export default cloudinary;2. Upload Middleware
ServerCN uses multer to handle multipart file uploads.
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;3. Cloudinary Services
Services for uploading files to Cloudinary and delete files from cloudinary.
src/services/cloudinary.service.ts or
src/utils/cloudinary.ts or
src/helpers/cloudinary.ts
import { DeleteApiResponse } from "cloudinary";
import cloudinary from "../configs/cloudinary.js";
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);
});
});
};Usage Example
src/controllers/upload.controller.ts
import { Request, Response } from "express";
import {
CloudinaryUploadResult,
deleteFileFromCloudinary,
uploadToCloudinary
} from "../services/cloudinary.service";
import { ApiError } from "../utils/api-error";
import { ApiResponse } from "../utils/api-response";
import { AsyncHandler } from "../utils/async-handler";
export const uploadFile = AsyncHandler(async (req: Request, res: Response) => {
if (!req.file) {
throw ApiError.badRequest("File is required");
}
const file = await uploadToCloudinary(req.file.buffer, {
folder: "uploads/files",
resource_type: "auto"
});
return ApiResponse.created(res, "File uploaded successfully", file);
});
export const uploadMultipleFile = AsyncHandler(
async (req: Request, res: Response) => {
const files = req.files as Express.Multer.File[];
if (!files || files.length === 0) {
throw ApiError.badRequest("Files are required");
}
const results: CloudinaryUploadResult[] = await Promise.all(
files.map(async file => {
return await uploadToCloudinary(file.buffer, {
folder: "uploads/images"
});
})
);
return ApiResponse.created(res, "Files uploaded successfully", results);
}
);
export const deleteFile = AsyncHandler(async (req: Request, res: Response) => {
const { public_id } = req.body;
if (!public_id) {
throw ApiError.badRequest("File ID is required");
}
await deleteFileFromCloudinary([public_id]);
return ApiResponse.Success(res, "File deleted successfully", null, 200);
});src/routes/upload.routes.ts
import { Router } from "express";
import upload from "../middlewares/upload-file";
import {
deleteFile,
uploadFile,
uploadMultipleFile
} from "../controllers/upload.controller";
const router = Router();
router.post("/file", upload.single("file"), uploadFile);
router.post("/files", upload.array("files", 10), uploadMultipleFile);
router.delete("/", deleteFile);
export default router;src/app.ts
import express, { Application } from "express";
import "dotenv/config";
import { errorHandler } from "./middlewares/error-handler";
import { logger } from "./utils/logger";
import uploadRoutes from "./routes/upload.routes";
import env from "./configs/env";
const app: Application = express();
const PORT = env.PORT;
// middlewares
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// routes
app.use("/api/uploads", uploadRoutes);
// Global error handler
app.use(errorHandler);
app.listen(PORT, () => {
logger.info(`Server is running on http://localhost:${PORT}`);
});Security Best Practices
- Enforce file size limits
- Validate MIME types when required
- Use private folders for sensitive uploads
- Never expose Cloudinary secrets to the client
- Prefer authenticated routes for uploads