- 📘 Introduction
- 🚀 Live Demo
- 💻 Getting Started
- 🔍 APIs Reference
- 🏗️🔨 Database ERD
- 🔄 Sequence Diagrams
- 📐 UML Diagram
- 👥 Author
- 🤝 Contributing
- ⭐️ Show Your Support
- 🔭 Up Next
- 💎 Lessons Learned
- 📜 License
Welcome to the How Backend project! Built with NestJS, a progressive Node.js framework, How is a robust and efficient Q&A application designed to empower users with knowledge and facilitate seamless communication. The How provides a SOLID foundation for building a feature-rich Q&A platform, where users can share knowledge, engage in discussions, and expand their understanding. This project consists of five modules, each serving a specific purpose to deliver a comprehensive user experience.
The authentication module provides secure user registration, login, and password reset functionality. With guards ensuring authentication and convenient decorators like @currentUser, accessing user information is a breeze. The email module integrates Nodemailer for reliable email communication, allowing users to stay connected effortlessly. The follow module enables users to connect with others, fostering a vibrant community. The question module empowers users to create, update, and delete questions, while the answer module facilitates answering and managing questions effectively. With a focus on data management, TypeORM is utilized to define entities and establish relationships between them, simplifying database operations.
With serialization and interception powered by the SerializeInterceptor, sensitive information is automatically excluded from outgoing responses, ensuring data privacy.
To optimize performance, caching has been implemented using Redis. The @nestjs/cache-manager package is used for caching, with Redis as the database store. The Cache-Aside Pattern and TTL (Time to Live) strategy are employed for cache management, improving response times and reducing database load.
To get a local copy up and running, follow these steps.
In order to run this project you need:
To run this project, you will need to add the following environment variables to a new file at the root directory named .env
:
HOST
: the host of your project (e.g. localhost)PORT
: the port of which your project work on (e.g. 3000)DATABASE_URL
: the postgres connection string postgres://username:password@host:port/databsename (e.g postgres://postgres:root@localhost:5432/How)REDIS_URL
: the redis connection string redis://host:port (e.g redis://localhost:6379)JWT_SECRET
: the json web token signature to create or validate token (e.g. jwtsecret)NODEMAILER_EMAIL
: the gmail account you will use to forward email (e.g. your-email@gmail.com)NODEMAILER_PASSWORD
: you should SMTP server password form you gmail and enable you 2-step verficaiotn (watch this video to get your password)COOKIE_SESSION_SECRET
: your cookie session secret (e.g sessionsecret)
- Clone the repository:
git clone https://github.com/ahmedeid6842/How
- Change to the project directory:
cd ./How
Install the project dependencies using NPM:
npm install
To start the application in development mode, run the following command:
npm run start:dev
The application will be accessible at http://localhost:3000.
- Alright, it's showtime! 🔥 Hit
http://localhost:3000
and BOOM! 💥 You should see the docs page and the HOW APIs working flawlessly. ✨🧙♂️
🏗️🔨 Database ERD
sequenceDiagram
participant User
participant AuthController
participant AuthService
participant UsersService
participant EmailService
participant JwtService
User->>+AuthController: register()
AuthController->>+AuthService: register(userCredentials)
AuthService->>+UsersService: createUser(userCredentials)
UsersService-->>-AuthService: user
AuthService->>+EmailService: sendRegistrationEmail(user)
EmailService-->>-AuthService: emailSent
AuthService-->>-AuthController: registrationSuccess
User->>+AuthController: login(credentials)
AuthController->>+AuthService: login(credentials)
AuthService->>+UsersService: getUserByEmail(email)
UsersService-->>-AuthService: user
AuthService->>+AuthService: comparePasswords(password, user.password)
AuthService->>+JwtService: generateToken(user)
JwtService-->>-AuthService: token
AuthService-->>-AuthController: loginSuccess(token)
User->>+AuthController: requestPasswordReset(email)
AuthController->>+AuthService: requestPasswordReset(email)
AuthService->>+UsersService: getUserByEmail(email)
UsersService-->>-AuthService: user
AuthService->>+AuthService: generatePasswordResetToken(user)
AuthService->>+EmailService: sendPasswordResetEmail(user, resetToken)
EmailService-->>-AuthService: emailSent
AuthService-->>-AuthController: passwordResetEmailSent()
User->>+AuthController: resetPassword(resetToken, newPassword)
AuthController->>+AuthService: resetPassword(resetToken, newPassword)
AuthService->>+AuthService: verifyPasswordResetToken(resetToken)
AuthService->>+UsersService: getUserById(userId)
UsersService-->>-AuthService: user
AuthService->>+AuthService: hashPassword(newPassword)
AuthService->>+UsersService: updatePassword(user, hashedPassword)
UsersService-->>-AuthService: updatedUser
AuthService-->>-AuthController: passwordResetSuccess()
User->>+AuthController: verifyEmail(email, verificationCode)
AuthController->>+AuthService: verifyEmail(email, verificationCode)
AuthService->>+UsersService: getUserByEmail(email)
UsersService-->>-AuthService: user
AuthService->>+AuthService: verifyEmail(user, verificationCode)
AuthService->>+UsersService: updateUserVerification(user)
UsersService-->>-AuthService: updatedUser
AuthService-->>-AuthController: emailVerificationSuccess()
User->>+AuthController: logout()
AuthController->>+AuthService: logout()
AuthService-->>-AuthController: logoutSuccess()
sequenceDiagram
participant Client
participant FollowController
participant FollowService
participant UserRepository
participant UserService
Client->FollowController: POST /follow
FollowController->FollowService: followUser(following_id, follower)
FollowService->UserService: findOne(followingId)
UserService-->FollowService: following
alt Invalid user id
FollowService-->FollowController: Throw BadRequestException("Invalid user id")
else
FollowService->FollowService: followExist(followingId, follower.id)
FollowService->UserService: findOne(follower.id)
UserService-->FollowService: follower
alt You can't follow yourself
FollowService-->FollowController: Throw BadRequestException("you can't follow yourself")
else
alt You already a follower
FollowService-->FollowController: Throw BadRequestException("you already a follower")
else
FollowService->UserRepository: create(following, follower)
UserRepository-->FollowService: follow
FollowService->UserRepository: save(follow)
UserRepository-->FollowService: savedFollow
FollowService-->FollowController: savedFollow
end
end
end
Client->FollowController: GET /follow/followers/:id
FollowController->FollowService: getUserFollowers(userId)
FollowService->UserRepository: find({ user: { id: userId } })
UserRepository-->FollowService: follows
FollowService-->FollowController: follows
Client->FollowController: GET /follow/following/:id
FollowController->FollowService: getUserFollowing(userId)
FollowService->UserRepository: find({ follower: { id: userId } })
UserRepository-->FollowService: follows
FollowService-->FollowController: follows
Client->FollowController: PATCH /follow/unfollow
FollowController->FollowService: unFollowUser(following_id, follower)
FollowService->UserService: findOne(followingId)
UserService-->FollowService: following
alt Invalid user id
FollowService-->FollowController: Throw BadRequestException("Invalid user id")
else
FollowService->FollowService: followExist(followingId, follower.id)
alt You are not following this user
FollowService-->FollowController: Throw BadRequestException("You are not following this user")
else
FollowService->UserRepository: remove(follow)
UserRepository-->FollowService: removedFollow
FollowService-->FollowController: removedFollow
end
end
sequenceDiagram
participant Client
participant Controller
participant Service
participant Repository
participant QuestionLikesService
Client->>Controller: POST /question
Controller->>Service: createQuestion()
alt Unique question check
Service->>Service: getQuestion({ title })
Service->>Repository: queryBuilder.getMany()
Repository->>Service: questions
alt Question not unique
Service->>Controller: BadRequestException
else
Service->>Repository: create()
Repository->>Service: savedQuestion
end
else
Service->>Repository: create()
Repository->>Service: savedQuestion
end
Service->>Service: addQuestion()
Client->>Controller: GET /question
Controller->>Service: getQuestion()
Service->>Repository: queryBuilder.getMany()
Repository->>Service: questions
alt No questions found
Service->>Controller: NotFoundException
else
Service->>Controller: questions
end
Client->>Controller: PATCH /question/:questionId
Controller->>Service: updateQuestion()
Service->>Repository: save()
Repository->>Service: updatedQuestion
Client->>Controller: DELETE /question/:questionId
Controller->>Service: deleteQuestion()
Service->>Repository: remove()
Client->>Controller: PATCH /question/like/:questionId
Controller->>Service: likeQuestion()
Service->>Service: getQuestion()
Service->>Repository: queryBuilder.getMany()
Repository->>Service: questions
alt Question not found
Service->>Controller: NotFoundException
else
Service->>QuestionLikesService: getLike()
QuestionLikesService->>Repository: findOne()
Repository->>QuestionLikesService: like
alt Like exists
Service->>Controller: BadRequestException
else
QuestionLikesService->>Repository: create()
Repository->>QuestionLikesService: like
QuestionLikesService->>Repository: save()
end
Service->>Repository: save()
Repository->>Service: question
end
sequenceDiagram
participant Client
participant Controller
participant Service
participant Repository
participant QuestionService
participant AnswerLikesService
Client->>Controller: POST /answer/:questionId
Controller->>Service: createAnswer(questionId, body, user)
Service->>QuestionService: getQuestion({ questionId })
QuestionService-->>Service: questionExist
alt questionExist is null
Service->>Controller: throw NotFoundException
else questionExist is not null
Service->>Repository: create(answer, questionExist, user)
Repository-->>Service: savedAnswer
end
Client->>Controller: GET /answer/?query
Controller->>Service: getAnswer(query)
Service->>Repository: queryBuilder.getMany()
Repository-->>Service: answers
alt answers is empty
Service->>Controller: throw NotFoundException
else answers is not empty
Service-->>Controller: answers
end
Client->>Controller: PATCH /answer/:answerId
Controller->>Service: updateAnswer(answer, body)
Service->>Repository: save(answer)
Repository-->>Service: updatedAnswer
Service-->>Controller: updatedAnswer
Client->>Controller: DELETE /answer/:questionId/:answerId
Controller->>Service: deleteAnswer(answer)
Service->>Repository: remove(answer)
Client->>Controller: PATCH /answer/like/:answerId
Controller->>Service: likeAnswer(answerId, user)
Service->>Service: getAnswer({ answerId })
Service->>AnswerLikesService: getLike(answerId, user.id)
AnswerLikesService-->>Service: likeExists
alt likeExists is not null
Service->>Controller: throw BadRequestException
else likeExists is null
Service->>AnswerLikesService: addLike(answer, user)
Service->>Repository: save(answer)
end
sequenceDiagram
participant Client
participant EmailService
participant nodemailer
Client->>EmailService: sendResetPasswordEmail(email, resetPasswordUrl)
EmailService->>nodemailer: createTransport(options)
nodemailer-->>EmailService: transporter
EmailService->>nodemailer: sendMail(message)
nodemailer-->>EmailService: result
EmailService-->>Client: result
classDiagram
class UsersService {
- userRepo
+ create()
+ findOne()
+ find()
+ update()
}
class AuthService {
- userService
- emailService
+ register()
+ login()
+ sendResetPasswordEmail()
+ resetPassword()
}
class FollowService {
- followRepo
- userService
+ startUserFollowing()
+ getUserFollowers()
+ getUserFollowing()
+ unFollowUser()
}
class QuestionService {
- questionRepository
- questionLikesService
+ addQuestion()
+ getQuestion()
+ updateQuestion()
+ deleteQuestion()
+ likeQuestion()
}
class QuestionLikesService {
- questionLikesRepository
+ getLike()
+ addLike()
}
class EmailService {
- transporter
- email
- password
+ sendResetPasswordEmail()
}
class AnswerService {
- questionService
- answerRepository
- answerLikeService
+ createAnswer()
+ getAnswer()
+ updateAnswer()
+ deleteAnswer()
+ likeAnswer()
}
class AnswerLikesService {
- answerLikesRepository
+ getLike()
+ addLike()
}
AuthService --> EmailService : depends on
AuthService --> UsersService : depends on
FollowService --> UsersService : depends on
QuestionService --> QuestionLikesService : depends on
AnswerService --> QuestionService : depends on
AnswerService --> AnswerLikesService : depends on
Ahmed Eid 🙋♂️
- Github: @ahmedeid6842
- LinkedIn : Ahmed Eid
- Twitter: @ahmedeid2684
We're always looking to improve this project! 🔍 If you notice any issues or have ideas for new features, please don't hesitate to submit a pull request 🙌 or create a new issue 💡. Your contribution will help make this project even better! ❤️ 💪
If you find this project helpful, I would greatly appreciate it if you could leave a star! 🌟 💟
- Implement Search engine for different question searches
- Support pagination for getting questions
- Enhance the DataBase queries time by using redis LRU caching
- Move from monolithic to microservices architecture.
- Apply Background jobs and task scheduling Use a job queue system like Bull or Agenda to handle time-consuming tasks.
- Secure user access with effective authentication and authorization.
- Use a well-structured architecture, such as Nest.js, for code organization, scalability, and maintainability.
- Take advantage of different NestJS components and decorators.
- There is something new to learn.
This project is licensed under the MIT License - you can click here to have more details MIT licensed.