Skip to content

Instantly share code, notes, and snippets.

@bxcodec
Last active May 18, 2023 15:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bxcodec/96683f0367d9c2e0145317e48d9cd055 to your computer and use it in GitHub Desktop.
Save bxcodec/96683f0367d9c2e0145317e48d9cd055 to your computer and use it in GitHub Desktop.
[Bahasa Indoneisa] Golang Tutorial for Beginner: Create a simple REST API

Introduction

Pada tutorial ini, akan ada beberapa steps yang akan kita lakukan. Semua steps akan dijelaskan dibawah berikut.

Adapun stepsnya adalah sebagai berikut

  • Inisialisasi project
  • Membuat models
  • Membuat HTTP Handler
  • Menyambungkan aplikasi ke Database
  • Melengkapi fungsi aplikasi

Prerequisite

  • Installed Go 1.11+
  • Installed Mysql 5.7

1. Inisialisasi Project

Yang pertama adalah lakukan install dependency management pada Golang. Terdapat 2 dependency yang cukup terkenal, yang pertama adalah Go Module (gomod) merupakan official dependency management. Lalu yang lainnya adalah Godep (dep) yang masih dipakai oleh beberapa orang.

Selanjutnya adalah inisialisasi project. Jika kamu menggunakan dep, maka project anda harus anda simpan di dalam folder GOPATH.

$ cd $GOPATH/src
$ mkdir project_name
$ cd project_name
$ dep init

Nah jika menggunakan go module, kamu bisa buat project kamu dimana aja selama bukan di GOPATH.

$ cd to_any_where
$ mkdir project_name
$ cd project_name
$ go mod init project_modul_name

2. Membuat Models

Selanjutnya buat sebuah model yang akan kita gunakan. Untuk modelnya, kita buat dipackage(folder) sehingga struktur foldernya akan terlihat seperti ini.

.
├── go.mod
├── go.sum
└── models
    └── article.go
package models

// Article represent the article structs
type Article struct {
	ID    string `json:"id"`
	Title string `json:"title"`
	Body  string `json:"body"`
}

package: menjelaskan tentang nama package dimana file tersebut disimpan

json:"title": merupakan struct tags, yang nantinya digunakan untuk mapping structs ke bentuk JSON

3. Membuat HTTP Handler

Nah untuk tahap ini kita akan membuat simple handler. Untuk sementara, kita buat sebuah handler tanpa connect kedatabase. Jadi masih menggunakan data static.

Namun sebelumnya, karena untuk handlernya kita menggunakan library orang lain, untuk kasus ini kita menggunakan echo, mini framework.

Jika menggunakan go module

$ go get github.com/labstack/echo/v4

Jika menggunakan dep, gunakan versi yang belum menggunakan gomod.

$ dep ensure -add github.com/labstack/echo

Setelah dependencynya didownload, selanjutnya kita mengerjakan handler kita. Untuk tahap ini, kita hanya akan membuat endpoint untuk Fetch Article. Untuk simple handlernya dapat dilihat berikut.

package handler

import (
	"net/http"
	"sample/models"

	"github.com/labstack/echo/v4"
)

type ArticleHandler struct {
}

func InitArticle() ArticleHandler {
	return ArticleHandler{}
}

func (h ArticleHandler) FetchArticles(c echo.Context) (err error) {
	datas := []models.Article{
		models.Article{
			ID:    "1",
			Title: "Hello World!",
			Body:  "No! Hi World!",
		},
	}

	return c.JSON(http.StatusOK, datas)
}

Lalu inisialisasi di main.go yang berada di root project.

package main

import (
	httpHandler "sample/handler"

	"github.com/labstack/echo/v4"
)

func main() {
	handler := httpHandler.InitArticle()
	echoServer := echo.New()

	// Register the handler
	echoServer.GET("/articles", handler.FetchArticles)

	// Start the server
	echoServer.Start(":9090")
}

Nah sampai disini, jika dilihat kembali, struktur folder kita akan seperti ini.

.
├── go.mod
├── go.sum
├── handler
│   └── rest.go
├── main.go
└── models
    └── article.go

Nah seharusnya dengan begini, kita sudah dapat menjalankan aplikasi kita. Kita dapat menjalankannya dengan command:

$ go run main.go

Selanjutnya check response dengan akses ke alamat server aplikasi kita.

$ curl http://localhost:9090/articles
[{"id":"1","title":"Hello World!","body":"No! Hi World!"}]

4. Menyambungkan dengan Database

Step sebelumnya, kita sudah membuat satu endpoint API yang sudah berjalan untuk endpoint GET articles. Namun, data yang diserve masih data statis.

Agar bisa dinamis, tentunya kita harus menggunakan database yang akan menjadi sumber data kita.

Untuk dapat connect ke database, sama seperti bahasa programming lain, di Go juga harus menggunakan Driver. Kalau di Java mungkin JDBC atau sejenisnya.

Di Golang, kita menggunakan library open-source yang sudah dipercaya banyak orang(dan digunakan orang) yakni library: https://github.com/go-sql-driver/mysql

Inject Database Client ke Handler

Langkah pertama adalah, kita akan meng-inject database client ke struct handler kita sebelumnya. Sekaligus tambahkan query untuk melakukan query dari database.

Sehingga, file rest.go akan terlihat sebagai berikut dibawah.

package handler

import (
	"database/sql"
	"net/http"
	"sample/models"

	"github.com/labstack/echo/v4"
)

type ErroResponse struct {
	Message string `json:"message"`
}

type ArticleHandler struct {
	DB *sql.DB
}

func InitArticle(db *sql.DB) ArticleHandler {
	return ArticleHandler{
		DB: db,
	}
}

func (h ArticleHandler) FetchArticles(c echo.Context) (err error) {
	datas := make([]models.Article, 0)
	query := `SELECT id, title, body FROM article`

	rows, err := h.DB.Query(query)
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		return c.JSON(http.StatusInternalServerError, resp)
	}
	defer rows.Close()

	for rows.Next() {
		var item models.Article
		err := rows.Scan(
			&item.ID,
			&item.Title,
			&item.Body,
		)
		if err != nil {
			resp := ErroResponse{
				Message: err.Error(),
			}
			return c.JSON(http.StatusInternalServerError, resp)
		}
		datas = append(datas, item)
	}

	return c.JSON(http.StatusOK, datas)
}

Pada tahap ini, kita telah melakukan query ke Database secara langsung.

Inisialisasi DB Connection

Selanjutnya adalah mengisi variable DB yang ada pada struct ArticleHandler. Pengisian variable DB kita lakukan di file main.go.

package main

import (
	"database/sql"
	"log"
	httpHandler "sample/handler"

	_ "github.com/go-sql-driver/mysql" //import for side effect
	"github.com/labstack/echo/v4"
)

func main() {
	dbHost := "localhost"
	dbPort := "3306"
	dbUser := "root"
	dbPass := "root"
	dbName := "article"
	dsn := dbUser + `:` + dbPass + `@tcp(` + dbHost + `:` + dbPort + `)/` + dbName + `?parseTime=1&loc=Asia%2FJakarta`

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Fatal(err)
	}

	handler := httpHandler.InitArticle(db)
	echoServer := echo.New()

	// Register the handler
	echoServer.GET("/articles", handler.FetchArticles)

	// Start the server
	echoServer.Start(":9090")
}

Untuk tahap ini, pastikan Server MySQL kita telah aktif. Dan untuk datababase dan tablenya, sesuaikan saja dengan Model yang kita miliki.

CREATE TABLE `article` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(250) NOT NULL DEFAULT '',
  `body` longtext NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Selanjutnya coba jalankan applikasinya, dan lakukan pengisian data secara manual di dalam databasenya.

Dan akses kembali GET localhost:9090/articles baik menggunakan postman atau REST Client lainnya.

5. Menambah yang lainnya.

Mengacu pada REST, maka untuk CRUD aplikasi yang akan kita buat, masih kekurangan banyak fungsi yakni diantaranya:

  • POST /articles => Insert Article
  • GET /articles/:id => Get a single Article
  • DELETE /articles/:id => Delete a single Article
  • PUT /articles/:id => Update a single Article

Add Insert Handler

Nah berikutnya adalah, kita akan membuat endpoint untuk insert Article.

