`mern-social`: The MERN Social Graph, Explained in Plain Code

A book companion project that doubles as a readable blueprint for follower relationships, feed generation, SSR, and the trade-offs behind a deliberately simple social app.

10 min read View on GitHub More from shamahoque

A wide editorial scene of profile cards pinned to a board, linked by taut strings that form a social graph. A feed column grows out of those links, showing how relationships become content delivery.
The repo’s big idea is not the interface. It is the graph beneath it, where follows, followers, and feeds become easy to trace in code and in motion.
Key Takeaways

The quickest way to understand mern-social is to stop thinking of it as a demo app. It is a lesson plan with a social feed attached.

That matters because the repo does something rare. It shows how a modern social app can be built from a small set of readable primitives: follower arrays, a feed query, server rendering, and auth that does not hide its seams.

The app is really a lesson in social graphs

The best entry point is the follow system. It is the clearest expression of the repo’s design philosophy: keep the model visible, keep the data shape simple, and let the code teach the relationship between users instead of abstracting it away.

The feed is not magic. It is a graph, then a query, then a rendered list.

Follow someone, update two sides, keep the model simple

The follow action uses a pragmatic duplication pattern. When one user follows another, the app updates both the current user’s following array and the target user’s followers array. That makes writes slightly more expensive, but it keeps reads easy and the data model legible.

// server/controllers/user.controller.js
// follow relationship is written to both users

exports.addFollowing = (req, res, next) => {
  User.findByIdAndUpdate(
    req.body.userId,
    { $push: { following: req.body.followId } },
    { new: true }
  )
  .then(result => {
    req.profile.password = undefined;
    req.profile.salt = undefined;
    res.json(result);
  })
  .catch(err => next(err));
};

exports.addFollower = (req, res, next) => {
  User.findByIdAndUpdate(
    req.body.followId,
    { $push: { followers: req.body.userId } },
    { new: true }
  )
  .then(result => {
    result.password = undefined;
    result.salt = undefined;
    res.json(result);
  })
  .catch(err => next(err));
};

That pattern is not clever. It is readable. And for a teaching repo, readable is the point.

A close-up ledger scene shows one pen updating a following list and another pen updating a followers list at the same time. Nearby, a small stack of post cards is assembled from matched authors, showing how the same relationship data powers the feed.
Two writes, one graph. The duplicated relationship is what makes the query downstream so direct.

The feed is a relational query wearing NoSQL clothes

Once the graph is in place, the feed logic reads like something from a database textbook. Find the posts written by people you follow. Sort them by creation date. Populate the author and comment data so the result is ready for display.

ProblemWhat the repo doesWhy it helps
Feed generationQuery posts where `postedBy` is in the following listMakes the relationship logic explicit
SortingOrder by `created`Keeps the newest content first
Hydration of dataPopulate author and commentsReturns display-ready results without extra client hops

SSR is the project’s quiet power move

The server-rendered entry path is where the repo starts to feel more production-shaped than most tutorials. renderToString, Material-UI’s ServerStyleSheets, and client-side hydrate() make the first paint clean and the transition to the browser smooth.

This project is the complete code for the application built in the book 'MERN Quick Start Guide'.

Shama Hoque, Creator, Author · shamahoque/mern-social README

That quote frames the repo correctly. It is not trying to be a platform. It is trying to be a complete, legible implementation that you can study end to end.

Authentication is intentionally redundant

The auth story is deliberately a little messy in a useful way. The server sets a JWT cookie, while the client can also store and attach the token from session storage. That redundancy makes the system flexible, even if it is not the sleekest possible design.

// server/controllers/auth.controller.js
res.cookie('t', token, { expire: new Date() + 9999 });

// client/helpers/auth-helper.js
if (typeof window !== 'undefined') {
  sessionStorage.setItem('jwt', data.token);
}

// client/api/api-user.js
headers['Authorization'] = `Bearer ${jwt}`;
ApproachStrengthCost
Cookie-based tokenWorks naturally for browser sessionsAdds server and browser coordination
Session storage plus headerSimple for API calls and client helpersDuplicates token handling logic
Purely server-managed authCleaner authority boundaryLess flexible for mixed client flows

The repo teaches by staying legible

The broader architecture follows a controller-route-model shape that experienced developers will recognize immediately. That is a feature, not a limitation. It keeps each step visible enough that a reader can trace a request without decoding framework magic.

PatternEffect on the readerTrade-off
Controller-route-modelEasy to trace behaviorLess abstraction, more repetition
Explicit error handlingFailures are visibleMore boilerplate
Familiar librariesFaster mental mappingLess novelty

That readability is the teaching strategy. The repo wants you to learn the shape of a real app, not memorize a clever pattern.

Where the design choices get opinionated

Some choices are clearly optimized for simplicity over scale. Storing images as Buffers in MongoDB keeps the stack self-contained, but it also pushes media and database concerns into the same place. Material-UI helps the UI stay coherent, while the older React and Webpack stack reflects the project’s era more than current fashion.

ChoiceWhy it works hereWhat it costs
Image Buffers in MongoDBFewer external dependenciesDatabase bloat and scaling friction
Material-UI heavy stylingConsistent look and fast UI assemblyMore framework coupling
Older React and Webpack stackMatches the book and keeps the repo stableNot the modern default for new projects

Those trade-offs are not mistakes in the context of the repo. They are the price of a codebase that is trying to teach, not dazzle.

Why this still matters among newer tutorials and boilerplates

Compared with newer tutorial repos and hosted social app platforms, `mern-social` is less about speed to launch and more about comprehension. It gives you a complete system you can read, not just a service you can click together.

OptionWhat you getWhat you lose
`mern-social`A full MERN app with visible plumbingModern polish and current-stack defaults
Generic boilerplateFast starting pointLess narrative structure and less guidance
Hosted social platformInstant infrastructureControl over the architecture and learning value

That is why the project still earns attention. It teaches the architecture of a social app by refusing to hide the architecture.