Day 7 - 60 Days of Learning 2025


On June 7, 2025, I worked on improving the user controller with user logout controller and setup the file upload for user avatar and cover image to Cloudinary.
The GitHub link for the repository of beats-backend service.
User Logout Controller and Cloudinary Setup
User Logout Controller
Created controller for user logout, before that created middleware for auth so that when a user is logged in some of the user data is accessible through the request object which in injected by the auth middleware.
Inside auth.middleware.ts
file
import { config } from "dotenv";
import { APIError } from "../utils/apiError";
import { asyncHandler } from "../utils/asyncHandler";
import jwt, { JwtPayload } from "jsonwebtoken";
import { UserResponseType } from "../types/user.type";
import { User } from "../models/user.model";
import "../types/express.type";
// Accessing environment variables
config();
const accessTokenSecret = process.env.ACCESS_TOKEN_SECRET;
if (!accessTokenSecret) {
throw new APIError(400, "JWT configurations are missing");
}
const verifyJWT = asyncHandler(async (req, res, next) => {
try {
// Get access token from cookies for request header (authorization header)
const accessToken =
req.cookies?.accessToken ||
req.header("Authorization")?.replace("Bearer ", "");
// Check if have accessToken or not
if (!accessToken) {
throw new APIError(401, "Unauthorized request");
}
// Decode the accessToken
const decodedAccessToken = jwt.verify(
accessToken,
accessTokenSecret
) as JwtPayload;
// Get user from database
const user: UserResponseType | null = await User.findById(
decodedAccessToken?._id
).select("-password -refreshToken");
// Check for user
if (!user) {
throw new APIError(401, "Invalid access token");
}
// Add user to request object
req.user = user;
// Pass the control to next
next();
} catch (error) {
// If it's already an APIError, re-throw it
if (error instanceof APIError) {
throw error;
}
// Handle JWT-specific errors
if (error instanceof jwt.JsonWebTokenError) {
throw new APIError(401, "Invalid access token");
}
if (error instanceof jwt.TokenExpiredError) {
throw new APIError(401, "Access token expired");
}
// Handle other errors (likely database errors)
throw new APIError(500, "Internal server error");
}
});
export { verifyJWT };
Inside user.controller.ts
file
// Logout user
const logoutUser = asyncHandler(async (req, res) => {
// Unset the refresh token in database
await User.findByIdAndUpdate(req.user?._id, {
$unset: {
refreshToken: 1,
},
});
// Clear cookies and send back response
res
.status(200)
.clearCookie("accessToken", cookiesOptions)
.clearCookie("refreshToken", cookiesOptions)
.json(new APIResponse(200, "User logged out successfully"));
});
Cloudinary Setup
Before setting up Cloudinary created a middleware for multer.
Inside multer.middleware.ts
file
import multer from "multer";
// Configure disk storage for multer
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "./public/temp");
},
filename: function (req, file, cb) {
cb(null, Date.now() + "-" + file.originalname);
},
});
export const upload = multer({ storage });
Create utility function for cloudinary
Inside utils/cloudinary.ts
file
import { v2 as cloudinary, UploadApiResponse } from "cloudinary";
import { config } from "dotenv";
import fs from "fs";
// Accessing environment variables
config();
const cloudName = process.env.CLOUDINARY_CLOUD_NAME;
const apiKey = process.env.CLOUDINARY_API_KEY;
const apiSecret = process.env.CLOUDINARY_API_SECRET;
const mainFolder = process.env.CLOUDINARY_MAIN_FOLDER;
// Configure cloudinary
cloudinary.config({
cloud_name: cloudName,
api_key: apiKey,
api_secret: apiSecret,
});
// Asset type
export type AssetType = "image" | "raw" | "video";
// Method for uploading
async function uploadOnCloudinary(
localFilePath: string,
folder: string
): Promise<UploadApiResponse | null> {
try {
if (!localFilePath) {
throw new Error("Invalid file path");
}
const response = await cloudinary.uploader.upload(localFilePath, {
resource_type: "auto",
folder: `${mainFolder}/${folder}`,
});
// Remove file from disk after uploading
fs.unlinkSync(localFilePath);
return response;
} catch (error) {
// Remove file from disk after failed to upload
fs.unlinkSync(localFilePath);
console.error("Failed to upload file:", error);
return null;
}
}
// Delete image file
async function deleteAssetOnCloudinary(
publicId: string,
assetType: AssetType = "image"
) {
try {
const response = await cloudinary.uploader.destroy(publicId, {
resource_type: assetType,
});
return response;
} catch (error) {
console.error("Failed to delete the asset:", error);
return null;
}
}
export { uploadOnCloudinary, deleteAssetOnCloudinary };
Previously when creating a user the avatar and cover image files were not uploaded, It was just a field. After this we can use uploadOnCloudinary
utility function to upload user's avatar and cover image to Cloudinary to storage.
Modifications inside loginUser
controller
// After checking for existing user
// Upload images to server's local path
const files = req.files as { [fieldname: string]: Express.Multer.File[] };
const avatarLocalPath =
files?.avatar && Array.isArray(files?.avatar) && files?.avatar?.length > 0
? files?.avatar[0]?.path
: "";
const coverImageLocalPath =
files?.coverImage &&
Array.isArray(files.coverImage) &&
files.coverImage.length > 0
? files.coverImage[0]?.path
: "";
// Upload images to cloudinary
const avatar =
avatarLocalPath &&
(await uploadOnCloudinary(avatarLocalPath, `users/${newUser.name}`));
const coverImage =
coverImageLocalPath &&
(await uploadOnCloudinary(coverImageLocalPath, `users/${newUser.name}`));
// Check if avatar image is uploaded successfully
if (!avatar) {
throw new APIError(
400,
"Avatar image is required, while uploading to Cloudinary"
);
}
// Create a new user
Object.assign(newUser, {
...newUser,
avatar: avatar.secure_url,
coverImage: coverImage ? coverImage.secure_url : "",
});
const user: UserType | null = await User.create(newUser);
// Then rest is same
Tested the configuration and setup
Subscribe to my newsletter
Read articles from Ashmin Bhujel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Ashmin Bhujel
Ashmin Bhujel
Learner | Software Developer | TypeScript | Node.js | React.js | MongoDB | Docker