Code Monkey home page Code Monkey logo

TUTORIAL FULL STACK REST API IN GOLANG LEVEL UP CODING (Bahasa Indonesia)


go

LANGKAH PERTAMA UNTUK MEMBUAT REST FULL API DENGAN GOLANG YAITU,

  1. Membuat folder project bernama Fullstack
  2. Inisialisai Poroject dengan cara melakukan syntax berikut .
go mod init github.com/{username git}/{nama folder}

// contoh
go mod init github.com/cepot-blip/fullstack
  1. Instalasi beberapa dependensi kebutuahn project kita, disini saya menggunakan ORM gorm
go get github.com/jinzhu/gorm
  1. Install bcrypt dengan melakukan syntax berikut, untuk melakukan penghashan pada password
go get golang.org/x/crypto/bcrypt
  1. Install Gorila mux untuk Router nya syntax sebagai berikut
go get github.com/gorilla/mux
  1. Install JWT atau Json Web Token untuk keperluan login pada user atau lainnya sbt .
go get github.com/dgrijalva/jwt-go
  1. Install Databases, karena disini saya menggunakan Databases Mysql untuk keperluan Project saya dan sebagai berikut syntax ny
go get github.com/jinzhu/gorm/dialects/mysql"

Setalah selesai semua kebutan dependensi yang kita butuhkan langsung masuk kedalam Kode editor kesayang kita semua Visual Studio Code, dan langkah selanjutanya yaitu menentukan folder atau bisa di sebut dengan FOLDER STRUKTURING agar mempermudahkan dalam pengerjaan project kita

Didalam folder project Fullstack yang tadi kita buat masuk kedalam nya dan kita akan membuat Folder dan File bernama API ,TEST, .ENV dan main.go

  • api

folder dibawah ini ada di dalam semua folder api

  • auth
  • controllers
  • middlewares
  • models
  • responses
  • seed
  • utils

*Dan didalam folder api membuat file bernama server.go*

*Dan DILUAR folder api membuat file bernama main.go untuk runing server utama kita*

Dan tampilan nya akan seperti ini jika sudah membuat folder semua itu.

sss

selanjut nya kita buka file .env kita untuk mengedit isi dari file tersebut dengan membuat seperti berikut.

env

Berhubunga kita menggunakan mysql sebagai DB nya kita membutuhkan DB_USER mysql dan DB_PASSWORD kita dan untuk DB_NAME itu kita membuat nya di dalam databases mysql kita bernama server_golang sebagai contoh, dan utuk db_port karena saya menggunakan window jadi port nya 3306 dan untuk pengguna mac os port nya 8889, jangan lupa untuk start mamp agar terconect ke server mysql nya, silahkan di download apk mamp nya di google banyak.

CONTOH MAMP YANG SAYA GUNAKAN UNTUK WINDOW

mamp

SELANJUT NYA kita akan membuat file di dalam folder controllers bernama base.go dimana sebagai controller untuk file server.go kita

package controllers

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/mux"
	"github.com/jinzhu/gorm"

	_ "github.com/jinzhu/gorm/dialects/mysql"
)

type Server struct {
	DB     *gorm.DB
	Router *mux.Router
}

func (server *Server) Initialize(Dbdriver, DbUser, DbPassword, DbPort, DbHost, DbName string) {

	var err error

	if Dbdriver == "mysql" {
		DBURL := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", DbUser, DbPassword, DbHost, DbPort, DbName)
		server.DB, err = gorm.Open(Dbdriver, DBURL)
		if err != nil {
			fmt.Printf("Cannot connect to %s database", Dbdriver)
			log.Fatal("This is the error:", err)
		} else {
			fmt.Printf("We are connected to the %s database", Dbdriver)
		}
	}

	server.Router = mux.NewRouter()

	server.initializeRoutes()
}

func (server *Server) Run(addr string) {
	fmt.Println("Listening to port 9000")
	log.Fatal(http.ListenAndServe(addr, server.Router))
}

SELANJUT NYA KITA AKAN MEMBUAT TOKEN FILE DIDALAM FOLDER AUTH bernama token.go untuk Membuat token pada users untuk validasi login

package auth

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
	jwt "github.com/dgrijalva/jwt-go"
)

