Skip to main content

IPTV Backend

You are tasked with building a backend for an IPTV Service. First check this ER Diagram:

Requirements

We can see that there are 7 tables in the database.

  1. Users
  2. Genres
  3. GenresSeries
  4. Series
  5. Seasons
  6. Episodes
  7. Streams
  8. Files
note

Genre and Series have a many-to-many relationship. A series can have multiple genres, and a genre can have multiple series. So, either you can create a new table for the relationship which you can see in the diagram, or you can assign an array of genre ids to the series. It is up to you.

Both are valid solutions, but the first one is more efficient, and the second one is easier to implement. You can choose either of them.

Technologies

You will be using the following technologies:

  • Node.js - For running the server
  • Express.js - creating the server and API
  • MongoDB - storing the data
  • Mongoose - connecting to the database, creating models, and querying the database
  • Joi - validation of the request's body, params, query, etc.
  • Multer - uploading files
  • JWT - authentication
  • Postman - testing the API
  • Git - version control
  • GitHub - hosting the repository
  • etc.

Instructions

Here are the instructions to complete the project:

Repository

  1. Download this repository
  2. Create your repo on GitHub, and name it iptv-backend
  3. Create a new branch called develop
  4. Understand the boilerplate, you have to follow the same structure i.e. function, variable, file, folder, etc. names
tip

If you are not familiar with Git and GitHub, you can just download the code, and skip the GitHub part, just start developing the project.

But, after completing the project, check out this link to learn Git and GitHub, it is essential for every developer to know Git.

General

Keep in mind the following points:

  • Use async/await or Promise for handling asynchronous code i.e. database queries

  • Use try/catch for error handling, every possible error should be handled

  • Use joi for validation of the request's body, params, query, etc.

  • Use jsonwebtoken for authentication, create a middleware for authentication

  • Use dotenv for environment variables, create a .env file, and add all the environment variables in it.

  • Use cors for cross-origin resource sharing, allow all the origins. You can use the cors package for this

  • Use nodemon for development, create a script in package.json for nodemon i.e. "dev": "nodemon index.js"

  • Don't save the password without hashing it, use bcrypt or password-hash for hashing the password

    Password Hashing

    If you don't know how to do password hashing, check out this

Request Response Format

The response should be in JSON format. The response should be like this:

POST:

Response of successful creation
{
"status": 201,
"response": "Created",
"message": "User created successfully",
"data": {
"id": "60f1c7b0e9b5a8a2b4b0b5a1",
"first_name": "Muhammad Rizwan",
"last_name": "Ashiq",
"email": "mrizwanashiq@outlook.com",
"password": "12345678"
}
}

GET

Response of successful fetching
{
"status": 200,
"response": "OK",
"message": "Genres fetched successfully",
"data": [
{
"id": "60f1c7b0e9b5a8a2b4b0b5a1",
"name": "Action"
},
{
"id": "60f1c7b0e9b5a8a2b4b0b5a2",
"name": "Comedy"
}
]
}

PATCH

Response of successful updating
{
"status": 200,
"response": "OK",
"message": "Series updated successfully",
"data": {
"id": "60f1c7b0e9b5a8a2b4b0b5a1",
"genre_id": "60f1c7b0e9b5a8a2b4b0b5a1",
"name": "The Flash",
"description": "The Flash is an American superhero television series developed by Greg Berlanti, Andrew Kreisberg, and Geoff Johns, airing on The CW. It is based on the DC Comics character Barry Allen / Flash, a costumed crime-fighter with the power to move at superhuman speeds. It is a spin-off from Arrow, existing in the same fictional universe.",
"trailer_id": "60f1c7b0e9b5a8a2b4b0b523",
"rating": 8.7,
"image_id": "60f1c7b0e9b5234a2b4b0b5a1"
}
}

DELETE

Response of successful deletion
{
"status": 200,
"response": "OK",
"message": "Season deleted successfully"
}

Status Codes

Status codes should be in the following format:

  • 200: OK (for GET requests)
  • 201: Created (for POST requests)
  • 400: Bad Request (for invalid inputs)
  • 401: Unauthorized (for invalid credentials)
  • 403: Forbidden (for unauthorized access)
  • 404: Not Found (for invalid URLs)
  • 500: Internal Server Error (for server errors)
  • 503: Service Unavailable (for server maintenance)

Public and Private APIs

There are two types of APIs:

Public APIs

In public APIs, you don't need to send any token (JWT) in the header, you can directly call the Public APIs. Only POST /users/login and POST /users/registration APIs are public APIs, all other APIs are private APIs.

Private APIs

On the other hand, private APIs are the APIs that are only accessible to authenticated users. You have to send the JWT (JSON Web Token) in the header with the name Authorization and value Bearer <token> in the request header to call the private APIs. The token can be obtained from the POST /users/login API.

Private APIs should be accessed only by authenticated users, if the user is not authenticated then the API should return a 401 status code with the following response:

Response
{
"status": 401,
"response": "Unauthorized",
"message": "Access denied. No token provided."
}

Private APIs are also called Protected APIs.

Tasks

Create models for all the tables or collection

Models are the representation of the tables in the database. You have to create models for all the tables.

note
Turn on the timestamps option for all the models

While creating a model, need to true the timestamps option. This will automatically add the createdAt and updatedAt fields to the model.

Model
import mongoose from "mongoose";

const genreSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
},
{ timestamps: true },
);

export default mongoose.model("Genre", genreSchema);

All the models should be in the models folder, already created in the boilerplate. Need to create a file for each model in the models folder. For example, genre.js for the Genre model, series.js for the Series model, etc. You need to create the following models:

  • Genre
  • GenreSeries
  • Series
  • Season
  • Episode
  • User
  • Stream
  • File

The index.js file should be in the models folder, which should export all the models. For example:

models/index.js
export * from "./genre.js";
export * from "./genreSeries.js";
export * from "./series.js";
export * from "./season.js";
export * from "./episode.js";
export * from "./user.js";
export * from "./stream.js";
export * from "./file.js";

Or

models/index.js
import Genre from "./genre.js";
import Series from "./series.js";
import Season from "./season.js";
import Episode from "./episode.js";
import User from "./user.js";
import Stream from "./stream.js";

export { Genre, Series, Season, Episode, User, Stream };

However, you can choose any of the above methods, both are correct, but the first one is more efficient.

Make these APIs

Users

  • POST /users/registration - Register a new user
  • POST /users/login - Login a user
  • GET /users - Get all users
  • GET /users/:id - Get a user by id
  • GET /users/:id/streams - Get all streams of a user by user id
  • GET /users/:id/streams/episode - Get episodes of all streams of a user by user id
  • PATCH /users/:id - Update a user by id
  • DELETE /users/:id - Delete a user by id
info

You would see in the GET APIs, that you've to get all from multiple tables, you can either use populate or aggregate method of Mongoose to get the data from multiple tables.

I would recommend you to use aggregate method, as it has so many advantages over populate method.

Genres

  • POST /genres - Create a new genre
  • GET /genres - Get all genres
  • GET /genres/:id - Get a genre by id
  • GET /genres/:id/series - Get all series of a genre by genre id
  • GET /genres/:id/series/seasons - Get all seasons of all series of a genre by genre id
  • PATCH /genres/:id - Update a genre by id
  • DELETE /genres/:id - Delete a genre by id

Series

  • POST /series - Create a new series
  • GET /series - Get all series
  • GET /series/:id - Get a series by id
  • GET /series/:id/seasons - Get all seasons of a series by series id
  • GET /series/:id/seasons/episodes - Get all episodes of a series by series id
  • PATCH /series/:id - Update a series by id
  • DELETE /series/:id - Delete a series by id

Seasons

  • POST /seasons - Create a new season
  • GET /seasons - Get all seasons
  • GET /seasons/:id - Get a season by id
  • GET /seasons/:id/episodes - Get all episodes of a season by season id
  • PATCH /seasons/:id - Update a season by id
  • DELETE /seasons/:id - Delete a season by id

Episodes

  • POST /episodes - Create a new episode
  • GET /episodes - Get all episodes
  • GET /episodes/:id - Get an episode by id
  • GET /episodes/:id/streams - Get all streams of an episode by episode id
  • PATCH /episodes/:id - Update an episode by id
  • DELETE /episodes/:id - Delete an episode by id

Streams

  • POST /streams - Create a new stream
  • GET /streams - Get all streams
  • GET /streams/:id - Get a stream by id
  • GET /streams/:id/episode - Get the episode of a stream by stream id
  • GET /streams/:id/user - Get the user of a stream by stream id
  • GET /streams/:id/episode/season - Get the season of an episode of a stream by stream id
  • GET /streams/:id/episode/season/series - Get the series of a season of an episode of a stream by stream id
  • GET /streams/:id/episode/season/series/genre - Get the genre of a series of a season of an episode of a stream by stream id
  • PATCH /streams/:id - Update a stream by id
  • DELETE /streams/:id - Delete a stream by id

Get API with Query Parameters

GET /ROUTE should have handled query parameters for pagination, sorting, and filtering of the data. First of all, let's understand what are pagination, sorting, and filtering.

tip

To understand pagination, sorting, and filtering, check out this article

This GET /series?page=1&limit=10&genre_id=1&sort=-name should return the first page of 10 series of genre id 1 sorted by name in descending order.

And this GET /seasons?page=2&limit=5&name=GOT&sort=+name should return the second page of 5 seasons of name GOT sorted by name in ascending order.

API Testing with Postman

All the APIs must be tested using Postman, you have to send the Authorization header with the value Bearer <token> in the request to access the private APIs

Create a new collection in Postman and name it IPTV, and create folders for each route. For example, create a folder for Users and create a request for each API in that folder like this:

image

Documentation

Create documentation for your application using Swagger, and Postman, and share the link with us via readme file in your repository, like this:

README.md
## Documentation

[Swagger](https://iptv-backend.herokuapp.com/api-docs)

[![Run in Postman]](https://app.getpostman.com/run-collection/XxxXxxXxxXxxXxxXxxX)

Deployment

Once you have completed your application, it's time to deploy it. Deployment is the process of deploying your application on any cloud platform. It allows you to access your application from anywhere in the world.

Deploy your application on any platform like Cyclic, DigitalOcean, Heroku, AWS, Azure, Google Cloud, etc.

tip

If you haven't deployed an application before, I would recommend you to go with Cyclic, it's free and easy to use. Create an account on Cyclic, and deploy your application on it.

On the local machine, you may be using a local database, but on the cloud, you have to use a cloud database like MongoDB Atlas. So, create an account on MongoDB Atlas, create a database for your application, and put the connection string in the environment variable MONGO_URI.

The connection string will look like this:

MongoDB Atlas Connection String
mongodb+srv://<username>:<password>@<cluster>.mongodb.net/<database>

Replace <username>, <password>, <cluster>, and <database> with your credentials.

Submission

  1. Push your code on the GitHub repository
  2. Create a pull request from your forked repository to the original repository
  3. Add your mentor as a reviewer on the pull request
  4. Your mentor will review your code and suggest changes if required by commenting on the pull request
  5. Once all the changes are done, and the code is ready to be merged, your mentor will merge your pull request
  6. You will get an email once your pull request is merged
  7. If you have any doubts, you can ask your mentor on the pull request or Slack
  8. Deploy the application on any cloud platform and share the link with us via a readme file in your repository

Evaluation Criteria

  • The code is pushed to the repository
  • Added mentor as a reviewer
  • Raised a pull request, and requested a review from the mentor
  • The project is deployed on any cloud platform
  • The structure of the project is correct, the code is clean, and all the standard practices are followed
  • All the said APIs are created, and working as expected
  • The status codes and the response data are correct and consistent with all the APIs
  • API data i.e. body, params, query is validated using Joi, and proper error messages are sent
  • All the Private APIs are secured using JWT and the user can access them only by giving the valid token
  • Documentation is created using Swagger and Postman and shared in the readme file as mentioned above

Resources

Conclusion

In this project, we learned how to create a backend project using Express and MongoDB. We learned how to create a REST API and how to use it in a frontend project. We learned how to use Postman to test our APIs. We learned how to use MongoDB Atlas to store our data in the cloud. We learned how to use Mongoose to create models and schemas for our data. We learned how to use Helmet to secure our Express app by setting various HTTP headers. We learned how to use Joi to validate the data sent by the user. We learned how to use Dotenv to store our environment variables. We learned how to use Nodemon to automatically restart the server when we make changes to our code. We learned how to use JWT to create and verify tokens. We learned how to use bcrypt to hash the passwords. We learned how to use async/await and try/catch to handle promises. We learned how to use proper error handling and status codes. We learned how to use Git and GitHub to manage our code. We learned how to deploy our project on Heroku.