By: Aldi Sugiarto
This project is made to take technical test at Privy as Backend Engineer.
There are some task to create:
- [GET] /cakes to get the list of cakes
- [GET] /cakes/:id to get a specific cake by :id
- [POST] /cakes to post a new cake
- [PATCH] /cakes/:id to update a cake by :id
- [DELETE] /cakes/:id to delete a cake by :id
The project have some requirement:
- The API must comply RESTFul guideline
- Free to improvise or create your own API response object
- Have to use the given endpoints and follow the requirements
- Create API use GO Language
- Store data use MariaDB or MySQL database
- Create script to migration database.
- Don’t use ORM
- Applied request validation
- Provide unit test on your project
- Provide the proper README
- Running in docker container
- Adding extras like (but not limited to) fancy architectural and elegant error handling & logging
There are two type to test the API
- Running with "go test"
- Running wiith docker
- Install XAMPP, laragon, or etc
- Run server (apache, nginx, etc) and database (mysql)
- Setting port refer to XAMPP, laragon, etc (Ex. Apache on port 80 and mysql on port 3306)
- Setting database for "go test" in file config-dev.json. Fill name with existing database. In this case I used sys database
{ "database": { "host": "127.0.0.1", "db-container": "rest_api-db", "port": "33066", "name": "sys", "user": "root", "pass": "root", "timeout": 1 } }
- Setting database connection at file config.go under folder config. Uncomment dbHost and dbPort, after that uncomment connection which used host and port
func InitDB(c *gin.Context) (db *sql.DB) { config := NewConfiguration() config.LoadConfigurationFromFile(getFilePathConfigEnvirontment()) dbDriver := "mysql" dbHost := config.GetValue(`database.host`) dbPort := config.GetValue(`database.port`) // dbContainer := config.GetValue(`database.db-container`) //Uncomment this row if use docker dbUser := config.GetValue(`database.user`) dbPass := config.GetValue(`database.pass`) // dbName := config.GetValue(`database.name`) connection := fmt.Sprintf(dbUser + ":" + dbPass + "@tcp(" + dbHost + ":" + dbPort + ")/") // connection := fmt.Sprintf(dbUser + ":" + dbPass + "@tcp(" + dbContainer + ":" + dbPort + ")/") //Uncomment this row if use docker db, err := sql.Open(dbDriver, connection) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "status": "Errors", }) } Migrate(db) return db }
- Open the terminal and type go test at the top of project
>> go test
- Setting database for "go test" in file config-dev.json. Fill name with existing database. In this case I used sys database
{ "database": { "host": "127.0.0.1", "db-container": "rest_api-db", "port": "33066", "name": "sys", "user": "root", "pass": "root", "timeout": 1 } }
- Setting database connection at file config.go under folder config. Uncomment dbContainer to run with docker
func InitDB(c *gin.Context) (db *sql.DB) { config := NewConfiguration() config.LoadConfigurationFromFile(getFilePathConfigEnvirontment()) dbDriver := "mysql" // dbHost := config.GetValue(`database.host`) dbPort := config.GetValue(`database.port`) dbContainer := config.GetValue(`database.db-container`) //Uncomment this row if use docker dbUser := config.GetValue(`database.user`) dbPass := config.GetValue(`database.pass`) // dbName := config.GetValue(`database.name`) // connection := fmt.Sprintf(dbUser + ":" + dbPass + "@tcp(" + dbHost + ":" + dbPort + ")/") connection := fmt.Sprintf(dbUser + ":" + dbPass + "@tcp(" + dbContainer + ":" + dbPort + ")/") //Uncomment this row if use docker db, err := sql.Open(dbDriver, connection) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "status": "Errors", }) } Migrate(db) return db }
- Check the docker-compose.yml for the database image.
database: image: mysql:5.7 container_name: rest_api-db restart: always environment: MYSQL_ROOT_PASSWORD: root MYSQL_USER: user MYSQL_PASSWORD: password MYSQL_DATABASE: privyTest MYSQL_ALLOW_EMPTY_PASSWORD: 1 ports: - 3306:3306 volumes: - rest_api-data:/var/lib/mysql networks: - rest_api-net
- Run docker daemon (in my case I used windows, so I install docker desktop) you can visit docker website for more information. Open docker desktop with Run as Administrator
- Open terminal on your top of project and type below command:
>> docker-compose build >> docker-compose up
- Check on your docker desktop that container for web, api, and pma is running.
- After all of services is running please follow below steps to test
- Server has been running at port :8080
- Move to test case section
- Open Postman and import apispec.json file in this project to do test
Refer from the task, we have created test case below:
- List of cakes.
- Endpoint : /cakes
- method : GET
- description : return a list of the cakes in JSON format, the cakes must be sorted by rating and alphabetically
- URL : http://localhost:8080/cakes
- Response:
{ "data": [ { "id": 1, "title": "Lemon cheesecake", "description": "A cheesecake made of lemon", "rating": 10, "image": "https://img.taste.com.au/ynYrqkOs/w720-h480-cfill-q80/taste/2016/11/sunny-lemon-cheesecake-102220-1.jpeg", "created_at": "2022-11-29T04:38:24Z", "updated_at": "2022-11-29T04:38:24Z" } ], "message": "get data cakes", "status": "success" }
- Detail of cake.
- Endpoint : /cakes/:id
- method : GET
- description :
return the details of a cake in JSON format example: { "id": 1, "title": "Lemon cheesecake", "description": "A cheesecake made of lemon", "rating": 7.0, "image": "https://img.taste.com.au/ynYrqkOs/w720-h480-cfill-q80/taste/2016/11/sunny-lemon-cheesecake-102220-1.jpeg", "created_at": "2020-02-01 10:56:31", "updated_at": "2020-02-13 09:30:23" }
- URL : http://localhost:8080/cakes/1
- Response:
{ "data": [ { "id": 1, "title": "Lemon cheesecake", "description": "A cheesecake made of lemon", "rating": 10, "image": "https://img.taste.com.au/ynYrqkOs/w720-h480-cfill-q80/taste/2016/11/sunny-lemon-cheesecake-102220-1.jpeg", "created_at": "2022-11-29T04:38:24Z", "updated_at": "2022-11-29T04:38:24Z" } ], "message": "get data cake by ID", "status": "success" }
- Add new cake
- Endpoint : /cakes
- method : POST
- description :
Add a cake to the cakes list, the data will be sent as a JSON in the request body : { "title": "Lemon cheesecake", "description": "A cheesecake made of lemon", "rating": 10, "image": "https://img.taste.com.au/ynYrqkOs/w720-h480-cfill-q80/taste/2016/11/sunny-lemon-cheesecake-102220-1.jpeg" }
- URL : http://localhost:8080/cakes
- Response:
{ "data": [ { "id": 1, "title": "Lemon cheesecake", "description": "A cheesecake made of lemon", "rating": 10, "image": "https://img.taste.com.au/ynYrqkOs/w720-h480-cfill-q80/taste/2016/11/sunny-lemon-cheesecake-102220-1.jpeg", "created_at": "2022-11-29T04:38:24Z", "updated_at": "2022-11-29T04:38:24Z" } ], "message": "add data cake", "status": "success" }
- Update cake
- Endpoint : /cakes/:id
- method : PATCH
- description :
Add a cake to the cakes list, the data will be sent as a JSON in the request body : { "title": "Lemon Tea", "description": "A cheesecake made of lemon", "rating": 9, "image": "https://img.taste.com.au/ynYrqkOs/w720-h480-cfill-q80/taste/2016/11/sunny-lemon-cheesecake-102220-1.jpeg" }
- URL : http://localhost:8080/cakes/1
- Response:
{ "data": [ { "id": 1, "title": "Lemon Tea", "description": "A cheesecake made of lemon", "rating": 9, "image": "https://img.taste.com.au/ynYrqkOs/w720-h480-cfill-q80/taste/2016/11/sunny-lemon-cheesecake-102220-1.jpeg", "created_at": "2022-11-29T04:38:24Z", "updated_at": "2022-11-29T04:38:24Z" } ], "message": "update data cake", "status": "success" }
- Delete cake
- Endpoint : /cakes/:id
- method : DELETE
- description : delete a cake from database
- URL : http://localhost:8080/cakes/1
- Response:
{ "message": "delete id:1 is success", "status": "success" }