Reddit-backend-task

Reddit-backend-task

Backend App of reddit build using latest tech stack and deployed using kubernetes

using microservice architecture

Overview

This project is built using a microservice architecture with the following tech stack:

  • TypeScript

  • PostgreSQL

  • Drizzle ORM

  • Express

  • Postman for API documentation

Services Completed are:

  • auth-service

  • subreddit-service

  • posts-service

  • comments-service

  • subscription-service

  • vote-service

  • RabbitMQ for communication between microservices

  • Kubernetes Deployment (will do soon)

  • RabbitMQ for proper usage (yet to be implemented)

File Structure:

.
├── .env
├── .gitignore
├── DockerFile
├── drizzle
│   ├── index.ts
│   ├── migrate.ts
│   ├── migrations
│   │   ├── 0000_living_mikhail_rasputin.sql
│   │   ├── 0001_concerned_speedball.sql
│   │   └── meta
│   │       ├── 0000_snapshot.json
│   │       ├── 0001_snapshot.json
│   │       └── _journal.json
│   ├── schema.account.ts
│   ├── schema.commentst.ts
│   ├── schema.commentVotes.ts
│   ├── schema.posts.ts
│   ├── schema.sessions.ts
│   ├── schema.subreddits.ts
│   ├── schema.subscription.ts
│   ├── schema.users.ts
│   └── schema.votes.ts
├── drizzle.config.ts
├── dummyquery
├── kubernetes
│   ├── ingress.yaml
│   ├── post-deployment.yaml
│   ├── postgres-deployment.yaml
│   ├── postgres-service.yaml
│   └── user-deployment.yaml
├── package-lock.json
├── package.json
├── project_structure.txt
├── readme
└── services
    ├── auth-service
    │   ├── .env
    │   ├── DockerFile
    │   ├── package-lock.json
    │   ├── package.json
    │   ├── src
    │   │   ├── app.ts
    │   │   ├── controllers
    │   │   │   └── authController.ts
    │   │   ├── db
    │   │   │   └── auth_entity.ts
    │   │   ├── helper
    │   │   │   └── authHelper.ts
    │   │   ├── middleware
    │   │   │   └── auth-middleware.ts
    │   │   ├── models
    │   │   │   └── User.ts
    │   │   ├── routes
    │   │   │   └── authRoutes.ts
    │   │   ├── services
    │   │   │   └── auth.ts
    │   │   ├── strategies
    │   │   │   ├── googleStrategies.ts
    │   │   │   └── localStrategy.ts
    │   │   ├── swagger.ts
    │   │   ├── types
    │   │   │   └── jwtPayload.ts
    │   │   └── utils
    │   │       ├── google-oauth.ts
    │   │       ├── jwt.ts
    │   │       ├── otp.ts
    │   │       ├── paseto.ts
    │   │       └── password.ts
    │   ├── tsconfig.json
    │   └── tsconfig.spec.json
    ├── comment-service
    │   ├── package-lock.json
    │   ├── package.json
    │   ├── src
    │   │   ├── app.ts
    │   │   ├── controllers
    │   │   │   └── commentController.ts
    │   │   ├── db
    │   │   │   └── entity.ts
    │   │   ├── models
    │   │   │   └── comment.ts
    │   │   ├── routes
    │   │   │   └── commentRoutes.ts
    │   │   ├── services
    │   │   │   └── commentService.ts
    │   │   └── swagger.ts
    │   ├── tsconfig.json
    │   └── tsconfig.spec.json
    ├── posts-service
    │   ├── package-lock.json
    │   ├── package.json
    │   ├── src
    │   │   ├── app.ts
    │   │   ├── controller
    │   │   │   └── postController.ts
    │   │   ├── db
    │   │   │   └── post-entity.ts
    │   │   ├── model
    │   │   │   └── post.ts
    │   │   ├── routes
    │   │   │   └── postRoutes.ts
    │   │   ├── service
    │   │   │   └── postService.ts
    │   │   ├── swagger.ts
    │   │   └── utils
    │   │       └── validatePost.ts
    │   ├── tsconfig.json
    │   └── tsconfig.spec.json
    ├── rabbitmq
    │   ├── consumer.ts
    │   ├── producer.ts
    │   └── rabbitmq.ts
    └── subreddits-service
        ├── package-lock.json
        ├── package.json
        ├── src
        │   ├── app.ts
        │   ├── controller
        │   │   └── subredditController.ts
        │   ├── db
        │   │   └── entity.ts
        │   ├── model
        │   │   └── subreddit.ts
        │   ├── routes
        │   │   └── subredditRoutes.ts
        │   ├── services
        │   │   └── subredditServices.ts
        │   ├── swagger.ts
        │   └── utils
        │       └── validateSubreddit.ts
        ├── tsconfig.json
        └── tsconfig.spec.json
