Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.cocobase.cc/llms.txt

Use this file to discover all available pages before exploring further.

Social Media App Example

Build a complete social media application with posts, likes, comments, and followers.

Features

  • User profiles
  • Create and share posts
  • Like and comment on posts
  • Follow other users
  • News feed
  • Real-time updates

Data Models

interface User {
  email: string;
  username: string;
  displayName: string;
  bio?: string;
  avatar?: string;
  followersCount: number;
  followingCount: number;
}

interface Post {
  authorId: string;
  authorName: string;
  authorAvatar?: string;
  content: string;
  images?: string[];
  likesCount: number;
  commentsCount: number;
  createdAt: string;
}

interface Like {
  postId: string;
  userId: string;
  createdAt: string;
}

interface Comment {
  postId: string;
  authorId: string;
  authorName: string;
  content: string;
  createdAt: string;
}

interface Follow {
  followerId: string;
  followingId: string;
  createdAt: string;
}

Implementation

import { Cocobase } from "cocobase";

const db = new Cocobase({
  apiKey: process.env.COCOBASE_API_KEY,
});

// Profile
async function getProfile(userId: string) {
  return await db.getDocument("users", userId);
}

async function updateProfile(data: Partial<User>) {
  const user = await db.auth.getUser();
  return await db.updateDocument("users", user.id, data);
}

// Posts
async function createPost(content: string, images?: string[]) {
  const user = await db.auth.getUser();
  const profile = await getProfile(user.id);

  return await db.createDocument("posts", {
    authorId: user.id,
    authorName: profile.data.displayName,
    authorAvatar: profile.data.avatar,
    content,
    images,
    likesCount: 0,
    commentsCount: 0,
    createdAt: new Date().toISOString(),
  });
}

async function getFeed() {
  const user = await db.auth.getUser();

  // Get list of users we follow
  const following = await db.listDocuments("follows", {
    filters: { followerId: user.id },
  });
  const followingIds = following.map((f) => f.data.followingId);

  // Include own posts
  followingIds.push(user.id);

  // Get posts from followed users
  return await db.listDocuments("posts", {
    filters: {
      authorId__in: followingIds.join(","),
      orderBy: "createdAt",
      order: "desc",
      limit: 20
    },
  });
}

async function getUserPosts(userId: string) {
  return await db.listDocuments("posts", {
    filters: {
      authorId: userId,
      orderBy: "createdAt",
      order: "desc",
      limit: 20,
    },
  });
}

// Likes
async function likePost(postId: string) {
  const user = await db.auth.getUser();

  // Check if already liked
  const existing = await db.listDocuments("likes", {
    filters: { postId, userId: user.id },
  });

  if (existing.length > 0) {
    return; // Already liked
  }

  // Create like
  await db.createDocument("likes", {
    postId,
    userId: user.id,
    createdAt: new Date().toISOString(),
  });

  // Increment like count
  const post = await db.getDocument("posts", postId);
  await db.updateDocument("posts", postId, {
    likesCount: post.data.likesCount + 1,
  });
}

async function unlikePost(postId: string) {
  const user = await db.auth.getUser();

  const existing = await db.listDocuments("likes", {
    filters: { postId, userId: user.id },
  });

  if (existing.length === 0) {
    return; // Not liked
  }

  // Delete like
  await db.deleteDocument("likes", existing[0].id);

  // Decrement like count
  const post = await db.getDocument("posts", postId);
  await db.updateDocument("posts", postId, {
    likesCount: Math.max(0, post.data.likesCount - 1),
  });
}

// Comments
async function addComment(postId: string, content: string) {
  const user = await db.auth.getUser();
  const profile = await getProfile(user.id);

  await db.createDocument("comments", {
    postId,
    authorId: user.id,
    authorName: profile.data.displayName,
    content,
    createdAt: new Date().toISOString(),
  });

  // Increment comment count
  const post = await db.getDocument("posts", postId);
  await db.updateDocument("posts", postId, {
    commentsCount: post.data.commentsCount + 1,
  });
}

async function getComments(postId: string) {
  return await db.listDocuments("comments", {
    filters: {
      postId,
      orderBy: "createdAt",
      order: "asc",
    },
  });
}

// Follow
async function followUser(userId: string) {
  const currentUser = await db.auth.getUser();

  if (userId === currentUser.id) {
    throw new Error("Cannot follow yourself");
  }

  const existing = await db.listDocuments("follows", {
    filters: {
      followerId: currentUser.id,
      followingId: userId,
    },
  });

  if (existing.length > 0) {
    return; // Already following
  }

  await db.createDocument("follows", {
    followerId: currentUser.id,
    followingId: userId,
    createdAt: new Date().toISOString(),
  });

  // Update counts
  const currentProfile = await getProfile(currentUser.id);
  const targetProfile = await getProfile(userId);

  await db.updateDocument("users", currentUser.id, {
    followingCount: currentProfile.data.followingCount + 1,
  });
  await db.updateDocument("users", userId, {
    followersCount: targetProfile.data.followersCount + 1,
  });
}

async function unfollowUser(userId: string) {
  const currentUser = await db.auth.getUser();

  const existing = await db.listDocuments("follows", {
    filters: {
      followerId: currentUser.id,
      followingId: userId,
    },
  });

  if (existing.length === 0) {
    return; // Not following
  }

  await db.deleteDocument("follows", existing[0].id);

  // Update counts
  const currentProfile = await getProfile(currentUser.id);
  const targetProfile = await getProfile(userId);

  await db.updateDocument("users", currentUser.id, {
    followingCount: Math.max(0, currentProfile.data.followingCount - 1),
  });
  await db.updateDocument("users", userId, {
    followersCount: Math.max(0, targetProfile.data.followersCount - 1),
  });
}

// Real-time feed updates
function watchFeed(callback: (event: any) => void) {
  return db.realtime.collection("posts", callback);
}

Key Concepts Demonstrated

  1. Denormalization - Author info stored in posts for fast reads
  2. Count Fields - Pre-computed like/comment/follower counts
  3. Many-to-Many - Follow relationships between users
  4. Feed Generation - Query posts from followed users
  5. Real-time Updates - Live feed with new posts

Next Steps

Chat App

Build real-time chat

Todo App

Build a todo app