When building a nested reply system, most developers worry about deep tree complexity or messy data structures. For Vlox, I took a different approach: keeping things flat, fast, and secure by reusing a single Mongoose schema with smart atomic limits.
Here is a deep dive into how I engineered a production-ready, race-condition-safe reply mechanism using MongoDB transactions, strict type sanitization, and automated limits.
How It Works 🛠️
- User Action: A user clicks the reply icon and submits their reply.
-
The Payload: Vlox's system sends 3 fields via the endpoint
/api/v1/reply/comment/post/:id:-
id: The post ID (passed as a URL parameter). -
rootCommentId: The ID of the root comment being replied to. -
reply: The raw text entered by the user.
-
-
Sanitization & Validation: The incoming reply is instantly converted to a trimmed string. It then passes through two critical validation checks:
-
Existence Check: The reply must exist. (If a malicious actor sends a payload without a body, the string literally evaluates to
"undefined"and gets blocked). - Length Limit: The reply must be under 201 characters, enforcing the standard comment limit.
-
Existence Check: The reply must exist. (If a malicious actor sends a payload without a body, the string literally evaluates to
-
Atomic Transactions: If the validation checks pass, the system initiates a Mongoose transaction to execute the following steps safely:
-
Permission Check: It verifies if the user has permission to reply by checking the post's status via
await schemas.Posts.findOne(hotQueries.find_user_post(id, req.session.userId));. - Creation: If permissions are valid, it creates a new reply. (Fun fact: It reuses the exact same schema as standard comments!)
-
Permission Check: It verifies if the user has permission to reply by checking the post's status via
-
The Reply Schema Structure: The reply object functions just like a normal comment, with two distinct exceptions:
- It does not contain a
repliesCountfield. - It includes an extra
rootIdfield, which explicitly points to the ID of the root comment being replied to.
- It does not contain a
- Concurrency & Caps: To guarantee that a single comment never receives more than 10 replies while simultaneously incrementing the counter, the system runs this precise atomic query:
{
_id: rootCommentId,
repliesCount: { $lt: 10 },
rootId: null
}
-
Error Handling: To catch race conditions or instances where a user attempts to reply to a thread that is already full, the system evaluates the write operation with
if (result.matchedCount === 0) throw new Error("COMMENT_UPDATE_FAILED");. Inside the catch block, it intercept this withif (txError.message === "COMMENT_UPDATE_FAILED") return res.status(400).json({ error: "You can't reply to this comment!" });.
The Production Code 💻
// In actions.js
router.post("/api/v1/reply/comment/post/:id", checkAuth, checkValidID, async (req, res) => {
try {
const id = req.params.id;
let { reply, rootCommentId } = req.body;
reply = String(reply).trim(); // You can change this to an explicit string check too
if (!reply) return res.status(400).json({ error: "Reply can't be empty!" });
if (reply.length > 200) return res.status(400).json({ error: "Reply cannot exceed 200 chars!" });
const session = await mongoose.startSession();
try {
await session.withTransaction(async () => {
// Find post
const post = await schemas.Posts.findOne(hotQueries.find_user_post(id, req.session.userId));
if (!post) throw new Error("POST_NOT_FOUND");
// Add reply
const newReply = new schemas.Comments({
content: reply,
rootId: rootCommentId,
for: id,
by: req.session.userId
});
await newReply.save({ session });
// Inc comments
const result = await schemas.Comments.updateOne({
_id: rootCommentId,
repliesCount: { $lt: 10 },
rootId: null
}, {
$inc: {
repliesCount: 1
}
}, { session });
if (result.matchedCount === 0) throw new Error("COMMENT_UPDATE_FAILED");
});
} catch (txError) {
if (txError.message === "POST_NOT_FOUND") return res.status(400).json({ error: "Post not found or you don't have permissions to see it!" });
if (txError.message === "COMMENT_UPDATE_FAILED") return res.status(400).json({ error: "You can't reply to this comment!" });
console.log("Error: " + txError.message);
return res.status(400).json({ error: "Failed to reply. Try again." });
} finally {
session.endSession();
}
return res.status(200).json({ success: true });
} catch (e) {
console.log("Error: " + e.message);
createErrorMessage(e, req.session.userId, req.originalUrl);
return res.status(400).json({ error: "Server error" });
}
});
// In server.js
app.post("/api/v1/get/post/replies/:id", checkAuth, checkValidID, async (req, res) => {
try {
const id = req.params.id;
const { rootCommentId } = req.body;
// Do you have permissions to access this post?
const post = await schemas.Posts.find(hotQueries.find_user_post(id, req.session.userId));
if (!post) return res.status(400).json({ error: "Post not found or you don't have permissions to see it!" });
// Find replies (Note: You might want to add limit(10) here as a best practice, though it is optional)
const replies = await schemas.Comments.find({
for: id,
rootId: rootCommentId
})
.populate("by", "-password -recoveryCodes -pinnedPosts -email -pinnedPostsCount"); // Removes secrets and non-essential data
return res.status(200).json({ success: true, replies: replies });
} catch (e) {
console.error("Fetch Replies Break: ", e.message);
return res.status(500).json({ error: "Could not retrieve replies." });
}
});
Links:
- Vlox Source Code: https://github.com/Hfs2024/Vlox
- Try Vlox: https://vlox.containers.snapdeploy.app/ or https://vlox.bonto.run/
Found this guide helpful? Drop a like! 🌟
Top comments (0)