└── tsconfig.base.json

Schema Table

Repository Link

Github Repo

git clone https://github.com/RazzaqShikalgar/reddit-trc-task

Installing Dependencies

To install dependencies for all services, navigate to the root directory of the project and run:

npm run install:services

Starting Services

To start an individual service, use the following command, replacing service_name with the name of the service you want to start:

npm run start:service_name

For example, to start the auth-service, you would run:

npm run start:auth-service

Database Setup

Generating the Database

To set up the database using DrizzleORM, run the following command:

npm run db:generate

This command will generate the necessary database schema.

Running Migrations

After generating the database, apply the migrations using:

npm run db:migrate

Starting Drizzle Studio

To run Drizzle Studio for database management, use:

npx drizzle-kit studio

Service-Specific Documentation

Auth Service

Handles user authentication, including login, signup, and token verification.

Schema

import { relations } from 'drizzle-orm'
import { pgTable, text, timestamp } from 'drizzle-orm/pg-core'

import { commentVotes } from './schema.commentVotes'
import { posts } from './schema.posts'
import { subreddits } from './schema.subreddits'
import { subscriptions } from './schema.subscription'
import { votes } from './schema.votes'

export const users = pgTable('user', {
    id: text('id').notNull().primaryKey(),
    name: text('name').notNull(),
    email: text('email').notNull(),
    emailVerified: timestamp('emailVerified', { mode: 'date' }),
    image: text('image'),
    password: text('password'),
    username: text('username').unique().notNull()
})

export type User = typeof users.$inferSelect
export type NewUser = typeof users.$inferInsert

export const usersRelations = relations(users, ({ many }) => ({
    createdSubreddits: many(subreddits, {
        relationName: 'CreatedBy'
    }),

    posts: many(posts),

    votes: many(votes),

    commentVotes: many(commentVotes),

    subscriptions: many(subscriptions)
}))

Endpoints:

  • /auth/register- User Registration

  • /auth/login - User Login

  • /auth/verify-token - Verify JWT token for checking purpose

  • /auth/google - Google login/Signup

  • /auth/profile - get user profile

Environment Variables:

  • JWT_SECRET - Secret key for JWT

Subreddits Service

Schema

import { relations } from 'drizzle-orm'
import { index, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core'

import { posts } from './schema.posts'
import { subscriptions } from './schema.subscription'
import { users } from './schema.users'

export const subreddits = pgTable(
  'subreddit',
  {
    id: serial('id').primaryKey(),
    name: text('name').notNull(),
    createdAt: timestamp('createdAt').defaultNow(),
    updatedAt: timestamp('updatedAt'),

    creatorId: text('creatorId').references(() => users.id)
  },
  (subreddit) => ({
    nameIdx: index('name_idx').on(subreddit.name)
  })
)

export type Subreddit = typeof subreddits.$inferSelect
export type NewSubreddit = typeof subreddits.$inferInsert

export const subredditsRelations = relations(subreddits, ({ one, many }) => ({
  creator: one(users, {
    fields: [subreddits.creatorId],
    references: [users.id],
    relationName: 'CreatedBy'
  }),

  posts: many(posts),

  subscribers: many(subscriptions)
}))

Manages subreddit creation and retrieval.

Endpoints:

  • /subreddits/create - Create a new subreddit

  • /subreddits/:id - Get subreddit details

Environment Variables:

  • DATABASE_URL

Posts Service

Manages posts within subreddits.

Schema

import { relations } from 'drizzle-orm'
import {
    integer,
    json,
    pgTable,
    serial,
    text,
    timestamp
} from 'drizzle-orm/pg-core'

import { comments } from './schema.commentst'
import { subreddits } from './schema.subreddits'
import { users } from './schema.users'
import { votes } from './schema.votes'

export const posts = pgTable('post', {
    id: serial('id').primaryKey(),
    title: text('title').notNull(),
    content: json('content'),
    createdAt: timestamp('createdAt').defaultNow(),
    updatedAt: timestamp('updatedAt'),

    subredditId: integer('subredditId')
        .notNull()
        .references(() => subreddits.id, { onDelete: 'cascade' }),

    authorId: text('authorId')
        .notNull()
        .references(() => users.id, { onDelete: 'cascade' })
})

export type Post = typeof posts.$inferSelect
export type NewPost = typeof posts.$inferInsert

export const postsRelations = relations(posts, ({ one, many }) => ({
    subreddit: one(subreddits, {
        fields: [posts.subredditId],
        references: [subreddits.id]
    }),

    author: one(users, {
        fields: [posts.authorId],
        references: [users.id]
    }),

    comments: many(comments),

    votes: many(votes)
}))

Endpoints:

  • /posts/create-posts - Create a new post

  • /posts/get-all-posts- Get post details

  • /posts/get-post-by:id- Get post by ID

Environment Variables:

  • DATABASE_URL

Comments Service

Manages comments on posts.

Schema

import { relations } from 'drizzle-orm'
import { integer, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core'

import { commentVotes } from './schema.commentVotes'
import { posts } from './schema.posts'
import { users } from './schema.users'

export const comments = pgTable('comment', {
    id: serial('id').primaryKey(),
    text: text('text').notNull(),
    createdAt: timestamp('createdAt').defaultNow(),
    replyToId: integer('replyToId'),

    authorId: text('authorId')
        .notNull()
        .references(() => users.id, { onDelete: 'cascade' }),

    postId: integer('postId')
        .notNull()
        .references(() => posts.id, { onDelete: 'cascade' })
})

export type Comment = typeof comments.$inferSelect
export type NewComment = typeof comments.$inferInsert

export const commentsRelations = relations(comments, ({ one, many }) => ({
    author: one(users, {
        fields: [comments.authorId],
        references: [users.id]
    }),

    post: one(posts, {
        fields: [comments.postId],
        references: [posts.id]
    }),

    replyTo: one(comments, {
        fields: [comments.replyToId],
        references: [comments.id],
        relationName: 'replies'
    }),

    replies: many(comments, {
        relationName: 'replies'
    }),

    votes: many(commentVotes)
}))

Endpoints:

  • /comments/create-comment

  • - Create a new comment

  • /comments/:postId - Get comment by postId

Environment Variables:

  • DATABASE_URL

RabbitMQ Setup

RabbitMQ is used for communication between the microservices. Configuration and implementation details will be added once the RabbitMQ setup is finalized.

Producer.ts


import amqp from 'amqplib';

export const sendMessage = async (queue: string, message: any) => {
    const connection = await amqp.connect('amqp://localhost'); // Connect to RabbitMQ
    const channel = await connection.createChannel();
    await channel.assertQueue(queue, { durable: true });
    channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)), { persistent: true });
    console.log(`Sent message to ${queue}:`, message);
    await channel.close();
    await connection.close();
};

consumer.ts


import amqp from 'amqplib';

export const consumeMessages = async (queue: string) => {
    const connection = await amqp.connect('amqp://localhost');
    const channel = await connection.createChannel();
    await channel.assertQueue(queue, { durable: true });

    channel.consume(queue, (msg) => {
        if (msg) {
            const message = JSON.parse(msg.content.toString());
            console.log(`Received message from ${queue}:`, message);
            channel.ack(msg); 
        }
    }, { noAck: false });
};

Kubernetes Deployment

Deployment scripts and configurations for Kubernetes will be added once the setup is complete.

Future Enhancements

  • Implement image uploading using Uploadthing.

  • Finalize and document RabbitMQ usage.

  • Complete Kubernetes deployment setup and documentation.

Errors I'm facing

  • facing issue when hitting protected endpoint in different service - getting jwt error

  • if the above error solves then all endpoint will be in working state still trying to figure out why it is happening

working fine in auth-service

Thank you, Guys!

It was a great learning experience for me. I learned things in 2 days that I had been avoiding for the last 2-3 months. I took this as a challenge.

Though I didn't reach the expected output, I'm still happy with the progress.

I would be very happy to work with you guys again.
Thank you.
Regards,
Razzaq Shikalgar