Likes
The Likes API allows users to like and unlike blog posts, providing engagement metrics for content. Each user can like a blog post only once, and the system tracks the total number of likes for each post.
Endpoints
Like Blog Post
Adds a like to a specific blog post and increments the blog's like count.
Endpoint: POST /likes/blog/{blogId}
Authentication: Required (Bearer token)
Path Parameters:
blogId
: MongoDB ObjectId of the blog post to like
Success Response (200 OK):
{
"likesCount": 156
}
Error Responses:
400
- User has already liked this blog post401
- Unauthorized404
- Blog post not found500
- Internal server error
Example Error Response (400 Bad Request):
{
"code": "BadRequest",
"message": "You already liked this blog"
}
Unlike Blog Post
Removes a like from a specific blog post and decrements the blog's like count.
Endpoint: DELETE /likes/blog/{blogId}
Authentication: Required (Bearer token)
Path Parameters:
blogId
: MongoDB ObjectId of the blog post to unlike
Success Response (204 No Content): Empty response body
Error Responses:
400
- User has not liked this blog post previously401
- Unauthorized404
- Blog post not found500
- Internal server error
Example Error Response (400 Bad Request):
{
"code": "BadRequest",
"message": "Like not found"
}
Like System Behavior
One Like Per User
Each user can like a blog post only once
Attempting to like an already-liked post returns a 400 error
Attempting to unlike a post that wasn't liked returns a 400 error
Like Count Tracking
Blog posts maintain a
likesCount
fieldCount is automatically incremented/decremented with likes/unlikes
The like count is returned when liking a post
Count is visible in blog post responses
Like Persistence
Likes are permanently stored in the database
The system tracks which user liked which post
Like history is maintained for analytics and user experience
Code Examples
Like a Blog Post
const likeBlogPost = async (accessToken, blogId) => {
const response = await fetch(`/api/v1/likes/blog/${blogId}`, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (response.ok) {
const data = await response.json();
return data.likesCount;
}
if (response.status === 400) {
const error = await response.json();
throw new Error(error.message || "Already liked");
}
throw new Error("Failed to like blog post");
};
Unlike a Blog Post
const unlikeBlogPost = async (accessToken, blogId) => {
const response = await fetch(`/api/v1/likes/blog/${blogId}`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (response.status === 204) {
return true;
}
if (response.status === 400) {
const error = await response.json();
throw new Error(error.message || "Not previously liked");
}
throw new Error("Failed to unlike blog post");
};
Complete Like Management Class
class LikeAPI {
constructor(baseURL, accessToken) {
this.baseURL = baseURL;
this.accessToken = accessToken;
}
async likeBlog(blogId) {
const response = await fetch(`${this.baseURL}/likes/blog/${blogId}`, {
method: "POST",
headers: this._getHeaders(),
});
if (response.ok) {
const data = await response.json();
return data.likesCount;
}
return this._handleError(response);
}
async unlikeBlog(blogId) {
const response = await fetch(`${this.baseURL}/likes/blog/${blogId}`, {
method: "DELETE",
headers: this._getHeaders(),
});
if (response.status === 204) {
return true;
}
return this._handleError(response);
}
async toggleLike(blogId, currentlyLiked) {
try {
if (currentlyLiked) {
await this.unlikeBlog(blogId);
return { liked: false, likesCount: null };
} else {
const likesCount = await this.likeBlog(blogId);
return { liked: true, likesCount };
}
} catch (error) {
throw new Error(`Failed to toggle like: ${error.message}`);
}
}
_getHeaders() {
return {
Authorization: `Bearer ${this.accessToken}`,
"Content-Type": "application/json",
};
}
async _handleError(response) {
const error = await response
.json()
.catch(() => ({ message: "Unknown error" }));
throw new Error(error.message || `HTTP ${response.status}`);
}
}
React Like Button Component
import React, { useState } from "react";
const LikeButton = ({
blogId,
initialLikesCount,
initiallyLiked,
accessToken,
}) => {
const [likesCount, setLikesCount] = useState(initialLikesCount);
const [isLiked, setIsLiked] = useState(initiallyLiked);
const [loading, setLoading] = useState(false);
const likeAPI = new LikeAPI("/api/v1", accessToken);
const handleToggleLike = async () => {
if (loading) return;
setLoading(true);
try {
const result = await likeAPI.toggleLike(blogId, isLiked);
setIsLiked(result.liked);
if (result.liked) {
setLikesCount(result.likesCount);
} else {
// Decrement count when unliking
setLikesCount((prev) => Math.max(0, prev - 1));
}
} catch (error) {
console.error("Failed to toggle like:", error);
alert(error.message);
}
setLoading(false);
};
return (
<button
onClick={handleToggleLike}
disabled={loading}
className={`like-button ${isLiked ? "liked" : ""}`}
aria-label={isLiked ? "Unlike this post" : "Like this post"}
>
<span className="like-icon">{isLiked ? "❤️" : "🤍"}</span>
<span className="like-count">{likesCount}</span>
{loading && <span className="loading">...</span>}
</button>
);
};
// Usage
const BlogPost = ({ blog, userLikes, accessToken }) => {
const isLiked = userLikes.includes(blog._id);
return (
<div className="blog-post">
<h1>{blog.title}</h1>
<div className="blog-content">{blog.content}</div>
<div className="blog-actions">
<LikeButton
blogId={blog._id}
initialLikesCount={blog.likesCount}
initiallyLiked={isLiked}
accessToken={accessToken}
/>
</div>
</div>
);
};
Bulk Like Status Check
// Helper function to check like status for multiple blogs
const checkLikeStatuses = async (accessToken, blogIds) => {
// Note: This would require a custom endpoint or multiple API calls
// Current API doesn't have bulk like status checking
const likeStatuses = {};
for (const blogId of blogIds) {
try {
// This is a workaround - try to like and immediately unlike
// In a real implementation, you'd want a dedicated endpoint
await likeBlog(accessToken, blogId);
await unlikeBlog(accessToken, blogId);
likeStatuses[blogId] = false; // Not previously liked
} catch (error) {
if (error.message.includes("already liked")) {
likeStatuses[blogId] = true; // Already liked
} else {
likeStatuses[blogId] = false; // Assume not liked on error
}
}
}
return likeStatuses;
};
User Experience Patterns
Optimistic Updates
For better user experience, implement optimistic updates:
const OptimisticLikeButton = ({
blogId,
initialLikesCount,
initiallyLiked,
accessToken,
}) => {
const [likesCount, setLikesCount] = useState(initialLikesCount);
const [isLiked, setIsLiked] = useState(initiallyLiked);
const [loading, setLoading] = useState(false);
const handleToggleLike = async () => {
// Optimistic update
const newLikedState = !isLiked;
const newCount = newLikedState ? likesCount + 1 : likesCount - 1;
setIsLiked(newLikedState);
setLikesCount(newCount);
setLoading(true);
try {
const likeAPI = new LikeAPI("/api/v1", accessToken);
if (newLikedState) {
const actualCount = await likeAPI.likeBlog(blogId);
setLikesCount(actualCount); // Use server count for accuracy
} else {
await likeAPI.unlikeBlog(blogId);
}
} catch (error) {
// Revert optimistic update on error
setIsLiked(isLiked);
setLikesCount(likesCount);
console.error("Failed to toggle like:", error);
}
setLoading(false);
};
return (
<button onClick={handleToggleLike} disabled={loading}>
{isLiked ? "❤️" : "🤍"} {likesCount}
</button>
);
};
Debounced Likes
To prevent rapid clicking issues:
import { useCallback, useRef } from "react";
const useDebouncedLike = (callback, delay = 500) => {
const timeoutRef = useRef();
return useCallback(
(...args) => {
clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => callback(...args), delay);
},
[callback, delay]
);
};
const DebouncedLikeButton = ({ blogId, accessToken }) => {
const [state, setState] = useState({ liked: false, count: 0 });
const handleLike = async () => {
const likeAPI = new LikeAPI("/api/v1", accessToken);
try {
const result = await likeAPI.toggleLike(blogId, state.liked);
setState({
liked: result.liked,
count: result.likesCount || state.count,
});
} catch (error) {
console.error("Like failed:", error);
}
};
const debouncedLike = useDebouncedLike(handleLike);
return (
<button onClick={debouncedLike}>
{state.liked ? "❤️" : "🤍"} {state.count}
</button>
);
};
Analytics and Insights
Like Metrics
The like system provides valuable engagement metrics:
Total Likes: Overall engagement across all blog posts
Like Rate: Percentage of viewers who like a post
Popular Content: Posts with highest like counts
User Engagement: Most active users based on likes given
Potential Analytics Endpoints
While not currently implemented, these could be valuable additions:
// Example additional endpoints that could be implemented
const getTopLikedPosts = async (accessToken, limit = 10) => {
// GET /analytics/posts/top-liked?limit=10
};
const getUserLikeHistory = async (accessToken, userId) => {
// GET /users/{userId}/likes
};
const getBlogLikeHistory = async (accessToken, blogId) => {
// GET /blogs/{blogId}/likes
};
Best Practices
For Users
Meaningful Likes: Like content that genuinely provides value
Authentic Engagement: Avoid like/unlike spamming
Discover Content: Use likes to bookmark interesting posts
For Developers
Error Handling: Always handle like/unlike errors gracefully
User Feedback: Provide clear visual feedback for like actions
Performance: Implement optimistic updates for better UX
Rate Limiting: Consider implementing like rate limiting
Analytics: Track like patterns for content insights
Security Considerations
Authentication: Always verify user authentication
Duplicate Prevention: Enforce one-like-per-user rule
Rate Limiting: Prevent like spam attacks
Input Validation: Validate blogId parameters
Integration with Other Features
Comments Integration
const BlogEngagement = ({ blog, accessToken }) => {
return (
<div className="engagement-section">
<LikeButton
blogId={blog._id}
initialLikesCount={blog.likesCount}
accessToken={accessToken}
/>
<CommentsSection
blogId={blog._id}
commentsCount={blog.commentsCount}
accessToken={accessToken}
/>
</div>
);
};
Social Sharing
const SocialActions = ({ blog, isLiked, accessToken }) => {
const shareText = `Check out this article: ${blog.title}`;
const shareUrl = `https://blog.example.com/posts/${blog.slug}`;
return (
<div className="social-actions">
<LikeButton blogId={blog._id} accessToken={accessToken} />
<button
onClick={() => navigator.share({ text: shareText, url: shareUrl })}
>
Share
</button>
<button onClick={() => copyToClipboard(shareUrl)}>Copy Link</button>
</div>
);
};
The Likes API provides a foundation for user engagement and content popularity metrics. While simple in design, it can be extended with additional features like comment likes, user like histories, and advanced analytics as the platform grows.
Last updated