This backend stores TV show subscriptions (shows they're following) and provides a way to intereact with TV open databases, such as the TMDB database.
I made this project in order showcase my code quality and add a few skills to my arsenal, such as Nest.js, MongoDB and JS unit testing (with Jest). Since this is my first time working with Nest.js and dependency injection, I'm not entirely sure if the app properly uses the framework and design pattern. So I will probably improve this later as I learn more.
This backend was supposed to developed with a React Native app, which would have allowed the user to search for TV shows and add them to their "tracked" list. Afterwards, the user would receive notifications to remind them about a show before they air. However, another project has taken my attention, so the release of the front-end app will be delayed indefintely.
Since this backend was supposed to be developed for a frontend, the only endpoints that were implemented were the ones required by that frontend. With that said, you will not see full CRUD support on every model. More endpoints will be added as needed.
- Nest.js - The backend framework which uses Node Express under the hood
- Typescript - Used by Nest.js out of the box
- MongoDB and MongoDB Compass - Database
- Jest - JavaScript Testing Framework
- Passport - Authentication Middleware
- Postman - API Testing Tool
- Bcrypt - a password hashing library
- TMDB database - to get most of the details of a show
- TVDB database - to get the air times of tv shows
The user is authenticated via Passport's JWT strategy. Here is the general flow of how it works:
The user will register with an email and password. A verification email is sent to the user with a random alphanumeric token. The user will send that token to the server when they click on the verification link, which will be cross referenced with the list of tokens on the server. User is verified if it finds a token-email match. The user's password is hashed with bcrypt.
When the user logs in, they will send their email and password to the server. If the provided password passes a bcrypt.compare() test with the hashed password stored in our database, then the user will receive a signed JWT token. The signed token will contain the user's email.
Any endpoint that has the AuthGuard decorator means all client requests for that endpoint will automatically be checked for a Bearer token in the authorization heading of the request. If a JWT token is found, it will automatically unsign the JWT token and pass it to a validation method for the server to validate. In the case of this app, we simply check to see if the email inside the token exists in the database. If it does, that means the server was the one that correctly signed this token. However, no action is currently being taken to deal with detecting and/or handling hijacked tokens.
Here are a list of routes and their brief descriptions:
- /register
- Accepts a username and password for registration
- /login
- Accepts a username and password for logging in, will return a signed JWT token
- /verify
- Accepts an email and verification token, returns an HTML page with a message
- /ping
- An AuthGuarded endpoint which lets a client test if their JWT is still valid
- /add
- Accepts a TMDB ID to add as a subscription
- /remove
- Accepts a TMDB ID to remove a susbcription
- /
- Returns a list of detailed TV shows that the user is subscribed to
- /query
- Returns a list of relevant TV shows based on a given query, or returns a "popular" list if no query is given
- /get/:id
- Accepts a TMDB ID to return details of a specific TV show
This project includes 52 unit tests and 47 end-to-end integration tests.
The unit tests only test the services of the application (which hold most of the logic). They are comprehensive enough to have 99.42% test coverage. At the top of each each .spec file contains the test plan for the respective file, but here is an example snippet:
registerUser()
√ [With non existing user & available email] calls doesEmailExist(), getUser() and sendVerificationEmail() with correct params.
√ [With non existing & unavailable email] throws NotFoundException and doesEmailExist() is called with correct params.
√ [With existing & inactive user, and available email] calls doesEmailExist(), getUser() and sendVerificationEmail() with correct params.
√ [With existing & active user, and available email] throws UnprocessableEntityException and doesEmailExist() is called with correct params.
The unit tests leave out the testing of our controllers, which is where our e2e tests come in. My e2e test are full live tests, meaning you need to have an internet connect and a MongoDB instance running with a localhost account (no credentials). The goal of these tests to test each server endpoint, which is what the controllers handle.
/register
√ [With existing email and valid password] returns 201: Verification email was sent
√ [With existing email and valid password (again)] returns 201: Verification email was sent
√ [With non existing email and valid password] returns 404: email does not exist!
√ [With existing email and short password] returns 400: Bad Request Password was not long enough!
√ [With existing email and very long password] returns 201: Verification email was sent
√ [With invalid email and short password] returns 400: Bad Request - Password was not long enough! Email was Invalid!
√ [With existing email and 9 character password] returns 400: Bad Request Password was not long enough!
√ [With existing email and 10 character password] returns 201: Verification email was sent
- root of src:
- app.module
- app-test.module
- general-interface (contains interfaces that dont belong to just one particular module)
- main
- [directory] auth
- [directory] tvshow
- [directory] config
Each module folder will contain some, if not all, the following directories:
- root of module directory
- controllers
- dto
- exception-filters
- interfaces
- middleware
- mocks
- schemas
- services
- specs (our unit tests)
- strategies
- Houssein Khafaja - [Should I put my LinkedIn here?]