Karena structur kita sudah cukup jelas di tahap sebelumnya, kini kita hanya menambahi handler yang sesua dengan kebutuhan. Sehigga untuk kasus Insert article, code rest.go akan kita tambahi code berikut:

func (h ArticleHandler) Insert(c echo.Context) (err error) {
	var item models.Article
	err = c.Bind(&item)
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		return c.JSON(http.StatusUnprocessableEntity, resp)
	}

	query := `INSERT article SET title=?, body=?`

	dbRes, err := h.DB.Exec(query, item.Title, item.Body)
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		return c.JSON(http.StatusInternalServerError, resp)
	}

	insertedID, err := dbRes.LastInsertId()
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		return c.JSON(http.StatusInternalServerError, resp)
	}

	item.ID = fmt.Sprintf("%d", insertedID)
	return c.JSON(http.StatusCreated, item)
}

Sehingga, file rest.go akan memiliki code sepenuhnya seperti ini.

package handler

import (
	"database/sql"
	"fmt"
	"net/http"
	"sample/models"

	"github.com/labstack/echo/v4"
)

type ErroResponse struct {
	Message string `json:"message"`
}

type ArticleHandler struct {
	DB *sql.DB
}

func InitArticle(db *sql.DB) ArticleHandler {
	return ArticleHandler{
		DB: db,
	}
}

func (h ArticleHandler) FetchArticles(c echo.Context) (err error) {
	datas := make([]models.Article, 0)
	query := `SELECT id, title, body FROM article`

	rows, err := h.DB.Query(query)
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		return c.JSON(http.StatusInternalServerError, resp)
	}
	defer rows.Close()

	for rows.Next() {
		var item models.Article
		err := rows.Scan(
			&item.ID,
			&item.Title,
			&item.Body,
		)
		if err != nil {
			resp := ErroResponse{
				Message: err.Error(),
			}
			return c.JSON(http.StatusInternalServerError, resp)
		}
		datas = append(datas, item)
	}

	return c.JSON(http.StatusOK, datas)
}

func (h ArticleHandler) Insert(c echo.Context) (err error) {
	var item models.Article
	err = c.Bind(&item)
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		return c.JSON(http.StatusUnprocessableEntity, resp)
	}

	query := `INSERT article SET title=?, body=?`

	dbRes, err := h.DB.Exec(query, item.Title, item.Body)
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		return c.JSON(http.StatusInternalServerError, resp)
	}

	insertedID, err := dbRes.LastInsertId()
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		return c.JSON(http.StatusInternalServerError, resp)
	}

	item.ID = fmt.Sprintf("%d", insertedID)
	return c.JSON(http.StatusCreated, item)
}

Register the Insert Handler

Meskipun kita sudah membuat handlernya, tetapi kita tetap harus menambah handler tersebut di main.go. Tambahkan code ini di main.go.

echoServer.POST("/articles", handler.Insert)

Sehingga, main.go akan berisi keseluruhan isi code berikut.

package main

import (
	"database/sql"
	"log"
	httpHandler "sample/handler"

	_ "github.com/go-sql-driver/mysql" //import for side effect
	"github.com/labstack/echo/v4"
)

func main() {
	dbHost := "localhost"
	dbPort := "3306"
	dbUser := "root"
	dbPass := "root-pass"
	dbName := "article"
	dsn := dbUser + `:` + dbPass + `@tcp(` + dbHost + `:` + dbPort + `)/` + dbName + `?parseTime=1&loc=Asia%2FJakarta`

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Fatal(err)
	}

	handler := httpHandler.InitArticle(db)
	echoServer := echo.New()

	// Register the handler
	echoServer.GET("/articles", handler.FetchArticles)
	echoServer.POST("/articles", handler.Insert)

	// Start the server
	echoServer.Start(":9090")
}

Nah sesudah sampai disini, coba aplikasinya di run ulang kembali, dan lakukan aksi POST ke endpoint tersebut menggunakan postman.

Add Get Article Handler

Di contoh sebelumnya, kita sudah menambah endpoint untuk Insert article baru. Selanjutnya kita akan membuat endpoint untuk get article detail.

Sama seperti sebelumnya, kita hanya tinggal menambah handler untuk get article saja di package handler di file rest.go. Tambahi code berikut.

func (h ArticleHandler) Get(c echo.Context) (err error) {
	articleID := c.Param("id")

	query := `SELECT id, title, body FROM article WHERE id=?`
	row := h.DB.QueryRow(query, articleID)
	var res models.Article
	err = row.Scan(
		&res.ID,
		&res.Title,
		&res.Body,
	)
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		if err == sql.ErrNoRows {
			return c.JSON(http.StatusNotFound, resp)
		}
		return c.JSON(http.StatusInternalServerError, resp)
	}

	return c.JSON(http.StatusCreated, res)
}

Sehingga, keseluruhan file rest.go akan tampak seperti ini

package handler

import (
	"database/sql"
	"fmt"
	"net/http"
	"sample/models"

	"github.com/labstack/echo/v4"
)

type ErroResponse struct {
	Message string `json:"message"`
}

type ArticleHandler struct {
	DB *sql.DB
}

func InitArticle(db *sql.DB) ArticleHandler {
	return ArticleHandler{
		DB: db,
	}
}

func (h ArticleHandler) FetchArticles(c echo.Context) (err error) {
	datas := make([]models.Article, 0)
	query := `SELECT id, title, body FROM article`

	rows, err := h.DB.Query(query)
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		return c.JSON(http.StatusInternalServerError, resp)
	}
	defer rows.Close()

	for rows.Next() {
		var item models.Article
		err := rows.Scan(
			&item.ID,
			&item.Title,
			&item.Body,
		)
		if err != nil {
			resp := ErroResponse{
				Message: err.Error(),
			}
			return c.JSON(http.StatusInternalServerError, resp)
		}
		datas = append(datas, item)
	}

	return c.JSON(http.StatusOK, datas)
}

func (h ArticleHandler) Insert(c echo.Context) (err error) {
	var item models.Article
	err = c.Bind(&item)
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		return c.JSON(http.StatusUnprocessableEntity, resp)
	}

	query := `INSERT article SET title=?, body=?`

	dbRes, err := h.DB.Exec(query, item.Title, item.Body)
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		return c.JSON(http.StatusInternalServerError, resp)
	}

	insertedID, err := dbRes.LastInsertId()
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		return c.JSON(http.StatusInternalServerError, resp)
	}

	item.ID = fmt.Sprintf("%d", insertedID)
	return c.JSON(http.StatusCreated, item)
}

func (h ArticleHandler) Get(c echo.Context) (err error) {
	articleID := c.Param("id")

	query := `SELECT id, title, body FROM article WHERE id=?`
	row := h.DB.QueryRow(query, articleID)
	var res models.Article
	err = row.Scan(
		&res.ID,
		&res.Title,
		&res.Body,
	)
	if err != nil {
		resp := ErroResponse{
			Message: err.Error(),
		}
		if err == sql.ErrNoRows {
			return c.JSON(http.StatusNotFound, resp)
		}
		return c.JSON(http.StatusInternalServerError, resp)
	}

	return c.JSON(http.StatusCreated, res)
}

Register the handler

Selanjutnya, daftarkan handler tersebut di file main.go

echoServer.GET("/articles/:id", handler.Get)

Full codenya akan terlihat sebagai berikut:

package main

import (
	"database/sql"
	"log"
	httpHandler "sample/handler"

	_ "github.com/go-sql-driver/mysql" //import for side effect
	"github.com/labstack/echo/v4"
)

func main() {
	dbHost := "localhost"
	dbPort := "3306"
	dbUser := "root"
	dbPass := "root-pass"
	dbName := "article"
	dsn := dbUser + `:` + dbPass + `@tcp(` + dbHost + `:` + dbPort + `)/` + dbName + `?parseTime=1&loc=Asia%2FJakarta`

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Fatal(err)
	}

	handler := httpHandler.InitArticle(db)
	echoServer := echo.New()

	// Register the handler
	echoServer.GET("/articles", handler.FetchArticles)
	echoServer.POST("/articles", handler.Insert)
	echoServer.GET("/articles/:id", handler.Get)

	// Start the server
	echoServer.Start(":9090")
}

Your Task!

Lengkapi keseluruhan Endpoint khususnya endpoint berikut:

  • DELETE /articles/:id => Delete a single Article
  • PUT /articles/:id => Update a single Article
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment