MCPcopy Index your code
hub / github.com/adrianhajdin/threads

github.com/adrianhajdin/threads @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
90 symbols 297 edges 59 files 0 documented · 0% 7 cross-repo links
What it actually does AI analysis from the code graph — generated when you open this
loading…
README
<a href="https://youtu.be/O5cmLDVTgAs?feature=shared" target="_blank">
  <img src="https://github.com/adrianhajdin/threads/assets/151519281/a9cd1088-968b-4b1d-b21a-f5f97d0c202b" alt="Project Banner">
</a>







<img src="https://img.shields.io/badge/-Next_JS-black?style=for-the-badge&logoColor=white&logo=nextdotjs&color=000000" alt="nextdotjs" />
<img src="https://img.shields.io/badge/-MongoDB-black?style=for-the-badge&logoColor=white&logo=mongodb&color=47A248" alt="mongodb" />
<img src="https://img.shields.io/badge/-Tailwind_CSS-black?style=for-the-badge&logoColor=white&logo=tailwindcss&color=06B6D4" alt="tailwindcss" />
<img src="https://img.shields.io/badge/-Clerk-black?style=for-the-badge&logoColor=white&logo=clerk&color=6C47FF" alt="clerk" />
<img src="https://img.shields.io/badge/-Shadcn_UI-black?style=for-the-badge&logoColor=white&logo=shadcnui&color=000000" alt="shadcnui" />
<img src="https://img.shields.io/badge/-Zod-black?style=for-the-badge&logoColor=white&logo=zod&color=3E67B1" alt="zod" />
<img src="https://img.shields.io/badge/-Typescript-black?style=for-the-badge&logoColor=white&logo=typescript&color=3178C6" alt="typescript" />

A full stack Threads Clone

 Build this project step by step with our detailed tutorial on <a href="https://www.youtube.com/@javascriptmastery/videos" target="_blank"><b>JavaScript Mastery</b></a> YouTube. Join the JSM family!

📋 Table of Contents

  1. 🤖 Introduction
  2. ⚙️ Tech Stack
  3. 🔋 Features
  4. 🤸 Quick Start
  5. 🕸️ Snippets
  6. 🔗 Links
  7. 🚀 More

🚨 Tutorial

This repository contains the code corresponding to an in-depth tutorial available on our YouTube channel, JavaScript Mastery.

If you prefer visual learning, this is the perfect resource for you. Follow our tutorial to learn how to build projects like these step-by-step in a beginner-friendly manner!

🤖 Introduction

Build a full stack Threads clone using Next.js 14+ with a redesigned look transformed from a Figma design, user interaction to community management, technical implementation, and various features, including nested deep comments, notifications, real-time-search, and more.

If you're getting started and need assistance or face any bugs, join our active Discord community with over 27k+ members. It's a place where people help each other out.

⚙️ Tech Stack

  • Next.js
  • MongoDB
  • Shadcn UI
  • TailwindCSS
  • Clerk
  • Webhooks
  • Serverless APIs
  • React Hook Form
  • Zod
  • TypeScript

🔋 Features

👉 Authentication: Authentication using Clerk for email, password, and social logins (Google and GitHub) with a comprehensive profile management system.

👉 Visually Appealing Home Page: A visually appealing home page showcasing the latest threads for an engaging user experience.

👉 Create Thread Page: A dedicated page for users to create threads, fostering community engagement

👉 Commenting Feature: A commenting feature to facilitate discussions within threads.

👉 Nested Commenting: Commenting system with nested threads, providing a structured conversation flow.

👉 User Search with Pagination: A user search feature with pagination for easy exploration and discovery of other users.

👉 Activity Page: Display notifications on the activity page when someone comments on a user's thread, enhancing user engagement.

👉 Profile Page: User profile pages for showcasing information and enabling modification of profile settings.

👉 Create and Invite to Communities: Allow users to create new communities and invite others using customizable template emails.

👉 Community Member Management: A user-friendly interface to manage community members, allowing role changes and removals.

👉 Admin-Specific Community Threads: Enable admins to create threads specifically for their community.

👉 Community Search with Pagination: A community search feature with pagination for exploring different communities.

👉 Community Profiles: Display community profiles showcasing threads and members for a comprehensive overview.

👉 Figma Design Implementation: Transform Figma designs into a fully functional application with pixel-perfect and responsive design.

👉 Blazing-Fast Performance: Optimal performance and instantaneous page switching for a seamless user experience.

👉 Server Side Rendering: Utilize Next.js with Server Side Rendering for enhanced performance and SEO benefits.

👉 MongoDB with Complex Schemas: Handle complex schemas and multiple data populations using MongoDB.

👉 File Uploads with UploadThing: File uploads using UploadThing for a seamless media sharing experience.

