Instagram-Style Backend API
Social Media Backend API
No frontend ships with this project on purpose. It's a social media backend API, the part that stores posts, tracks who follows whom, and works out what actually lands in someone's feed. Built on NestJS, Prisma, and PostgreSQL, it exposes everything through GraphQL rather than REST. And the feed itself doesn't just show the newest post first. It ranks content with a hotScore that weighs popularity against age.
You can check the live demo or look through the source code directly. This sits alongside other backend work in the project portfolio, and it's a solid example of what a NestJS GraphQL API looks like once real social features get layered on top.

Project Overview
This social media API includes the pieces you'd expect from an Instagram-style backend: posts, likes, follows, notifications, and authentication. Everything runs through a single GraphQL endpoint, so a client asks for exactly the fields it needs instead of getting stuck with whatever shape a REST endpoint happens to return.
The feed is where most of the actual thought went. Sorting purely by newest post is the obvious approach, and it's also the weakest one, since a post from sixty seconds ago would outrank something genuinely popular from earlier that day. hotScore blends engagement with how recently something was posted instead, so posts people actually reacted to stay visible for a while before naturally fading as newer content takes over.
Under the hood, this is a modular monolith: separate modules for separate features, shipped as one deployable app rather than a scattering of microservices.
My Role
- Designed the GraphQL schema: types, queries, and mutations
- Modeled the database in Prisma and PostgreSQL, including the follow relationships
- Built the feature modules for posts, likes, follows, and notifications
- Implemented JWT authentication and locked down the resolvers that need it
- Wrote the hotScore ranking that orders the feed
- Deployed the API with a public GraphQL endpoint
The Problem
A follow is just a link from one user to another, sitting inside the same table. Simple enough on paper. But a feed then has to gather posts from everyone a given user follows and put them into some sensible order, and likes and notifications stack even more relationships on top of that. So the actual engineering challenge in this social network backend wasn't any single feature. It was modeling those relationships correctly in PostgreSQL, keeping the feed query fast even as data grew, and keeping the codebase from turning into a tangle as features kept getting added.
How I Built It
NestJS made sense here because its module system tends to match how a social product actually grows over time. Posts, likes, follows, notifications, and auth each get their own module, complete with their own resolvers and services, so a change to how notifications work never accidentally touches post logic.
Prisma sits between those modules and PostgreSQL. Its schema defines users, posts, likes, follows, and notifications along with every relation connecting them, and it generates a fully typed client from that definition. Ask for a field that doesn't exist, and the build fails immediately rather than surfacing as a bug later in production.
GraphQL is the only way in. Each module contributes its own resolvers, and NestJS merges them into a single schema. Authentication runs on JWT: signing in hands back a token, and any resolver touching private data checks that token before doing anything else.
The feed query itself pulls posts from everyone a user follows, then sorts by hotScore rather than creation date. For more on how I approach this kind of relational, API-heavy backend work, take a look at my backend development skills and NestJS and Node.js skills.
Key Features
Posts and likes
Users post and like content, and like counts feed straight into ranking. A like isn't just a number sitting on a post here; it actually shapes what other people see.
Follow relationships
Accounts follow each other, and those links decide whose posts show up in a feed. In the database, this is a self-referencing relationship from the users table back to itself, which was one of the trickier parts to get right.
Ranked feed with hotScore
Engagement and recency get weighed together, so a popular post stays visible for longer while a brand-new post with zero likes doesn't immediately vanish under everything else.
Notifications
A like or a new follower writes one notification record for the affected user. The client reads that single list to show recent activity, no polling across a dozen other tables required.
JWT authentication
Signing in returns a token, and it travels with every subsequent request. Resolvers touching private data check it first.
GraphQL schema
One query pulls posts, their authors, and their like counts together. A REST API would typically need several round trips to assemble the same response.
Technical Architecture
- GraphQL client
- NestJS GraphQL layer
- Feature modules for posts, likes, follows, notifications, and auth
- Prisma
- PostgreSQL
A GraphQL query arrives at the NestJS app, gets routed to the resolver inside the right feature module, and that resolver calls its service for the real logic. Services reach PostgreSQL through Prisma. It all deploys as a single app. The boundaries between features are real and enforced, but they live inside the code rather than across separate servers, so there's no added infrastructure cost from running several services at once.
Engineering Decisions
Modular monolith instead of microservices
- Why
- Going with a modular monolith instead of microservices came down to how tightly these features actually depend on each other. A single like touches posts, users, and notifications all at once, and splitting those into separate services would mean adding network calls between things that genuinely belong together. NestJS modules give clear internal boundaries without that overhead,
- Trade-off
- though the cost is that everything deploys together; even a small change means redeploying the whole API. At this size, that's a far smaller price than coordinating several separate services would be.
GraphQL over REST
- Why
- GraphQL won out over REST largely because social data is so interconnected. A feed needs posts, authors, and like counts all at once, and REST usually forces a choice between several thin endpoints or one bloated one returning more than a client asked for. GraphQL lets the client describe exactly what it wants.
- Trade-off
- The trade-off shows up in query cost, which becomes harder to predict, and in nested queries, which can trigger repeated database lookups if resolvers aren't careful about how they fetch related data.
hotScore over reverse-chronological feed
- Why
- Choosing hotScore over a plain reverse-chronological feed came from a simple observation: a timeline is the easiest feed to build and the least interesting one to actually scroll through. Blending engagement with recency keeps good content visible longer while still giving new posts a fair shot at surfacing.
- Trade-off
- It does make the feed harder to reason about, though. When a post lands somewhere unexpected, you have to actually calculate its score rather than glance at a timestamp.
Prisma as the database layer
- Why
- Prisma earned its spot as the database layer because the schema acts as a single source of truth, and the generated client is fully typed. With relationships as tangled as follows and likes, catching a typo'd field name at compile time saves real debugging time later.
- Trade-off
- The downside is that genuinely complex queries occasionally still need raw SQL, which means giving up some fine control over exactly what runs against the database.
Challenges and Trade-offs
- Modeling the follow relationship as a self-reference back to the users table took real care early on, since the entire feed query depends on getting that right. A structural fix later would have rippled through nearly everything downstream.
- Nested GraphQL queries can fire off repeated database calls for related records, and that problem only becomes visible once the dataset actually grows. The fix belongs in the query layer itself, not scattered across individual resolvers trying to patch around it.
- The hotScore formula, to be honest, is a judgment call more than a settled fact. It behaves reasonably in testing, but any ranking system like this needs ongoing tuning once real content starts moving through it and real usage patterns emerge.
Technology Stack
API
- NestJS
- GraphQL
- TypeScript
Data
- Prisma
- PostgreSQL
Auth
- JWT
Deployment
- Render
Outcome
- A working GraphQL API covering posts, likes, follows, notifications, and authentication.
- A feed ordered by hotScore instead of a plain chronological list.
- A relational PostgreSQL schema that actually handles follow relationships and engagement data cleanly.
- And a public GraphQL endpoint where you can explore the schema yourself.
What I Learned
- Database modeling has to come before API design, not run alongside it as an afterthought. The follow relationship shaped nearly every query I wrote after it, which is worth knowing before you start planning a social media database from scratch.
- Setting up module boundaries early paid off repeatedly. Once posts, likes, and notifications each had their own space, new features stopped bleeding into code they had no business touching.
- GraphQL trades over-fetching for a query-cost problem. That's not a one-time fix; it needs continuous attention as the schema and usage patterns evolve.
Frequently Asked Questions
What's this social media backend API actually built with?
NestJS, GraphQL, Prisma, and PostgreSQL, with JWT handling authentication. It exposes a single GraphQL endpoint covering posts, likes, follows, and notifications, rather than separate REST routes for each.
How does the ranking algorithm decide what shows up in the feed?
A hotScore blends engagement with how recently something was posted, so a popular post stays visible longer while brand-new content still gets a real chance to surface, instead of everything sorting purely by timestamp.
Why GraphQL instead of REST for a social platform?
GraphQL lets a client ask for exactly the fields it needs in one request. For a feed, that means posts, authors, and like counts can arrive together in a single query, where REST would typically need several separate calls to build the same response.
Is this built as microservices or a monolith?
It's a modular monolith. Posts, likes, follows, notifications, and auth each live in their own NestJS module, but the whole thing ships and deploys as one application rather than as separate independently-deployed services.
API work like this, spanning NestJS, GraphQL, Prisma, and PostgreSQL, makes up a large share of what I build. Look through my backend development skills.
There's more backend and full-stack work in the wider portfolio. See every project I've shipped.
Or check the backend API development services.