//		CREATE TOKEN USERS
func CreateToken(user_id uint32) (string, error) {
	claims := jwt.MapClaims{}
	claims["authorized"] = true
	claims["user_id"] = user_id
	claims["exp"] = time.Now().Add(time.Hour * 1).Unix() //Token expayed sebelum 1 jam
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(os.Getenv("API_SECRET")))
}

//		EXTRACT TOKEN
func ExtractToken(r *http.Request) string {
	keys := r.URL.Query()
	token := keys.Get("token")
	if token != "" {
		return token
	}
	bearerToken := r.Header.Get("Authorization")
	if len(strings.Split(bearerToken, " ")) == 2 {
		return strings.Split(bearerToken, " ")[1]
	}
	return ""
}

//		EXTRACT TOKEN ID
func ExtractTokenID(r *http.Request) (uint32, error) {

	tokenString := ExtractToken(r)
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return []byte(os.Getenv("API_SECRET")), nil
	})
	if err != nil {
		return 0, err
	}
	claims, ok := token.Claims.(jwt.MapClaims)
	if ok && token.Valid {
		uid, err := strconv.ParseUint(fmt.Sprintf("%.0f", claims["user_id, admins_id"]), 10, 32)
		if err != nil {
			return 0, err
		}
		return uint32(uid), nil
	}
	return 0, nil
}

// 	Cukup tampilkan klaim licely di terminal
func Pretty(data interface{}) {
	e, err := json.MarshalIndent(data, "", "")
	if err != nil {
		log.Println(err)
		return
	}

	fmt.Println(string(e))
}

SELANJUT NYA KITA AKAN MEMBUAT FILE DIDALAM FOLDER RESPONSES bernama handlejson.go untuk Menjadikan json,

package responses

import (
	"encoding/json"
	"fmt"
	"net/http"
)

func JSON(w http.ResponseWriter, statusCode int, data interface{}) {
	w.WriteHeader(statusCode)
	err := json.NewEncoder(w).Encode(data)
	if err != nil {
		fmt.Fprintf(w, "%s", err.Error())
	}
}

func ERROR(w http.ResponseWriter, statusCode int, err error) {
	if err != nil {
		JSON(w, statusCode, struct {
			Error string `json:"error"`
		}{
			Error: err.Error(),
		})
		return
	}
	JSON(w, http.StatusBadRequest, nil)
}

SELANJUT NYA KITA AKAN MEMBUAT FOLDER DIDALAM FOLDER UTILS bernama FORMATERROR didalam FOLDER FORMATERROR buat file bernama handleerror.go untuk Menghandle apa bila terjadi error,

package formaterror

import (
	"errors"
	"strings"
)

func FormatError(err string) error {

	if strings.Contains(err, "nickname") {
		return errors.New("Nickname must be unique")
	}

	if strings.Contains(err, "email") {
		return errors.New("Email must be unique")
	}

	if strings.Contains(err, "title") {
		return errors.New("Title Already Taken")
	}
	if strings.Contains(err, "hashedPassword") {
		return errors.New("Incorrect Password")
	}
	return errors.New("Incorrect Details")
}

SELANJUT NYA KITA AKAN MEMBUAT FILE DIDALAM FOLDER MODEL bernama Users.go untuk membuat Model Users Apa saja yang di butuhkan,

package models

import (
	"errors"
	"html"
	"log"
	"strings"
	"time"

	"github.com/badoux/checkmail"
	"github.com/jinzhu/gorm"
	"golang.org/x/crypto/bcrypt"
)

//		MODEL YANG INGIN DI BUAT
type User struct {
	ID        uint32    `gorm:"primary_key;auto_increment" json:"id"`
	Nickname  string    `gorm:"size:255;not null;unique" json:"nickname"`
	Email     string    `gorm:"size:100;not null;unique" json:"email"`
	Password  string    `gorm:"size:100;not null;" json:"password"`
	CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
	UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
}

//		HASH PASSWORD
func Hash(password string) ([]byte, error) {
	return bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
}

//		COMPARE PASSWORD
func VerifyPassword(hashedPassword, password string) error {
	return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
}

func (u *User) BeforeSave() error {
	hashedPassword, err := Hash(u.Password)
	if err != nil {
		return err
	}
	u.Password = string(hashedPassword)
	return nil
}

