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
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