🔍 Understanding Key Backend Concepts in a Real Express + MongoDB API

When building backend APIs using Express.js and MongoDB, you’ll come across many concepts like route parameters, aggregation pipelines, and error handling. This article breaks down some essential backend topics using an example like getUserChannelProfile
, a function used to fetch a YouTube-style user profile with subscriber data.
Let’s dive in 👇
🔧 Express.js Route Handler
In Express, a controller is a function that handles incoming requests. It's where we put the logic to process data and send a response.
jsCopyEditconst getUserChannelProfile = asyncHandler(async (req, res) => {
// logic goes here
});
req
(request): contains details about what the user sent (URL, params, body).res
(response): used to send back data or messages to the client.
🛣 Route Parameters in Express
When we access dynamic parts of a URL like /channel/:userName
, we get the value using req.params
.
jsCopyEditconst { userName } = req.params;
We also validate it to avoid errors:
jsCopyEditif (!userName?.trim()) {
throw new ApiError(400, "Username is missing");
}
🔄 Async/Await and Error Handling
To handle database operations smoothly, we use async/await
. It makes code easier to read and avoids “callback hell.”
We also use a package like express-async-handler
to catch errors without repeating try...catch
.
jsCopyEditconst getUserChannelProfile = asyncHandler(async (req, res) => { ... });
🧩 MongoDB Aggregation Pipeline
Aggregation in MongoDB lets us process data in stages, similar to a pipeline. Each stage transforms the data before passing it to the next.
Example:
jsCopyEditUser.aggregate([
{ $match: { userName: "jayesh" } },
{ $lookup: { ... } },
{ $addFields: { ... } },
{ $project: { ... } }
]);
🔍 $match
in MongoDB
This stage filters documents, similar to a SQL WHERE
clause.
jsCopyEdit{ $match: { userName: userName.toLowerCase() } }
We use .toLowerCase()
to make the search case-insensitive.
🔗 $lookup
in MongoDB
Used to join one collection with another, like SQL joins.
jsCopyEdit{
$lookup: {
from: "subscriptions",
localField: "_id",
foreignField: "channel",
as: "subscribers"
}
}
from
: the other collection (subscriptions)localField
: the field from the current collection (_id
)foreignField
: the matching field in the other collectionas
: name for the resulting joined array
➕ $addFields
in MongoDB
Used to add new fields that didn’t exist before. Great for computed values.
jsCopyEdit{
$addFields: {
subscribersCount: { $size: "$subscribers" },
isSubscribed: {
$cond: {
if: { $in: [req.user?._id, "$subscribers.subscriber"] },
then: true,
else: false
}
}
}
}
🔢 $size
in MongoDB
Returns the number of items in an array. Here, it's used to count followers.
jsCopyEditsubscribersCount: { $size: "$subscribers" }
🔎 $in
in MongoDB
Checks if a specific value exists inside an array.
jsCopyEdit{ $in: [req.user?._id, "$subscribers.subscriber"] }
Used to check if the logged-in user is already a subscriber.
🔀 $cond
in MongoDB
This is the if-else logic operator. It works like a ternary:
jsCopyEdit$cond: {
if: <condition>,
then: <value if true>,
else: <value if false>
}
Example: Set isSubscribed
to true or false based on user status.
🎯 $project
in MongoDB
Specifies which fields to include in the final output:
jsCopyEdit{
$project: {
fullName: 1,
userName: 1,
avatar: 1,
subscribersCount: 1,
isSubscribed: 1
}
}
1
means include this field, 0
means exclude.
🌐 HTTP Status Codes
APIs return status codes to tell the client what happened:
Code | Meaning |
200 | OK – everything worked |
400 | Bad Request – something is missing or wrong |
404 | Not Found – user/channel doesn’t exist |
📦 API Response Format
Instead of raw JSON, we use a consistent format using a custom class like ApiResponse
.
jsCopyEditreturn res.status(200).json(
new ApiResponse(200, channel[0], "User channel fetched successfully")
);
It includes:
Status code
Data
Message
This keeps frontend integration clean and predictable.
❌ Custom Error Handling (ApiError)
To handle errors in a clean way, we use a custom class:
jsCopyEditthrow new ApiError(404, "Channel does not exist");
This makes error messages more descriptive and easier to manage across the app.
🛡 Optional Chaining in JavaScript (?.
)
This feature helps avoid crashes when accessing nested properties that might be undefined.
jsCopyEditreq.user?._id
Without optional chaining, this would throw an error if req.user
is undefined.
✅ Conclusion
In just one backend API function, we covered a wide range of essential concepts:
Express route handling
MongoDB aggregation and operators
Error handling and response formatting
Optional chaining for safer code
Understanding these topics will help you write more reliable, efficient, and clean backend APIs. Keep practicing, and you’ll master these patterns in no time!
Subscribe to my newsletter
Read articles from Jayesh Verma directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