👉 Real-Time Events Listening: Real-time events listening with webhooks to keep users updated.

👉 Middleware, API Actions, and Authorization: Utilize middleware, API actions, and authorization for robust application security.

👉 Next.js Layout Route Groups: New Next.js layout route groups for efficient routing

👉 Data Validation with Zod: Data integrity with data validation using Zod

👉 Form Management with React Hook Form: Efficient management of forms with React Hook Form for a streamlined user input experience.

and many more, including code architecture and reusability

🤸 Quick Start

Follow these steps to set up the project locally on your machine.

Prerequisites

Make sure you have the following installed on your machine:

Cloning the Repository

git clone https://github.com/adrianhajdin/threads.git
cd threads

Installation

Install the project dependencies using npm:

npm install

Set Up Environment Variables

Create a new file named .env in the root of your project and add the following content:

MONGODB_URL=
CLERK_SECRET_KEY=
UPLOADTHING_SECRET=
UPLOADTHING_APP_ID=
NEXT_CLERK_WEBHOOK_SECRET=
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=

Replace the placeholder values with your actual credentials. You can obtain these credentials by signing up for the corresponding websites on MongoDB, Clerk, and Uploadthing.

Running the Project

npm run dev

Open http://localhost:3000 in your browser to view the project.

🕸️ Snippets

clerk.route.ts

/* eslint-disable camelcase */
// Resource: https://clerk.com/docs/users/sync-data-to-your-backend
// Above article shows why we need webhooks i.e., to sync data to our backend

// Resource: https://docs.svix.com/receiving/verifying-payloads/why
// It's a good practice to verify webhooks. Above article shows why we should do it
import { Webhook, WebhookRequiredHeaders } from "svix";
import { headers } from "next/headers";

import { IncomingHttpHeaders } from "http";

import { NextResponse } from "next/server";
import {
  addMemberToCommunity,
  createCommunity,
  deleteCommunity,
  removeUserFromCommunity,
  updateCommunityInfo,
} from "@/lib/actions/community.actions";

// Resource: https://clerk.com/docs/integration/webhooks#supported-events
// Above document lists the supported events
type EventType =
  | "organization.created"
  | "organizationInvitation.created"
  | "organizationMembership.created"
  | "organizationMembership.deleted"
  | "organization.updated"
  | "organization.deleted";

type Event = {
  data: Record<string, string | number | Record<string, string>[]>;
  object: "event";
  type: EventType;
};

export const POST = async (request: Request) => {
  const payload = await request.json();
  const header = headers();

  const heads = {
    "svix-id": header.get("svix-id"),
    "svix-timestamp": header.get("svix-timestamp"),
    "svix-signature": header.get("svix-signature"),
  };

  // Activitate Webhook in the Clerk Dashboard.
  // After adding the endpoint, you'll see the secret on the right side.
  const wh = new Webhook(process.env.NEXT_CLERK_WEBHOOK_SECRET || "");

  let evnt: Event | null = null;

  try {
    evnt = wh.verify(
      JSON.stringify(payload),
      heads as IncomingHttpHeaders & WebhookRequiredHeaders
    ) as Event;
  } catch (err) {
    return NextResponse.json({ message: err }, { status: 400 });
  }

  const eventType: EventType = evnt?.type!;

  // Listen organization creation event
  if (eventType === "organization.created") {
    // Resource: https://clerk.com/docs/reference/backend-api/tag/Organizations#operation/CreateOrganization
    // Show what evnt?.data sends from above resource
    const { id, name, slug, logo_url, image_url, created_by } =
      evnt?.data ?? {};

    try {
      // @ts-ignore
      await createCommunity(
        // @ts-ignore
        id,
        name,
        slug,
        logo_url || image_url,
        "org bio",
        created_by
      );

      return NextResponse.json({ message: "User created" }, { status: 201 });
    } catch (err) {
      console.log(err);
      return NextResponse.json(
        { message: "Internal Server Error" },
        { status: 500 }
      );
    }
  }

  // Listen organization invitation creation event.
  // Just to show. You can avoid this or tell people that we can create a new mongoose action and
  // add pending invites in the database.
  if (eventType === "organizationInvitation.created") {
    try {
      // Resource: https://clerk.com/docs/reference/backend-api/tag/Organization-Invitations#operation/CreateOrganizationInvitation
      console.log("Invitation created", evnt?.data);

      return NextResponse.json(
        { message: "Invitation created" },
        { status: 201 }
      );
    } catch (err) {
      console.log(err);

      return NextResponse.json(
        { message: "Internal Server Error" },
        { status: 500 }
      );
    }
  }

  // Listen organization membership (member invite & accepted) creation
  if (eventType === "organizationMembership.created") {
    try {
      // Resource: https://clerk.com/docs/reference/backend-api/tag/Organization-Memberships#operation/CreateOrganizationMembership
      // Show what evnt?.data sends from above resource
      const { organization, public_user_data } = evnt?.data;
      console.log("created", evnt?.data);

      // @ts-ignore
      await addMemberToCommunity(organization.id, public_user_data.user_id);

      return NextResponse.json(
        { message: "Invitation accepted" },
        { status: 201 }
      );
    } catch (err) {
      console.log(err);

      return NextResponse.json(
        { message: "Internal Server Error" },
        { status: 500 }
      );
    }
  }

  // Listen member deletion event
  if (eventType === "organizationMembership.deleted") {
    try {
      // Resource: https://clerk.com/docs/reference/backend-api/tag/Organization-Memberships#operation/DeleteOrganizationMembership
      // Show what evnt?.data sends from above resource
      const { organization, public_user_data } = evnt?.data;
      console.log("removed", evnt?.data);

      // @ts-ignore
      await removeUserFromCommunity(public_user_data.user_id, organization.id);

      return NextResponse.json({ message: "Member removed" }, { status: 201 });
    } catch (err) {
      console.log(err);

      return NextResponse.json(
        { message: "Internal Server Error" },
        { status: 500 }
      );
    }
  }

  // Listen organization updation event
  if (eventType === "organization.updated") {
    try {
      // Resource: https://clerk.com/docs/reference/backend-api/tag/Organizations#operation/UpdateOrganization
      // Show what evnt?.data sends from above resource
      const { id, logo_url, name, slug } = evnt?.data;
      console.log("updated", evnt?.data);

      // @ts-ignore
      await updateCommunityInfo(id, name, slug, logo_url);

      return NextResponse.json({ message: "Member removed" }, { status: 201 });
    } catch (err) {
      console.log(err);

      return NextResponse.json(
        { message: "Internal Server Error" },
        { status: 500 }
      );
    }
  }

  // Listen organization deletion event
  if (eventType === "organization.deleted") {
    try {
      // Resource: https://clerk.com/docs/reference/backend-api/tag/Organizations#operation/DeleteOrganization
      // Show what evnt?.data sends from above resource
      const { id } = evnt?.data;
      console.log("deleted", evnt?.data);

      // @ts-ignore
      await deleteCommunity(id);

      return NextResponse.json(
        { message: "Organization deleted" },
        { status: 201 }
      );
    } catch (err) {
      console.log(err);

      return NextResponse.json(
        { message: "Internal Server Error" },
        { status: 500 }
      );
    }
  }
};

community.actions.ts

```typescript "use server";

import { FilterQuery, SortOrder } from "mongoose";

import Community from "../models/community.model"; import Thread from "../models/thread.model"; import User from "../models/user.model";

import { connectToDB } from "../mongoose";

export async function createCommunity( id: str

Extension points exported contracts — how you extend this code

Props (Interface)
(no doc)
components/forms/Comment.tsx
Props (Interface)
(no doc)
components/shared/ProfileHeader.tsx
Props (Interface)
(no doc)
components/cards/CommunityCard.tsx
TextareaProps (Interface)
(no doc)
components/ui/textarea.tsx
Params (Interface)
(no doc)
lib/actions/thread.actions.ts
Props (Interface)
(no doc)
components/forms/DeleteThread.tsx
Props (Interface)
(no doc)
components/shared/Searchbar.tsx
Props (Interface)
(no doc)
components/cards/UserCard.tsx

Core symbols most depended-on inside this repo

cn
called by 34
lib/utils.ts
connectToDB
called by 18
lib/mongoose.ts
fetchUser
called by 9
lib/actions/user.actions.ts
dispatch
called by 5
components/ui/use-toast.ts
useFormField
called by 4
components/ui/form.tsx
handleNavigation
called by 2
components/shared/Pagination.tsx
addToRemoveQueue
called by 2
components/ui/use-toast.ts
fetchUsers
called by 2
lib/actions/user.actions.ts

Shape

Function 72
Interface 18

Languages

TypeScript100%

Modules by API surface

components/ui/use-toast.ts9 symbols
lib/actions/community.actions.ts8 symbols
lib/actions/thread.actions.ts7 symbols
lib/actions/user.actions.ts6 symbols
lib/utils.ts4 symbols
components/forms/AccountProfile.tsx4 symbols
components/shared/ThreadsTab.tsx3 symbols
components/shared/Pagination.tsx3 symbols
components/forms/PostThread.tsx3 symbols
components/forms/Comment.tsx3 symbols
components/ui/form.tsx2 symbols
components/shared/Searchbar.tsx2 symbols

Datastores touched

CommunityCollection · 1 repos
ThreadCollection · 1 repos
UserCollection · 1 repos
(mongodb)Database · 1 repos

For agents

$ claude mcp add threads \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact