- Description
- Live Demo
- Features
- Tech Stack
- Run Locally
- Database Structure
- API Structure
- API Endpoints
Tokoplay is a platform where seller can promote products through videos to potential buyers. It is a simple clone of Tokopedia Play. It is built for the final project of Generasi GIGIH 3.0. This repo is the API of the app. The API is built with Express. The data is stored in MongoDB database. You can access the frontend app in this repo.
Check out the deployed API of Tokoplay here: tokoplay.up.railway.app
These are the features of the API. Features marked with (additional)
are features I added that are not included in the minimum requirements.
- Get list of videos
- Sort videos by the most recent (additional)
- Search videos (additional)
- Add a video (additional)
- Get video details
- Update a video (additional)
- Delete a video (additional)
- Get list of products (additional)
- Add a product (additional)
- Update a product (additional)
- Delete a product (additional)
- Get products of a video
- Add product(s) to a video (additional)
- Delete a product from a video (additional)
- Get comments of a video
- Sort comments by the most recent (additional)
- Add a comment to a video
- Language: JavaScript
- JavaScript Runtime Environment: Node.js
- Web Framework: Express
- Database: MongoDB
- ODM: Mongoose
- Linter: ESLint
- Code Formatter: Prettier
-
Make sure you have Node.js & Yarn installed on your computer.
-
Create a MongoDB database. You can create the database on your local, but make sure you have MongoDB Community Server & MongoDB Shell installed on your computer. You can also create the database in the cloud through MongoDB Atlas.
-
Clone the repo.
git clone https://github.com/nadiannis/tokoplay-api.git
cd tokoplay-api
-
Make a copy of
env.example
file & rename it to.env
.Write a MongoDB URI to connect the API to a database. Make sure the MongoDB is ready to use. I use MongoDB database, named
tokoplay
.# example MONGODB_URI=mongodb://localhost:27017/tokoplay
-
Install the dependencies.
yarn
Run the development server. The API will run on port 8080, but you can change the port by specifying the PORT
variable in .env
file.
yarn dev
You can prepopulate data into the database. I put the sample data in ./src/seeds/data/
. The data is in JSON. There are 2 ways to prepopulate data into the database.
1. By running a script.
The script is located in ./src/seeds/index.js
. Prepopulate the data by running this command in the root of the project.
node ./src/seeds/index
You can also use Yarn to run the script.
yarn seed
2. By importing directly through MongoDB Atlas or MongoDB Compass.
If you can access the database directly, you can import JSON data through MongoDB Atlas or MongoDB Compass installed on your computer. First, make sure that the database (tokoplay
) & collections (products
, videos
, comments
) are created. Then in each collection, import the JSON data.
Here you can see the relationships between entities.
This is the implementation of the database design in MongoDB.
products
{
"_id": objectId,
"title": string,
"price": integer,
"pageUrl": string
}
videos
{
"_id": objectId,
"title": string,
"thumbnailUrl": string,
"videoUrl": string,
"createdAt": date,
"updatedAt": date,
"products": array(objectId)
}
comments
{
"_id": objectId,
"username": string,
"content": string,
"createdAt": date,
"videoId": objectId
}
- There are 3 collections:
products
,videos
, &comments
. - A video can have many products, and a product can be in many videos. It is many-to-many relationship. In MongoDB, we can use array as a value of the field. Instead of adding a collection to store the id of video & product, I put array of product id in each video document since the products will also need to be retrieved when a video is retrieved.
- A video has many comments. But a comment belongs to just one video. It is one-to-many relationship. There is a video id in each comment document to identify which comment belongs to which video.
I use N-layer architecture for the API, which includes a controller, service, & model. N-layer architecture is a pattern used to organize & separate concerns in software development, making the code more modular & maintainable.
- Model Layer: This layer contains the data model. Models represent the data structures & interactions with the database.
- Service Layer: This layer contains core business logic of the API.
- Controller Layer: This layer is responsible for handling the requests, process them, & send back responses.
.
├── src # contains main source code
│ ├── controllers # stores the controllers
│ ├── models # stores the models
│ ├── routes # contains route files
│ ├── seeds # contains sample data in json that can be prepopulated to db
│ ├── services # contains business logic
│ ├── utils # stores utility functions
│ └── index.js # the entry point
├── .env # stores environment variables
├── ...
├── package.json # contains information about the project, dependencies, scripts, configs
├── README.md # documentation
├── yarn.lock
.
Video
- Video object
{
"_id": string,
"title": string,
"thumbnailUrl": string,
"videoUrl": string,
"createdAt": datetime,
"updatedAt": datetime,
"products": [<product_id>, <product_id>, ...]
}
- Simpler video object (not full)
{
"_id": string,
"title": string,
"thumbnailUrl": string,
"createdAt": datetime
}
- Video object with products populated
{
"_id": string,
"title": string,
"thumbnailUrl": string,
"videoUrl": string,
"createdAt": datetime,
"updatedAt": datetime,
"products": [
{<product_object>},
{<product_object>},
...
]
}
Returns all videos.
-
URL Params
None
-
Data Params
None
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 200
Content:
{ "status": "success", "message": "Videos retrieved successfully", "data": [ {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, ], "page": integer, "totalPages": integer, "count": integer }
OR
Content:
{ "status": "success", "message": "There are no videos available", "data": [], "page": 1, "totalPages": 0, "count": 0 }
-
-
Error Response
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Returns all videos sorted by the most recent.
-
URL Params
None
-
Data Params
None
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 200
Content:
{ "status": "success", "message": "Videos retrieved successfully", "data": [ {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, ], "page": integer, "totalPages": integer, "count": integer }
OR
Content:
{ "status": "success", "message": "There are no videos available", "data": [], "page": 1, "totalPages": 0, "count": 0 }
-
-
Error Response
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Returns videos filtered by a query.
-
URL Params
None
-
Data Params
None
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 200
Content:
{ "status": "success", "message": "Videos retrieved successfully", "data": [ {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, {<simpler_video_object>}, ], "page": integer, "totalPages": integer, "count": integer }
OR
Content:
{ "status": "success", "message": "There are no videos available", "data": [], "page": 1, "totalPages": 0, "count": 0 }
-
-
Error Response
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Creates a new video & returns the new object.
-
URL Params
None
-
Data Params
{ "title": string, "thumbnailUrl": string, "videoUrl": string }
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 201
Content:
{ "status": "success", "message": "Video created successfully", "data": {<video_object>} }
-
-
Error Response
-
Code: 400
Content:
{ "status": "error", "message": "Invalid request body" }
OR
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Returns the specified video.
-
URL Params
Required:
videoId=[string]
-
Data Params
None
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 200
Content:
{ "status": "success", "message": "Video retrieved successfully", "data": {<video_object_with_products_populated>} }
-
-
Error Response
-
Code: 400
Content:
{ "status": "error", "message": "Video ID is not valid" }
OR
-
Code: 404
Content:
{ "status": "error", "message": "Video not found" }
OR
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Updates fields on the specified video & returns the updated object.
-
URL Params
Required:
videoId=[string]
-
Data Params
{ "title": string, "thumbnailUrl": string, "videoUrl": string }
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 200
Content:
{ "status": "success", "message": "Video updated successfully", "data": {<video_object>} }
-
-
Error Response
-
Code: 400
Content:
{ "status": "error", "message": "Video ID is not valid" }
OR
-
Code: 404
Content:
{ "status": "error", "message": "Video not found" }
OR
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Deletes the specified video.
-
URL Params
Required:
videoId=[string]
-
Data Params
None
-
Headers
Content-Type: application/json
-
Success Response
- Code: 200
Content:
{ "status": "success", "message": "Video deleted successfully" }
-
Error Response
-
Code: 400
Content:
{ "status": "error", "message": "Video ID is not valid" }
OR
-
Code: 404
Content:
{ "status": "error", "message": "Video not found" }
OR
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Product
- Product object
{
"_id": string,
"title": string,
"price": integer,
"pageUrl": string
}
Returns all products.
-
URL Params
None
-
Data Params
None
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 200
Content:
{ "status": "success", "message": "Products retrieved successfully", "data": [ {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, ], "page": integer, "totalPages": integer, "count": integer }
OR
Content:
{ "status": "success", "message": "There are no products available", "data": [], "page": 1, "totalPages": 0, "count": 0 }
-
-
Error Response
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Creates a new product & returns the new object.
-
URL Params
None
-
Data Params
{ "title": string, "price": integer, "pageUrl": string }
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 201
Content:
{ "status": "success", "message": "Product created successfully", "data": {<product_object>} }
-
-
Error Response
-
Code: 400
Content:
{ "status": "error", "message": "Invalid request body" }
OR
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Updates fields on the specified product & returns the updated object.
-
URL Params
Required:
productId=[string]
-
Data Params
{ "title": string, "price": integer, "pageUrl": string }
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 200
Content:
{ "status": "success", "message": "Product updated successfully", "data": {<product_object>} }
-
-
Error Response
-
Code: 400
Content:
{ "status": "error", "message": "Product ID is not valid" }
OR
-
Code: 404
Content:
{ "status": "error", "message": "Product not found" }
OR
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Deletes the specified product.
-
URL Params
Required:
productId=[string]
-
Data Params
None
-
Headers
Content-Type: application/json
-
Success Response
- Code: 200
Content:
{ "status": "success", "message": "Product deleted successfully" }
-
Error Response
-
Code: 400
Content:
{ "status": "error", "message": "Product ID is not valid" }
OR
-
Code: 404
Content:
{ "status": "error", "message": "Product not found" }
OR
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Returns all products associated with the specified video.
-
URL Params
Required:
videoId=[string]
-
Data Params
None
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 200
Content:
{ "status": "success", "message": "Products of the video retrieved successfully", "data": [ {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, {<product_object>}, ] }
OR
Content:
{ "status": "success", "message": "There are no products in the video", "data": [] }
-
-
Error Response
-
Code: 400
Content:
{ "status": "error", "message": "Video ID is not valid" }
OR
-
Code: 404
Content:
{ "status": "error", "message": "Video not found" }
OR
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Adds product(s) to the specified video & returns the updated specified video with the products.
-
URL Params
Required:
videoId=[string]
-
Data Params
{ "productIds": [<product_id>, <product_id>, ...] }
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 201
Content:
{ "status": "success": "message": "Products added to the video successfully", "data": {<video_object>} }
-
-
Error Response
-
Code: 400
Content:
{ "status": "error", "message": "Video ID is not valid" }
OR
Content:
{ "status": "error", "message": "Invalid request body" }
OR
Content:
{ "status": "error", "message": "productIds should be an array" }
OR
Content:
{ "status": "error", "message": "There is something wrong with the product ID. Product may not be found in the database. Make sure the product ID is correct." }
OR
-
Code: 404
Content:
{ "status": "error", "message": "Video not found" }
OR
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Deletes a product from a specified video & returns the updated specified video with the products.
-
URL Params
Required:
videoId=[string]
& Required:productId=[string]
-
Data Params
None
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 200
Content:
{ "status": "success": "message": "Product removed from the video successfully", "data": {<video_object>} }
-
-
Error Response
-
Code: 400
Content:
{ "status": "error", "message": "Video ID is not valid" }
OR
Content:
{ "status": "error", "message": "Product ID is not valid" }
OR
-
Code: 404
Content:
{ "status": "error", "message": "Video not found" }
OR
Content:
{ "status": "error", "message": "Product not found in the video" }
OR
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Comment
- Comment object
{
"_id": string,
"username": string,
"content": string,
"createdAt": datetime,
"videoId": <video_id>
}
Returns all comments associated with the specified video.
-
URL Params
Required:
videoId=[string]
-
Data Params
None
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 200
Content:
{ "status": "success", "message": "Comments of the video retrieved successfully", "data": [ {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, ], "page": integer, "totalPages": integer, "count": integer }
OR
Content:
{ "status": "success", "message": "There are no comments in the video", "data": [], "page": 1, "totalPages": 0, "count": 0 }
-
-
Error Response
-
Code: 400
Content:
{ "status": "error", "message": "Video ID is not valid" }
OR
-
Code: 404
Content:
{ "status": "error", "message": "Video not found" }
OR
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Returns all comments associated with the specified video, sorted by the most recent.
-
URL Params
Required:
videoId=[string]
-
Data Params
None
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 200
Content:
{ "status": "success", "message": "Comments of the video retrieved successfully", "data": [ {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, {<comment_object>}, ], "page": integer, "totalPages": integer, "count": integer }
OR
Content:
{ "status": "success", "message": "There are no comments in the video", "data": [], "page": 1, "totalPages": 0, "count": 0 }
-
-
Error Response
-
Code: 400
Content:
{ "status": "error", "message": "Video ID is not valid" }
OR
-
Code: 404
Content:
{ "status": "error", "message": "Video not found" }
OR
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-
Creates a new comment associated with the specified video & returns the new object.
-
URL Params
Required:
videoId=[string]
-
Data Params
{ "username": string, "content": string }
-
Headers
Content-Type: application/json
-
Success Response
-
Code: 201
Content:
{ "status": "success": "message": "Comment created successfully", "data": {<comment_object>} }
-
-
Error Response
-
Code: 400
Content:
{ "status": "error", "message": "Video ID is not valid" }
OR
Content:
{ "status": "error", "message": "Invalid request body" }
OR
-
Code: 404
Content:
{ "status": "error", "message": "Video not found" }
OR
-
Code: 500
Content:
{ "status": "error", "message": <error_message> }
-