func (u *User) Prepare() {
	u.ID = 0
	u.Nickname = html.EscapeString(strings.TrimSpace(u.Nickname))
	u.Email = html.EscapeString(strings.TrimSpace(u.Email))
	u.CreatedAt = time.Now()
	u.UpdatedAt = time.Now()
}

//		VALIDASI
func (u *User) Validate(action string) error {
	switch strings.ToLower(action) {
	case "update":
		if u.Nickname == "" {
			return errors.New("required Nickname")
		}
		if u.Password == "" {
			return errors.New("required Password")
		}
		if u.Email == "" {
			return errors.New("required Email")
		}
		if err := checkmail.ValidateFormat(u.Email); err != nil {
			return errors.New("invalid Email")
		}
		return nil

	case "login":
		if u.Password == "" {
			return errors.New("required Password")
		}
		if u.Email == "" {
			return errors.New("required Email")
		}
		if err := checkmail.ValidateFormat(u.Email); err != nil {
			return errors.New("invalid Email")
		}
		return nil

	default:
		if u.Nickname == "" {
			return errors.New("required Nickname")
		}
		if u.Password == "" {
			return errors.New("required Password")
		}
		if u.Email == "" {
			return errors.New("required Email")
		}
		if err := checkmail.ValidateFormat(u.Email); err != nil {
			return errors.New("invalid Email")
		}
		return nil
	}
}

//		CREATE USERS
func (u *User) SaveUser(db *gorm.DB) (*User, error) {

	var _, err error
	err = db.Debug().Create(&u).Error
	if err != nil {
		return &User{}, err
	}
	return u, nil
}

//		READ ALL USERS
func (u *User) FindAllUsers(db *gorm.DB) (*[]User, error) {
	var err error
	users := []User{}
	err = db.Debug().Model(&User{}).Limit(100).Find(&users).Error
	if err != nil {
		return &[]User{}, err
	}
	return &users, err
}

//		LOGIN USERS BY ID
func (u *User) FindUserByID(db *gorm.DB, uid uint32) (*User, error) {
	var _, err error
	err = db.Debug().Model([]User{}).Where("id = ?", uid).Take(&u).Error
	if err != nil {
		return &User{}, err
	}
	if gorm.IsRecordNotFoundError(err) {
		return &User{}, errors.New("User Not Found")
	}
	return u, err
}

//		UPDATE USERS
func (u *User) UpdateAUser(db *gorm.DB, uid uint32) (*User, error) {

	// Untuk hash password kembali
	err := u.BeforeSave()
	if err != nil {
		log.Fatal(err)
	}
	db = db.Debug().Model(&User{}).Take(&User{}).UpdateColumns(
		map[string]interface{}{
			"password":  u.Password,
			"nickname":  u.Nickname,
			"email":     u.Email,
			"update_at": time.Now(),
		},
	)
	if db.Error != nil {
		return &User{}, db.Error
	}

	//   Ini adalah tampilan users yang diperbarui
	err = db.Debug().Model(&User{}).Take(&u).Error
	if err != nil {
		return &User{}, err
	}
	return u, nil
}

//		DELETE USERS
func (u *User) DeleteAUser(db *gorm.DB, uid uint32) (int64, error) {

	db = db.Debug().Model(&User{}).Where("id = ?", uid).Take(&User{}).Delete(&User{})

	if db.Error != nil {
		return 0, db.Error
	}
	return db.RowsAffected, nil
}

SELANJUT NYA KITA AKAN MEMBUAT FILE DIDALAM FOLDER CONTROLLERS bernama users_controllers.go untuk Mengcontroller Model Users yang sudah kita buat tadi,

package controllers

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"

	"github.com/cepot-blip/fullstack/api/auth"
	"github.com/cepot-blip/fullstack/api/models"
	"github.com/cepot-blip/fullstack/api/responses"
	"github.com/cepot-blip/fullstack/api/utils/formaterror"
	"github.com/gorilla/mux"
)

//		CREATE USERS
func (server *Server) CreateUser(w http.ResponseWriter, r *http.Request) {

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		responses.ERROR(w, http.StatusUnprocessableEntity, err)
	}
	user := models.User{}
	err = json.Unmarshal(body, &user)
	if err != nil {
		responses.ERROR(w, http.StatusUnprocessableEntity, err)
		return
	}
	user.Prepare()
	err = user.Validate("")
	if err != nil {
		responses.ERROR(w, http.StatusUnprocessableEntity, err)
		return
	}
	userCreated, err := user.SaveUser(server.DB)

	if err != nil {

		formattedError := formaterror.FormatError(err.Error())

		responses.ERROR(w, http.StatusInternalServerError, formattedError)
		return
	}
	w.Header().Set("Location", fmt.Sprintf("%s%s/%d", r.Host, r.RequestURI, userCreated.ID))
	responses.JSON(w, http.StatusOK, userCreated)
}

