Project task from dicoding.com Back-End Intermediate Class.
This project is a learning outcome through the platform dicoding.com. The aim is to enable students to create a RestfulAPI rich in features, including data validations, database relations, auth with JWT Token, MQ, File Storage, and Server-Side Cache.
Link to MQ Consumer: dicoding-back-end-intermediate-mq-consumer
- Project Set Up
- TO DO API.v1
- TO DO API.v1 Details
- TO DO API.v2
- TO DO API.v2 Details
- TO DO API.v3
- TO DO API.v3 Details
-
Create the ".env" file with the following data below:
# Server config HOST=localhost PORT=<desired port> # node-postgres config PGUSER=<your psql user> PGHOST=<your psql host> PGPASSWORD=<your psql password> PGDATABASE=<your psql database name> PGPORT=<your psql port> # JWT config ACCESS_TOKEN_KEY=<random string> REFRESH_TOKEN_KEY=<random string> ACCESS_TOKEN_AGE=<duration in ms> # RabbitMQ config RABBITMQ_SERVER=<your RabbitMQ server> # Redis config REDIS_SERVER=<your Redis host>
-
Then run this command
npm install npm run pgm up
Note:
If you do TRUNCATE tables, make sure to re-add default album data (id: album-unknown) into "albums" table.
INSERT INTO albums
VALUES('album-unknown', 'Unknown', 1945)
- Albums endpoint.
- Songs endpoint.
- Data validation.
- Error handling.
- Using Database.
- "/albums/{id}" endpoint response array of Song on Album too.
- Query params for songs endpoint.
- Registration and Authentication Users.
- Playlist endpoint.
- Implement Foreign Key on Database Tables.
- Data validation for new endpoints.
- Error handling for new endpoints.
- Keep features from API.v1.
- Collaborations on Playlists Feature.
- Activities endpoint for Playlist Log History.
- Keep optional features from API.v1.
- Export playlist feature. MQ Consumer: dicoding-back-end-intermediate-mq-consumer
- Upload album cover feature.
- Like and Unlike albums feature.
- Server-Side cache.
- Keep features from API.v2.
No optional task yay.
Back to Top | Optional Tasks | TO DO API.v2 Details | TO DO API.v3 Details
*any: Any string, but not null.
Album obj structure:
{
"id": "album-<unique-id-here>",
"name": "lorem ipsum",
"year": 2012
}
*any: Any string, but not null.
*?: Can be null or undefined.
Song obj structures:
- Main structure.
{
"id": "song-<unique-id-here>",
"title": "Lorem Ipsum",
"year": 2008,
"performer": "John Doe",
"genre": "Indie",
"duration": 120,
"albumId": "album-id"
}
- Only for GET /songs endpoint.
{
"id": "song-<unique-id-here>",
"title": "Life in Technicolor",
"performer": "Coldplay"
}
-
POST /albums
- name: string, required.
- year: number, required.
-
PUT /albums
- name: string, required.
- year: number, required.
-
POST /songs
- title: string, required.
- year: number, required.
- genre: string, required.
- performer: string, required.
- duration: number.
- albumId: string.
-
PUT /songs
- title: string, required.
- year: number, required.
- genre: string, required.
- performer: string, required.
- duration: number.
- albumId: string.
- Validation Error Response:
- status code: 400 (Bad Request)
- response body:
{ "status": "fail", "message": <any but not null>, }
- Not Found Error Response:
- status code: 404 (Not Found)
- response body:
{ "status": "fail", "message": <any but not null>, }
- Server Error Response:
- status code: 500 (Internal Server Error)
- response body:
{ "status": "error", "message": <any but not null>, }
-
Use PostgreSQL to store data. So the data will not be lost if the server is down.
-
Use dotenv to manage environment variables that store credentials for accessing database.
Back to Top | Back to Mandatory Tasks | TO DO API.v3 Details
Example:
{
"status": "success",
"data": {
"album": {
"id": "album-Mk8AnmCp210PwT6B",
"name": "Viva la Vida",
"year": 2008,
"songs": [
{
"id": "song-Qbax5Oy7L8WKf74l",
"title": "Life in Technicolor",
"performer": "Coldplay"
}
]
}
}
}
Make the GET /songs support query params for searching.
- ?title: Search song based on title.
- ?performer: Search song based on performer.
Note: Both queries can be combined ( ".../songs?title=lmao&performer=pisan" )
Back to Top | Optional Tasks | TO DO API.v1 Details | TO DO API.v3 Details
*any: Any string, but not null.
Conditions:
- Username must unique.
- Using JWT token for Auth.
- JWT token payload contains userId.
- JWT token secret key value stored on envs as ACCESS_TOKEN_KEY and REFRESH_TOKEN_KEY.
*any: Any string, but not null.
Conditions:
- Restrict endpoint (Need "access token" to access).
- GET /playlists returns owned playlists (And collab playlists if exist).
- Collaborator (if exist) can access songs (add, get, and delete) from playlist, but only owners can delete their own playlists.
- Only valid songId can be add/delete to/from playlist.
Responses:
- GET /playlists
{
"status": "success",
"data": {
"playlists": [
{
"id": "playlist-Qbax5Oy7L8WKf74l",
"name": "Lagu Indie Hits Indonesia",
"username": "dicoding"
},
{
"id": "playlist-lmA4PkM3LseKlkmn",
"name": "Lagu Untuk Membaca",
"username": "dicoding"
}
]
}
}
- GET /playlists/{id}/songs
{
"status": "success",
"data": {
"playlist": {
"id": "playlist-Mk8AnmCp210PwT6B",
"name": "My Favorite Coldplay",
"username": "dicoding",
"songs": [
{
"id": "song-Qbax5Oy7L8WKf74l",
"title": "Life in Technicolor",
"performer": "Coldplay"
},
{
"id": "song-poax5Oy7L8WKllqw",
"title": "Centimeteries of London",
"performer": "Coldplay"
},
{
"id": "song-Qalokam7L8WKf74l",
"title": "Lost!",
"performer": "Coldplay"
}
]
}
}
}
Obj Playlist for Database:
{
"id": "playlist-Qbax5Oy7L8WKf74l",
"name": "Lagu Indie Hits Indonesia",
"owner": "user-Qbax5Oy7L8WKf74l"
}
- Table songs related to albums.
- Table playlists related to users.
- etc.
-
POST /users
- username: string, required.
- password: string, required.
- fullname: string, required.
-
POST /authentications
- username: string, required.
- password: string, required.
-
PUT /authentications
- refreshToken: string, required.
-
DELETE /authentications
- refreshToken: string, required.
-
POST /playlists
- name: string, required.
-
POST /playlists/{playlistId}/songs
- songId: string, required.
The previous error handler is still in use, but there is a new handler for Auth.
- Authorization Error:
- status code: 401 (Unauthorized)
- response body:
{ "status": "fail", "message": <Any, but not null> }
- Restrict Error:
- status code: 403 (Forbidden)
- response body:
{ "status": "fail", "message": <Any, but not null> }
- Albums Feature.
- Songs Feature.
- Validations for Songs and Albums Endpoints.
Back to Top | Back to Mandatory Tasks | TO DO API.v1 Details | TO DO API.v3 Details
*any: Any string, but not null.
Conditions:
- Only the owner of the playlist can add or remove collaborators from the playlist.
Collaborator access rights:
- Collaborated playlists also shown on "GET /playlists" endpoint data.
- Can add/get/delete songs to/from playlist.
- Can see playlist activities too (If already implemented).
This feature is used to record the history of adding and removing songs from playlists by users or collaborators.
Endpoint: GET /playlists/{id}/activities
Response example:
- Status Code: 200
- Body:
{ "status": "success", "data": { "playlistId": "playlist-Mk8AnmCp210PwT6B", "activities": [ { "username": "dicoding", "title": "Life in Technicolor", "action": "add", "time": "2021-09-13T08:06:20.600Z" }, { "username": "dicoding", "title": "Centimeteries of London", "action": "add", "time": "2021-09-13T08:06:39.852Z" }, { "username": "dimasmds", "title": "Life in Technicolor", "action": "delete", "time": "2021-09-13T08:07:01.483Z" } ] } }
- Songs list from album detail.
- Query param for search songs.
Back to Top | TO DO API.v1 Details | TO DO API.v2 Details
Server Route:
- Endpoint: POST /export/playlists/{id}
- Body Req (JSON):
{ targetEmail: <string> }
Server Response:
- Status code: 201
- Body Res:
{ "status": "success", "message": <any string but not null> }
Conditions:
-
Using RabbitMQ.
- The RabbitMQ server host value must be stored in the environment variable as RABBITMQ_SERVER.
-
Only owner can export his/her own playlists.
-
The export result is in JSON format file.
{ "playlist": { "id": "playlist-Mk8AnmCp210PwT6B", "name": "My Favorite Coldplay Song", "songs": [ { "id": "song-Qbax5Oy7L8WKf74l", "title": "Life in Technicolor", "performer": "Coldplay" }, { "id": "song-poax5Oy7L8WKllqw", "title": "Centimeteries of London", "performer": "Coldplay" }, { "id": "song-Qalokam7L8WKf74l", "title": "Lost!", "performer": "Coldplay" } ] } }
-
Also create the consumer source code in a new project, not in this one. With condition below.
- Send the export result through email using nodemailer.
- SMTP credentials value must be stored in the environment variable as MAIL_ADDRESS and MAIL_PASSWORD.
- SMTP server value must be stored in the environment variable as MAIL_HOST and MAIL_PORT.
- Send the export result through email using nodemailer.
Server Route:
- Endpoint: POST /albums/{id}/covers
- Body Req:
{ "cover": <image file> }
Server Response:
- Status code: 201
- Body Res:
{ "status": "success", "message": <any string but not null> }
Conditions:
-
Only MIME types of images are allowed to upload.
-
File max size is 512000 Bytes.
-
Local storage or AWS S3 are allowed to use.
- When using AWS S3 Bucket, the bucket name must be stored in the environment variable as AWS_BUCKET_NAME.
-
GET /albums/{id} must response uploaded coverUrl too.
{ "status": "success", "data": { "album": { "id": "album-Mk8AnmCp210PwT6B", "name": "Viva la Vida", "year": 2018 "coverUrl": "http://...." } } }
- coverUrl response an uploaded image.
- If cover not uploaded yet, then response null on coverUrl instead.
- When uploadig cover to an album that already has cover, the old cover are replaced.
*any: Any string, but not null.
Conditions:
- Liking or unliking an album is a strict resource so authentication is required to access it. This aims to find out whether the user has liked the album.
- If the user hasn't liked the album, then the "POST action /albums/{id}/likes" is to like the album. If the user has already liked the album, then the action is unliking.
- Implement a server-side cache on the number of likes on an album (GET /albums/{id}/likes).
- Cache expiration time: 30 minutes.
- The response header for cache result is "X-Data-Source" with "cache" value.
- Cache should be deleted every time there is a change in the number of likes on an album.
- Must use Redis (Memurai for Windows) memory caching engine.
- Redis Host value must be stored in the environment variable as REDIS_SERVER.
Of course 🙄.
Back to Top | TO DO API.v1 Details | TO DO API.v2 Details | TO DO API.v3 Details