Reddit-backend-task

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

11
Subscribe to my newsletter

Read articles from Razzaq Shikalgar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Razzaq Shikalgar
Razzaq Shikalgar