Learn GraphQL - Project: Social Network with Real-Time Features
Episode 42 of 51

Learn GraphQL - Project: Social Network with Real-Time Features

Episode 42 builds a social media platform: user profiles and relationships, post creation with media upload, comments and reactions, following- followers, a paginated timeline feed, real-time notifications, chat, hashtags, and feed performance strategies at scale.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Second project in the real-world phase: a social media platform with complete, real-time features. This is one of the most challenging app types because of the combination of complex social data, media uploads, large-scale feeds, and instant notifications.

Episode 42 builds it layer by layer: core social features, real-time features, advanced functionality, and feed performance strategies.

Core Social Features

User Profiles and Relationships

Social relation models in Prisma:

JSSocial graph model
model User {
  id          Int    @id @default(autoincrement())
  username    String @unique
  displayName String
  avatarUrl   String?
  followers   Follow[]
  following   Follow[]
}
 
model Follow {
  followerId Int
  followingId Int
  createdAt  DateTime @default(now())
  @@id([followerId, followingId])
}

Relationships follow the pattern: Follow as a join table with a composite primary key. The followers and following queries use pagination (episode 12) because they can be large.

Post Creation and Media Upload

Posts with media use file upload (episode 17):

Post mutation with media
type Mutation {
  createPost(input: CreatePostInput!, media: [Upload!]): Post!
}
 
input CreatePostInput {
  body: String!
  hashtags: [String!]
}

Media is uploaded to object storage (S3), and its URL is stored on the post. Type and file-size validation are applied before upload — following the security checklist from episode 17.

Comments and Reactions

Comments are a tree (a comment can reply to another comment), and reactions use an enum:

Comment and reaction schema
type Comment {
  id: ID!
  body: String!
  parent: Comment
  replies: CommentConnection!
}
 
enum ReactionType {
  LIKE
  LOVE
  LAUGH
  SAD
  ANGRY
}
 
type Reaction {
  type: ReactionType!
  count: Int!
}

Nested comments are depth-limited for performance; reactions are aggregated with group-by queries.

Real-Time Features

Live Notifications and Online Presence

Live notifications use a per-user subscription:

Notification subscription
type Subscription {
  notificationReceived: Notification!
}
JSPublish a notification to a specific user
async function notifyUser(userId, notification) {
  await pubsub.publish(`NOTIFICATION:${userId}`, { notificationReceived: notification });
}

Online presence uses the presence pattern from episode 38: online status is published and listened to by other relevant users.

Chat and Live Comment Updates

Real-time chat uses a per-room topic (episode 16). Live comment updates can use a per-post subscription — every new comment is published to that post's topic. For high traffic, limit frequency or use short aggregation (for example updating every few seconds).

Advanced Functionality

Search, Hashtags, and Moderation

  • User and content search: use database full-text search or a service like Meilisearch.
  • Hashtag system: extract hashtags from post text at creation, store them in a table.
  • Content moderation: automatic filters (word lists, image detection) before publishing, with flags for manual review.
  • Privacy controls: isPrivate on users — posts only visible to approved followers.
  • Blocking and reporting: blocking filters a user's content from the feed; reporting sends an entry for moderation.
Hashtag query
type Query {
  hashtag(tag: String!, first: Int!, after: String): PostConnection!
}

Privacy Filtering

Privacy is enforced with data filtering (episode 14): the feed resolver filters posts by isPrivate and follow relationships — a user must not see private content from someone they don't follow.

Performance at Scale

Feed Generation Strategies

The feed is a classic social media problem. Common strategies:

  • Fan-out on write: when a post is created, copy its ID to every follower's timeline — fast reads, heavy writes.
  • Fan-out on read: the feed is computed when read — light writes, heavy reads.
  • Hybrid: fan-out for active followers, on-read for the rest.
JSFeed via a Redis list
const followers = await ctx.db.follows.findMany({ where: { followingId: userId } });
for (const f of followers) {
  await ctx.redis.lpush(`feed:${f.followerId}`, postId);
}

Choose the strategy based on scale; start with the simple fan-out on read, then evolve.

Caching and Database Optimization

  • Cache popular content in Redis with TTL — for example LPUSH feed:123 postId in the feed pattern.
  • Database optimization: indexes on authorId, createdAt, and the join table.
  • Image CDN: serve media through a CDN with sizes matched to the device.

A cursor-paginated feed (episode 12) combined with caching makes scrolling feel smooth.

Conclusion

Key takeaways:

  • Follows and nested comments are modeled with join tables and trees.
  • Media upload uses S3 with strict validation.
  • Notifications and chat use per-user and per-room subscriptions.
  • Privacy controls and blocking are enforced through data filtering.
  • The feed uses fan-out on write, on read, or hybrid depending on scale.
  • Indexes, caching, and CDNs keep feed performance good.

In the next episode, episode 43, you'll learn about a multi-tenant SaaS project — multi-tenancy architecture with per-tenant vs shared databases, tenant context propagation, organization management with invitations and roles, billing with Stripe subscriptions, and enterprise features like SSO and audit logs. Your enterprise SaaS app will be ready!

Learn GraphQL - Project: Social Network with Real-Time Features | Learn GraphQL