Facebook Pixel

How to Build a Feed API in Node.js

A feed API returns a paginated list of items. Here is how to build one in Node.js.

How to Build a Feed API in Node.js

A feed API returns a paginated list of items (users, posts, products). Here is how to build one in Node.js.

The Endpoint

GET /api/v1/feed?skip=0&limit=20

Returns a paginated list of items for the authenticated user.

The Controller

exports.getFeed = asyncHandler(async (req, res) => {
  const skip = parseInt(req.query.skip) || 0;
  const limit = Math.min(parseInt(req.query.limit) || 20, 100);

  const filter = { _id: { $ne: req.user.userId } };
  if (req.query.gender) filter.gender = req.query.gender;
  if (req.query.minAge || req.query.maxAge) {
    filter.age = {};
    if (req.query.minAge) filter.age.$gte = parseInt(req.query.minAge);
    if (req.query.maxAge) filter.age.$lte = parseInt(req.query.maxAge);
  }

  const [items, total] = await Promise.all([
    User.find(filter).select('firstName photoUrl about age gender').skip(skip).limit(limit).lean(),
    User.countDocuments(filter)
  ]);

  res.json({ data: items, total, skip, limit });
});

Exclude the Current User

The feed should not show the user themselves. Filter with _id: { $ne: req.user.userId }.

Exclude Already-Swiped Users

For a dating app, exclude users the current user has already swiped on:

const swipedIds = await ConnectionRequest.find({ fromUserId: req.user.userId }).distinct('toUserId');
const filter = { _id: { $nin: [req.user.userId, ...swipedIds] } };

Cap the Limit

Math.min(parseInt(req.query.limit) || 20, 100). Prevents abuse. Without a cap, a client can request 10000 items.

Use .lean() and .select()

.lean() skips hydrating full documents. .select() limits fields. Together they reduce memory and payload.

Return Total

{ data: items, total, skip, limit }. The client can show "showing 1-20 of 432" and decide whether to show a next button.

Run Queries in Parallel

Promise.all([find, countDocuments]). Both queries run at once. Faster than awaiting them sequentially.

Index the Filter Fields

Index the fields you filter by. For a feed, index gender, age, and any other filter. Use compound indexes if you filter on multiple fields together.

The Takeaway

Build a feed API with GET /feed?skip=&limit=. Exclude the current user and already-swiped users. Cap the limit. Use .lean() and .select(). Return total. Run find and countDocuments in parallel with Promise.all. Index the filter fields.

GET /feed?skip=&limit=. Exclude the current user and already-swiped users. Cap the limit. Use .lean() and .select(). Return total. Run find and countDocuments in parallel with Promise.all. Index the filter fields.

Fetch the swiped user IDs with ConnectionRequest.find({ fromUserId }).distinct('toUserId'). Then filter the feed with _id: { $nin: [req.user.userId, ...swipedIds] }. Excludes the current user and anyone already swiped on.

To prevent abuse. Without a cap, a client can request 10000 items, eating memory and bandwidth. Use Math.min(parseInt(req.query.limit) || 20, 100). Most UIs only show 20 at a time anyway.

So the client can show 'showing 1-20 of 432' and decide whether to show a next button. Use Promise.all([find, countDocuments]) to run both queries in parallel and avoid waterfall delays.

.lean() skips hydrating full Mongoose documents (faster, less memory). .select() limits fields to what the client needs (smaller payload). Together they make the feed API faster and lighter.

Ready to master Node.js completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.