//		READ ALL USERS
func (server *Server) GetUsers(w http.ResponseWriter, r *http.Request) {

	user := models.User{}

	users, err := user.FindAllUsers(server.DB)
	if err != nil {
		responses.ERROR(w, http.StatusInternalServerError, err)
		return
	}
	responses.JSON(w, http.StatusOK, users)
}

//		FIND USERS BY ID
func (server *Server) GetUser(w http.ResponseWriter, r *http.Request) {

	vars := mux.Vars(r)
	uid, err := strconv.ParseUint(vars["id"], 10, 32)
	if err != nil {
		responses.ERROR(w, http.StatusBadRequest, err)
		return
	}
	user := models.User{}
	userGet, err := user.FindUserByID(server.DB, uint32(uid))
	if err != nil {
		responses.ERROR(w, http.StatusBadRequest, err)
		return
	}
	responses.JSON(w, http.StatusOK, userGet)
}

//		UPDATE USERS
func (server *Server) UpdateUser(w http.ResponseWriter, r *http.Request) {

	vars := mux.Vars(r)
	uid, err := strconv.ParseUint(vars[""], 10, 32)
	if err != nil {
		responses.ERROR(w, http.StatusBadRequest, err)
		return
	}
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		responses.ERROR(w, http.StatusUnprocessableEntity, err)
		return
	}
	user := models.User{}
	err = json.Unmarshal(body, &user)
	if err != nil {
		responses.ERROR(w, http.StatusUnprocessableEntity, err)
		return
	}
	tokenID, err := auth.ExtractTokenID(r)
	 if err != nil {
	 	responses.ERROR(w, http.StatusUnauthorized, errors.New("Unauthorized"))
	 	return
	 }
	 if tokenID != uint32(uid) {
		responses.ERROR(w, http.StatusUnauthorized, errors.New(http.StatusText(http.StatusUnauthorized)))
 	return
	 }
	user.Prepare()
	err = user.Validate("update")
	if err != nil {
		responses.ERROR(w, http.StatusUnprocessableEntity, err)
		return
	}
	updatedUser, err := user.UpdateAUser(server.DB, uint32(uid))
	if err != nil {
		formattedError := formaterror.FormatError(err.Error())
		responses.ERROR(w, http.StatusInternalServerError, formattedError)
		return
	}
	responses.JSON(w, http.StatusOK, updatedUser)
}

//		DELETE USERS
func (server *Server) DeleteUser(w http.ResponseWriter, r *http.Request) {

	vars := mux.Vars(r)

	user := models.User{}

	uid, err := strconv.ParseUint(vars["id"], 10, 32)
	if err != nil {
		responses.ERROR(w, http.StatusBadRequest, err)
		return
	}
	 tokenID, err := auth.ExtractTokenID(r)
	 if err != nil {
	 	responses.ERROR(w, http.StatusUnauthorized, errors.New("Unauthorized"))
	 	return
	 }
	 if tokenID != 0 && tokenID != uint32(uid) {
	 	responses.ERROR(w, http.StatusUnauthorized, errors.New(http.StatusText(http.StatusUnauthorized)))
	 	return
	 }
	_, err = user.DeleteAUser(server.DB, uint32(uid))
	if err != nil {
		responses.ERROR(w, http.StatusInternalServerError, err)
		return
	}
	w.Header().Set("Entity", fmt.Sprintf("%d", uid))
	responses.JSON(w, http.StatusNoContent, "")
}

SELANJUT NYA KITA AKAN MEMBUAT FILE DI DALAM FOLDER SEED bernama seed.go untuk mengkonekan file server.go agar terkonek ke DB atau untuk ngeload data dari databases ketika sudah dibuatkan model

package seed

import (
	"log"

	"github.com/cepot-blip/fullstack/api/models"
	"github.com/jinzhu/gorm"
)

var users = []models.User{
	models.User{
		Nickname: "pak tarno rasa leci",
		Email:    "[email protected]",
		Password: "password",
	},
	models.User{
		Nickname: "udin rasa kecap",
		Email:    "[email protected]",
		Password: "password",
	},
}

var posts = []models.Post{
	models.Post{
		Title:   "Title 1",
		Content: "tutorial muka glow up",
	},
	models.Post{
		Title:   "Title 2",
		Content: "tutorial muka glow down",
	},
}

func Load(db *gorm.DB) {

	err := db.Debug().DropTableIfExists(&models.User{}).Error
	if err != nil {
		log.Fatalf("gagal drop table: %v", err)
	}
	err = db.Debug().AutoMigrate(&models.User{}).Error
	if err != nil {
		log.Fatalf("gagal migrasi table: %v", err)
	}

	for i, _ := range users {
		err = db.Debug().Model(&models.User{}).Create(&users[i]).Error
		if err != nil {
			log.Fatalf("gagal membuat seed table users: %v", err)
		}
	}
}

SELANJUT NYA KITA AKAN MENGEDIT FILE server.go untuk mengkonekan file .env kita ke server.go

package api

import (
	"fmt"
	"log"
	"os"

	"github.com/cepot-blip/fullstack/api/controllers"
	"github.com/cepot-blip/fullstack/api/seed"
	"github.com/joho/godotenv"
)

var server = controllers.Server{}

func Run() {

	var err error
	err = godotenv.Load()
	if err != nil {
		log.Fatalf("Error dalam mendapatkan file env, not comming through %v", err)
	} else {
		fmt.Println("berhasil mendapatkan env")
	}

	server.Initialize(os.Getenv("DB_DRIVER"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_PORT"), os.Getenv("DB_HOST"), os.Getenv("DB_NAME"))

	seed.Load(server.DB)

	server.Run(":9000")

}

SELANJUT NYA KITA AKAN MEMBUAT FILE middlewares.go di dalam folder MIDDLEWARES untuk kepetingan routing pada END POINT,

package middlewares

import (
	"errors"
	"net/http"

	"github.com/cepot-blip/fullstack/api/auth"
	"github.com/cepot-blip/fullstack/api/responses"
)

func SetMiddlewareJSON(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		next(w, r)
	}
}

func SetMiddlewareAuthentication(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := (r)
		if err != nil {
			responses.ERROR(w, http.StatusUnauthorized, errors.New("Unauthorized"))
			return
		}
		next(w, r)
	}
}

SELANJUT NYA KITA AKAN MEMBUAT FILE routes.go untuk mebuat EndPoint dan memanggil model yang sudah kita buat ,

package controllers

import "github.com/cepot-blip/fullstack/api/middlewares"

func (s *Server) initializeRoutes() {

	// 		Home Routes
	s.Router.HandleFunc("/", middlewares.SetMiddlewareJSON(s.Home)).Methods("GET")

			// Login Routes USER
	s.Router.HandleFunc("/user_login", middlewares.SetMiddlewareJSON(s.Login)).Methods("POST")

	//		Users routess
	s.Router.HandleFunc("/users_create", middlewares.SetMiddlewareJSON(s.CreateUser)).Methods("POST")
	s.Router.HandleFunc("/users_read", middlewares.SetMiddlewareJSON(s.GetUsers)).Methods("GET")
	s.Router.HandleFunc("/users_update", middlewares.SetMiddlewareJSON(s.UpdateUser)).Methods("PUT")
	s.Router.HandleFunc("/users_delete/{id}", middlewares.SetMiddlewareJSON(s.DeleteUser)).Methods("DELETE")
}

SELANJUT LANGKAH TERAKHIR KITA AKAN MENGEDIT FILE main**.go untuk memanggil folder api kita untuk meruning struktur foldeer yang sudah kita buat,**

package main

import "github.com/cepot-blip/fullstack/api"

func main() {
	api.Run()
}

DAN UNTUK MERUNING SERVER KITA DI TERMINAL SILAHKAN KETIK SYNTAX BERIKUT,

  • _nodemon --exec go run _.go -signal SIGTERM*

// khusus pengguna nodemone lakukan syntax seperti diatas .

atau bisa juga dengan menjalankan file utama kita yatu

  • go run main.go

cepot-blip's Projects

docs icon docs

The open-source repo for docs.github.com

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.