Kita akan menggunakan library gorilla / mux untuk apis dan PostgreSQL DB untuk menyimpan data. Oke langsung saja ke tutorialnya, simak baik-baik.
Yang diperlukan:
Setup Project
Kita buat project direktori terlebih dahulu. terserah kalian beri nama apa. Contoh disini kita beri nama golang-postgres-crud
Buka terminal di folder yang kita buat tadi. Kita akan menginisialisasikan go modules, perintahnya sebegai berikut
go mod init go-postgres-crud
Go tidak mendukung mekanisme local include seperti kita memanggil skrip kode lokal(local import) dengan memasukan alamat skrip kode tersebut:
import “../../hw.go” // contoh salah
Untuk itu kita mengunakan nama package, nama package ini hirarkis berdasarkan penempatan pada foldernya dan tentunya kita wajib membuat go modules (go mod init ) bila berurusan dengan banyak local import.
Jika ingin mengakses / memanggil skrip kode lokal(local import) kita hanya memanggil dan menginisialisasikan di line pertama file kita. contoh
import “/modul”
/*
panggil fungsi yang ada di modul yang kita import
*/
Ketika menjalankan go mod init tadi, maka akan membuat sebuah file baru yaitu go.mod
Install Package
Pada projek ini kita menggunakan 3 package.
Buka terminal didalam folder yang kita buat tadi sebelumnya (didalam folder golang-postgres-crud )
Jalankan perintah beberapa perintah dibawah ini
Package g
import “/modul”
/*
panggil fungsi yang ada di modul yang kita import
*/
2 mengimplementasikan request router dan dispatcher untuk menangkap incoming requests untuk respective handler. Jalankan perintah perintah dibawah ini
import “/modul”
/*
panggil fungsi yang ada di modul yang kita import
*/
3
Package
import “/modul”
/*
panggil fungsi yang ada di modul yang kita import
*/
4adalah package sebagai postgres driver. Jalankan perintah perintah dibawah ini
import “/modul”
/*
panggil fungsi yang ada di modul yang kita import
*/
5
Kita akan menggunakan package godotenv untuk membaca file .env . file .env digunakan untuk menyimpan environment variables. Environment variables digunakan untuk menjaga keamanan data sensitif. Jalankan perintah perintah dibawah ini
import “/modul”
/*
panggil fungsi yang ada di modul yang kita import
*/
6
Jika sudah menjalankan perintah diatas, kita buka file go.mod dan cek file tersebut. Semua package telah terinstall terlihat dari list di file tersebut.
Seperti contoh dibawah ini
module go-postgres-crud
go 1.13
require (
github.com/gorilla/mux v1.8.0
github.com/joho/godotenv v1.3.0
github.com/lib/pq v1.8.0
)
Postgres Setup
mengapa menggunakan PostgreSQL? sistem object-relational database open source yang powerful. Ini dikenal karena reliability, ketahanan fitur, dan kinerja.
Silahkan jalankan query dibawah ini.
-- Table: buku
-- DROP TABLE buku;
CREATE TABLE buku
(
id serial NOT NULL,
judul_buku character varying NOT NULL,
penulis character varying,
tgl_publikasi date,
CONSTRAINT pk_buku PRIMARY KEY (id )
)
WITH (
OIDS=FALSE
);
ALTER TABLE buku
OWNER TO postgres;
Ketika menjalankan query sql diatas, maka akan membuat tabel buku.
Let's Coding
Struktur direktori
Folder config, didalamnya ada file config.go yang memiliki fungsi untuk konfigurasi koneksi ke DB PostgreSql.
Folder controller, didalamnya terdapat file buku.go yang memiliki fungsi sebagai wadah komunikasi awal sebelum terjadinya transaksi data ke db.
Folder models, didalamnya terdapat file models.go yang memiliki fungsi sebagai komunikasi ke db yang biasanya terjadi operasi transaksi CRUD data.
Folder router, didalamnya terdapat file router.go yang memiliki fungsi sebagai penunjuk arah route mana yang tersedia dan akan memanggil fungsi controller sesuai route yang diminta(request).
File main.go, adalah file utama dalam projek ini, kita menjalankan projek ini dengan menjalankan / mengeksekusi file ini terlebih dahulu.
Jangan lupa!!!
Untuk penamaan function harus diawali dengan huruf kapital.
Karena untuk pemanggilan dari file skrip kode lain
Config
Didalam folder config kita akan membuat file yaitu config.go,
Silahkan copy code dibawah ini;
package config
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"os"
"github.com/joho/godotenv" // package used to read the .env file
_ "github.com/lib/pq" // postgres golang driver
)
// kita buat koneksi dgn db posgres
func CreateConnection() *sql.DB {
// load .env file
err := godotenv.Load(".env")
if err != nil {
log.Fatalf("Error loading .env file")
}
// Kita buka koneksi ke db
db, err := sql.Open("postgres", os.Getenv("POSTGRES_URL"))
if err != nil {
panic(err)
}
// check the connection
err = db.Ping()
if err != nil {
panic(err)
}
fmt.Println("Sukses Konek ke Db!")
// return the connection
return db
}
type NullString struct {
sql.NullString
}
func (s NullString) MarshalJSON() ([]byte, error) {
if !s.Valid {
return []byte("null"), nil
}
return json.Marshal(s.String)
}
func (s *NullString) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
s.String, s.Valid = "", false
return nil
}
s.String, s.Valid = string(data), true
return nil
}
Penjelasan:
Function yang utama disini adalah CreateConnection(). Function ini sebagai konektor ke database Postgres.
Kita akan mengload / memanggil file .env yang dimana terdapat konfigurasi untuk menyambung projek kita ke databasenya.
Setelah itu kita buka koneksi ke db postgres.
Kita cek dengan Ping() apakah sudah terkoneksi apa belum.
Jika sudah terkoneksi maka dia akan menampilkan text "Sukses Konek ke Db!" di terminal.
Fungsi lain yang kita buat ada 2, kedua fungsi ini berguna untuk menampung jika struct / data bertipe NULL maka dia akan mengisikan data(string) kosong.
Router
Didalam folder router kita akan membuat file yaitu router.go,
Silahkan copy code dibawah ini;
package router
import (
"go-postgres-crud/controller"
"github.com/gorilla/mux"
)
func Router() *mux.Router {
router := mux.NewRouter()
router.HandleFunc("/api/buku", controller.AmbilSemuaBuku).Methods("GET", "OPTIONS")
router.HandleFunc("/api/buku/{id}", controller.AmbilBuku).Methods("GET", "OPTIONS")
router.HandleFunc("/api/buku", controller.TmbhBuku).Methods("POST", "OPTIONS")
router.HandleFunc("/api/buku/{id}", controller.UpdateBuku).Methods("PUT", "OPTIONS")
router.HandleFunc("/api/buku/{id}", controller.HapusBuku).Methods("DELETE", "OPTIONS")
return router
}
Penjelasan:
Function yang utama disini adalah Router(). Function ini sebagai memberi arah yang didapat dari request user.
Jika request mengarah ke /api/buku maka dia akan memanggil controller functionnya AmbilSemuaBuku dengan method GET
dan seterusnya.
Controller
Didalam folder controller kita akan membuat file yaitu buku.go,
Silahkan copy code dibawah ini;
package controller
import (
"encoding/json" // package untuk enkode dan mendekode json menjadi struct dan sebaliknya
"fmt"
"strconv" // package yang digunakan untuk mengubah string menjadi tipe int
"log"
"net/http" // digunakan untuk mengakses objek permintaan dan respons dari api
"go-postgres-crud/models" //models package dimana Buku didefinisikan
"github.com/gorilla/mux" // digunakan untuk mendapatkan parameter dari router
_ "github.com/lib/pq" // postgres golang driver
)
type response struct {
ID int64 `json:"id,omitempty"`
Message string `json:"message,omitempty"`
}
type Response struct {
Status int `json:"status"`
Message string `json:"message"`
Data []models.Buku `json:"data"`
}
// TambahBuku
func TmbhBuku(w http.ResponseWriter, r *http.Request) {
// create an empty user of type models.User
// kita buat empty buku dengan tipe models.Buku
var buku models.Buku
// decode data json request ke buku
err := json.NewDecoder(r.Body).Decode(&buku)
if err != nil {
log.Fatalf("Tidak bisa mendecode dari request body. %v", err)
}
// panggil modelsnya lalu insert buku
insertID := models.TambahBuku(buku)
// format response objectnya
res := response{
ID: insertID,
Message: "Data buku telah ditambahkan",
}
// kirim response
json.NewEncoder(w).Encode(res)
}
// AmbilBuku mengambil single data dengan parameter id
func AmbilBuku(w http.ResponseWriter, r *http.Request) {
// kita set headernya
w.Header().Set("Context-Type", "application/x-www-form-urlencoded")
w.Header().Set("Access-Control-Allow-Origin", "*")
// dapatkan idbuku dari parameter request, keynya adalah "id"
params := mux.Vars(r)
// konversi id dari tring ke int
id, err := strconv.Atoi(params["id"])
if err != nil {
log.Fatalf("Tidak bisa mengubah dari string ke int. %v", err)
}
// memanggil models ambilsatubuku dengan parameter id yg nantinya akan mengambil single data
buku, err := models.AmbilSatuBuku(int64(id))
if err != nil {
log.Fatalf("Tidak bisa mengambil data buku. %v", err)
}
// kirim response
json.NewEncoder(w).Encode(buku)
}
// Ambil semua data buku
func AmbilSemuaBuku(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Context-Type", "application/x-www-form-urlencoded")
w.Header().Set("Access-Control-Allow-Origin", "*")
// memanggil models AmbilSemuaBuku
bukus, err := models.AmbilSemuaBuku()
if err != nil {
log.Fatalf("Tidak bisa mengambil data. %v", err)
}
var response Response
response.Status = 1
response.Message = "Success"
response.Data = bukus
// kirim semua response
json.NewEncoder(w).Encode(response)
}
func UpdateBuku(w http.ResponseWriter, r *http.Request) {
// kita ambil request parameter idnya
params := mux.Vars(r)
// konversikan ke int yang sebelumnya adalah string
id, err := strconv.Atoi(params["id"])
if err != nil {
log.Fatalf("Tidak bisa mengubah dari string ke int. %v", err)
}
// buat variable buku dengan type models.Buku
var buku models.Buku
// decode json request ke variable buku
err = json.NewDecoder(r.Body).Decode(&buku)
if err != nil {
log.Fatalf("Tidak bisa decode request body. %v", err)
}
// panggil updatebuku untuk mengupdate data
updatedRows := models.UpdateBuku(int64(id), buku)
// ini adalah format message berupa string
msg := fmt.Sprintf("Buku telah berhasil diupdate. Jumlah yang diupdate %v rows/record", updatedRows)
// ini adalah format response message
res := response{
ID: int64(id),
Message: msg,
}
// kirim berupa response
json.NewEncoder(w).Encode(res)
}
func HapusBuku(w http.ResponseWriter, r *http.Request) {
// kita ambil request parameter idnya
params := mux.Vars(r)
// konversikan ke int yang sebelumnya adalah string
id, err := strconv.Atoi(params["id"])
if err != nil {
log.Fatalf("Tidak bisa mengubah dari string ke int. %v", err)
}
// panggil fungsi hapusbuku , dan convert int ke int64
deletedRows := models.HapusBuku(int64(id))
// ini adalah format message berupa string
msg := fmt.Sprintf("buku sukses di hapus. Total data yang dihapus %v", deletedRows)
// ini adalah format reponse message
res := response{
ID: int64(id),
Message: msg,
}
// send the response
json.NewEncoder(w).Encode(res)
}
Penjelasan:
kali ini saya tidak menjelaskan banyak, karena tiap line kode sudah beri penjelasannya.
Dari kode diatas terdapat 2 struct yaitu response dan Response yang berguna untuk menampilkan data berupa JSON.
struct response untuk menampung / menginisialisasi jika message/pesannya error atau sukses ketika memproses Add / Delete data
struct Response untuk menampung / menginisialisasi jika ada request Insert / update / display data
Dari kode diatas terdapat 5 function yang memiliki tugas yang berbeda-beda.
Function TmbhBuku untuk menambah data buku baru (Insert)
Function AmbilBuku untuk mengambil salah satu data buku (GET)
Function AmbilSemuaBuku untuk mengambil semua data buku (GET)
Funtion UpdateBuku untuk mengupdate salah satu / banyak data buku (PUT)
Function HapusBuku untuk menghapus salah satu / banyak data buku (DELETE)
Models
Didalam folder models kita akan membuat file yaitu models.go,
Silahkan copy code dibawah ini;
package models
import (
"database/sql"
"fmt"
"go-postgres-crud/config"
"log"
_ "github.com/lib/pq" // postgres golang driver
)
// Buku schema dari tabel Buku
// kita coba dengan jika datanya null
// jika return datanya ada yg null, silahkan pake NullString, contohnya dibawah
// Penulis config.NullString `json:"penulis"`
type Buku struct {
ID int64 `json:"id"`
Judul_buku string `json:"judul_buku"`
Penulis string `json:"penulis"`
Tgl_publikasi string `json:"tgl_publikasi"`
}
func TambahBuku(buku Buku) int64 {
// mengkoneksikan ke db postgres
db := config.CreateConnection()
// kita tutup koneksinya di akhir proses
defer db.Close()
// kita buat insert query
// mengembalikan nilai id akan mengembalikan id dari buku yang dimasukkan ke db
sqlStatement := `INSERT INTO buku (judul_buku, penulis, tgl_publikasi) VALUES ($1, $2, $3) RETURNING id`
// id yang dimasukkan akan disimpan di id ini
var id int64
// Scan function akan menyimpan insert id didalam id id
err := db.QueryRow(sqlStatement, buku.Judul_buku, buku.Penulis, buku.Tgl_publikasi).Scan(&id)
if err != nil {
log.Fatalf("Tidak Bisa mengeksekusi query. %v", err)
}
fmt.Printf("Insert data single record %v", id)
// return insert id
return id
}
// ambil satu buku
func AmbilSemuaBuku() ([]Buku, error) {
// mengkoneksikan ke db postgres
db := config.CreateConnection()
// kita tutup koneksinya di akhir proses
defer db.Close()
var bukus []Buku
// kita buat select query
sqlStatement := `SELECT * FROM buku`
// mengeksekusi sql query
rows, err := db.Query(sqlStatement)
if err != nil {
log.Fatalf("tidak bisa mengeksekusi query. %v", err)
}
// kita tutup eksekusi proses sql qeurynya
defer rows.Close()
// kita iterasi mengambil datanya
for rows.Next() {
var buku Buku
// kita ambil datanya dan unmarshal ke structnya
err = rows.Scan(&buku.ID, &buku.Judul_buku, &buku.Penulis, &buku.Tgl_publikasi)
if err != nil {
log.Fatalf("tidak bisa mengambil data. %v", err)
}
// masukkan kedalam slice bukus
bukus = append(bukus, buku)
}
// return empty buku atau jika error
return bukus, err
}
// mengambil satu buku
func AmbilSatuBuku(id int64) (Buku, error) {
// mengkoneksikan ke db postgres
db := config.CreateConnection()
// kita tutup koneksinya di akhir proses
defer db.Close()
var buku Buku
// buat sql query
sqlStatement := `SELECT * FROM buku WHERE id=$1`
// eksekusi sql statement
row := db.QueryRow(sqlStatement, id)
err := row.Scan(&buku.ID, &buku.Judul_buku, &buku.Penulis, &buku.Tgl_publikasi)
switch err {
case sql.ErrNoRows:
fmt.Println("Tidak ada data yang dicari!")
return buku, nil
case nil:
return buku, nil
default:
log.Fatalf("tidak bisa mengambil data. %v", err)
}
return buku, err
}
// update user in the DB
func UpdateBuku(id int64, buku Buku) int64 {
// mengkoneksikan ke db postgres
db := config.CreateConnection()
// kita tutup koneksinya di akhir proses
defer db.Close()
// kita buat sql query create
sqlStatement := `UPDATE buku SET judul_buku=$2, penulis=$3, tgl_publikasi=$4 WHERE id=$1`
// eksekusi sql statement
res, err := db.Exec(sqlStatement, id, buku.Judul_buku, buku.Penulis, buku.Tgl_publikasi)
if err != nil {
log.Fatalf("Tidak bisa mengeksekusi query. %v", err)
}
// cek berapa banyak row/data yang diupdate
rowsAffected, err := res.RowsAffected()
//kita cek
if err != nil {
log.Fatalf("Error ketika mengecheck rows/data yang diupdate. %v", err)
}
fmt.Printf("Total rows/record yang diupdate %v\n", rowsAffected)
return rowsAffected
}
func HapusBuku(id int64) int64 {
// mengkoneksikan ke db postgres
db := config.CreateConnection()
// kita tutup koneksinya di akhir proses
defer db.Close()
// buat sql query
sqlStatement := `DELETE FROM buku WHERE id=$1`
// eksekusi sql statement
res, err := db.Exec(sqlStatement, id)
if err != nil {
log.Fatalf("tidak bisa mengeksekusi query. %v", err)
}
// cek berapa jumlah data/row yang di hapus
rowsAffected, err := res.RowsAffected()
if err != nil {
log.Fatalf("tidak bisa mencari data. %v", err)
}
fmt.Printf("Total data yang terhapus %v", rowsAffected)
return rowsAffected
}
Penjelasan:
kali ini saya tidak menjelaskan banyak, karena tiap line kode sudah beri penjelasannya.
Dari kode diatas terdapat 1 struct yaitu Buku Struct yang berguna untuk menampung / display data berupa JSON.
Dari kode diatas terdapat 5 function yang memiliki tugas yang berbeda-beda.
Function TambahBuku untuk menambah data buku baru
Function AmbilSatuBuku untuk mengambil salah satu data buku
Function AmbilSemuaBuku untuk mengambil semua data buku
Funtion UpdateBuku untuk mengupdate salah satu / banyak data buku
Function HapusBuku untuk menghapus salah satu / banyak data buku
Main.go
File main.go adalah server utama kita. erver akan running pada port 8080 dan menjalankan Router.
Buat file main.go, dan copy kode dibawah ini;
package main
import (
"fmt"
"go-postgres-crud/router"
"log"
"net/http"
)
func main() {
r := router.Router()
// fs := http.FileServer(http.Dir("build"))
// http.Handle("/", fs)
fmt.Println("Server dijalankan pada port 8080...")
log.Fatal(http.ListenAndServe(":8080", r))
}
Jalankan server melalui terminal/cmd dengan perintah sebagai berikut
go run main.go
Test API
Kita akan menguji endpoint API dengan menggunakan insomnia.
Crate data buku (POST)
URL: http://127.0.0.1:8080/api/buku
Body: raw/json
Isikan json data sepperti dibawah ini
import “/modul”
/*
panggil fungsi yang ada di modul yang kita import
*/
0
Ambil data salah satu buku (GET)
URL: http://127.0.0.1:8080/api/buku/{id}
Ambil semua data buku (GET)
URL: http://127.0.0.1:8080/api/buku/
Update salah satu buku (PUT)
URL: http://127.0.0.1:8080/api/buku/{id}
Body: raw/json
Isikan json data sepperti dibawah ini
import “/modul”
/*
panggil fungsi yang ada di modul yang kita import
*/
1
Hapus salah satu buku (DELETE)
URL: http://127.0.0.1:8080/api/buku/{id}
Sebelum dihapus
Sesudah dihapus
Oke cukup sekian,
Kita telah mengetahui cara membuat CRUD sederhana golang menggunakan database PostgreSql Jika kalian ingin mempelajari tertarik mempelajari Golang silahkan eksplorasi dokumentasinya ya dan sampai jumpa di konten Golang berikutnya.
Wassalamualaikum Warahmatullahi Wabarakatuh.
File project bisa dilihat disini
Apabila tutorial diatas membantu kamu dalam belajar, dan jika kamu memiliki sedikit rezeki, mohon dukungannya dalam bentuk segelas kopi melalui URL dibawah ini: