เว็บเฟรมเวิร์ค Gin

เว็บเฟรมเวิร์ค Gin ใช้ HttpRouter เวอร์ชันที่กำหนดเอง ซึ่งแตกต่างจากเฟรมเวิร์กเว็บ Go อื่น ๆ ซึ่งหมายความว่าสามารถนำทางผ่านเส้นทาง API ของคุณได้เร็วกว่าเฟรมเวิร์กส่วนใหญ่ที่มีอยู่ ผู้สร้างยังอ้างว่าสามารถทำงานได้เร็วกว่า Martini ถึง 40 เท่า


อย่างไรก็ตาม Gin เป็นไมโครเฟรมเวิร์กที่ไม่ได้มาพร้อมกับฟีเจอร์แฟนซีมากมายที่มาพร้อมในเฟรมเวิร์กนี้ มีเพียงเครื่องมือที่จำเป็นในการสร้าง API เช่น การกำหนดเส้นทาง การตรวจสอบแบบฟอร์ม ฯลฯ ดังนั้นสำหรับงานต่างๆ เช่น การตรวจสอบสิทธิ์ผู้ใช้ การอัปโหลดไฟล์ และการส่งอีเมล คุณต้องติดตั้งไลบรารีอื่นๆหรือดำเนินการด้วยตนเอง แต่ถ้าคุณมีความกระหายที่จะลงทุนระยะยาว คุณสามารถใช้ประโยชน์จากประสิทธิภาพและความยืดหยุ่นของ Gin ที่ไม่ธรรมดาได้จริงๆ


ออกแบบ REST API


ก่อนจะลงมือสร้าง API ก็ต้องออกแบบ API กันก่อน ในวันนี้เราจะสร้าง API พื้นฐานเบื้องต้น ดังนี้

POST /books สร้างข้อมูลใหม่
GET /books/1 ขอดูข้อมูลไอดีที่ 1
PATCH /books/1 แก้ไขข้อมูลไอดีที่ 1
DELETE /books/4 ลบข้อมูลไอดีที่ 4



ข้อกำหนดเบื้องต้น


ข้อกำหนดสำหรับบทความนี้คือ คุณต้องติดตั้ง Go บนคอมพิวเตอร์ของคุณตามบทความ ติดตั้ง Go และ ทดสอบ Hello World  มาก่อน


สร้าง Workspace สำหรับ Gin Framework


สร้างโฟลเดอร์ใหม่สำหรับการเรียนรู้ Gin Framework ชื่อ gin-framework ที่ GOPATH ในตัวอย่างคือ C:/User/ชื่อผู้ใช้/go/src/gin-framework

สร้างไฟล์ Go Modules ด้วยคำสั่ง

go mod init gin-framework


ติดตั้ง Gin Package


ติดตั้ง Gin Package ด้วยคำสั่ง go get ก็จะได้ Gin ใน GOPATH

go get -u github.com/gin-gonic/gin


โปรแกรมแรก กับ Gin Framework


เริ่มต้นด้วยการสร้างเซิร์ฟเวอร์ Hello World ภายในไฟล์ main.go

package main

import (
  "net/http"
  "github.com/gin-gonic/gin"
)

func main() {
  r := gin.Default()

  r.GET("/", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"data": "hello world"})    
  })

  r.Run()
}


ใน code ตัวอย่างนี้จะเป็นการสร้าง path / ซึ่งเป็น method GET โดยจะ response กลับมาเป็น json ที่มีข้อความ {“data”:”hello world”}

ก่อนอื่นเราต้องประกาศฟังก์ชันหลักที่จะถูกเรียกใช้เมื่อใดก็ตามที่เราเรียกใช้แอปพลิเคชันของเรา ภายในฟังก์ชันนี้ เราจะเริ่มต้นเราเตอร์ Gin ใหม่ภายในตัวแปร r เราใช้เราเตอร์เริ่มต้นเนื่องจาก Gin มีมิดเดิลแวร์ที่มีประโยชน์ซึ่งเราสามารถใช้เพื่อดีบักเซิร์ฟเวอร์ของเรา

ต่อไป เราจะกำหนดเส้นทาง GET ไปยัง / หากคุณเคยทำงานกับเฟรมเวิร์กอื่นๆ เช่น Express.js, Flask หรือ Sinatra คุณควรคุ้นเคยกับรูปแบบนี้

ในการกำหนดเส้นทาง เราต้องระบุสองสิ่ง: จุดปลายและตัวจัดการ ปลายทางคือเส้นทางที่ไคลเอ็นต์ต้องการดึงข้อมูล ตัวอย่างเช่น หากผู้ใช้ต้องการหยิบหนังสือทั้งหมดในร้านหนังสือของเรา พวกเขาจะดึงข้อมูลปลายทาง /books ในทางกลับกัน ตัวจัดการจะกำหนดวิธีที่เราให้ข้อมูลแก่ลูกค้า นี่คือที่ที่เราใส่ตรรกะทางธุรกิจของเรา เช่น การดึงข้อมูลจากฐานข้อมูล การตรวจสอบความถูกต้องของอินพุตของผู้ใช้ และอื่นๆ

เราส่งการตอบกลับไปยังไคลเอ็นต์ได้หลายประเภท แต่โดยทั่วไป RESTful API จะตอบกลับในรูปแบบ JSON ในการทำเช่นนั้นใน Gin เราสามารถใช้วิธี JSON ที่ให้มาจากบริบทคำขอ วิธีนี้ต้องใช้รหัสสถานะ HTTP และการตอบสนอง JSON เป็นพารามิเตอร์

สุดท้ายนี้ เราสามารถเรียกใช้เซิร์ฟเวอร์ของเราโดยเรียกใช้เมธอด Run ของอินสแตนซ์ Gin ของเรา

ทดสอบการเพื่อทดสอบ เราจะเริ่มต้นเซิร์ฟเวอร์ของเราโดยใช้คำสั่งด้านล่าง

go run main.go


เว็บเบราว์เซอร์ที่ใช้ทดสอบเลือกเป็น Firefox Browser แล้วติดตั้ง Extension ชื่อ SP REST JSON เพิ่มเข้าไป

ป้อน URL http://localhost:8080/ คุณจะเห็น {“data”:”hello world”} อยู่บนหน้าจอของคุณ


ติดตั้ง Gorm Package


ติดตั้ง Gorm Package ด้วยคำสั่ง go get ก็จะได้ Gorm ใน GOPATH

go get github.com/jinzhu/gorm


การตั้งค่าฐานข้อมูล


สิ่งต่อไปที่เราต้องทำคือสร้างโมเดลฐานข้อมูลของเรา

โมเดลคือคลาส (หรือโครงสร้างใน Go) ที่ช่วยให้เราสามารถสื่อสารกับตารางเฉพาะในฐานข้อมูลของเรา ใน Gorm เราสามารถสร้างแบบจำลองของเราโดยกำหนดโครงสร้าง Go โมเดลนี้จะมีคุณสมบัติที่แสดงฟิลด์ในตารางฐานข้อมูลของเรา เนื่องจากเรากำลังพยายามสร้าง API ร้านหนังสือ มาสร้างแบบจำลองหนังสือกัน

สร้างโฟลเดอร์ models แล้วสร้างไฟล์ book.go

package models

type Book struct {
  ID     uint   `json:"id" gorm:"primary_key"`
  Title  string `json:"title"`
  Author string `json:"author"`
}

รูปแบบหนังสือของเราค่อนข้างตรงไปตรงมา หนังสือแต่ละเล่มควรมีชื่อและชื่อผู้แต่งที่มีชนิดข้อมูลสตริง รวมถึง ID ซึ่งเป็นตัวเลขที่ไม่ซ้ำกันเพื่อแยกความแตกต่างของหนังสือแต่ละเล่มในฐานข้อมูลของเรา

เรายังระบุแท็กในแต่ละฟิลด์โดยใช้คำอธิบายประกอบแบบย้อนกลับ ซึ่งช่วยให้เราสามารถแมปแต่ละฟิลด์เป็นชื่อที่แตกต่างกันเมื่อเราส่งเป็นการตอบกลับ เนื่องจาก JSON และ Go มีรูปแบบการตั้งชื่อที่แตกต่างกัน

ในการจัดระเบียบโค้ดของเราเล็กน้อย เราสามารถใส่โค้ดนี้ในโมดูลแยกต่างหากที่เรียกว่า models

ต่อไป เราต้องสร้างฟังก์ชันยูทิลิตี้ที่เรียกว่า ConnectDatabase ซึ่งช่วยให้เราสร้างการเชื่อมต่อกับฐานข้อมูลและโอนย้าย schema ของโมเดลของเราได้ เราสามารถใส่สิ่งนี้ไว้ในไฟล์ setup.go ในโมดูล models ของเรา

package models

import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/sqlite"
)

var DB *gorm.DB

func ConnectDatabase() {
  database, err := gorm.Open("sqlite3", "test.db")

  if err != nil {
    panic("Failed to connect to database!")
  }

  database.AutoMigrate(&Book{})

  DB = database
}


ภายในฟังก์ชันนี้ เราสร้างการเชื่อมต่อใหม่ด้วยวิธีการ gorm.Open ที่นี่ เราระบุชนิดของฐานข้อมูลที่เราวางแผนจะใช้และวิธีการเข้าถึงฐานข้อมูล ปัจจุบัน Gorm รองรับฐานข้อมูล SQL สี่ประเภทเท่านั้น เพื่อจุดประสงค์ในการเรียนรู้ เราจะใช้ SQLite และจัดเก็บข้อมูลของเราไว้ในไฟล์ test.db


ในการเชื่อมต่อเซิร์ฟเวอร์ของเรากับฐานข้อมูล เราจำเป็นต้องนำเข้าไดรเวอร์ของฐานข้อมูล ซึ่งอยู่ภายในโมดูล github.com/jinzhu/gorm/dialects


ติดตั้ง github.com/jinzhu/gorm/dialects ด้วยคำสั่ง go get

go get github.com/jinzhu/gorm/dialects/sqlite@v1.9.16


เรายังต้องตรวจสอบว่าสร้างการเชื่อมต่อสำเร็จหรือไม่ หากไม่เป็นเช่นนั้น ระบบจะพิมพ์ข้อผิดพลาดไปที่คอนโซลและยุติเซิร์ฟเวอร์

ต่อไป เราย้ายสคีมาฐานข้อมูลโดยใช้ AutoMigrate อย่าลืมเรียกวิธีนี้ในแต่ละรุ่นที่คุณสร้างขึ้น

สุดท้าย เราเติมตัวแปร DB ด้วยอินสแตนซ์ฐานข้อมูลของเรา เราจะใช้ตัวแปรนี้ในตัวควบคุมของเราเพื่อเข้าถึงฐานข้อมูลของเรา


ใน main.go เราต้องเรียกใช้ฟังก์ชันต่อไปนี้ก่อนที่เราจะเรียกใช้แอปของเรา

package main

import (
  "github.com/gin-gonic/gin"

  "gin-framework/models" // new
)

func main() {
  r := gin.Default()

  models.ConnectDatabase() // new

  r.Run()
}


RESTful routes


สิ่งสุดท้ายที่เราต้องทำคือนำคอนโทรลเลอร์ของเราไปใช้ ในส่วนที่แล้ว เราได้เรียนรู้วิธีสร้างตัวจัดการเส้นทาง (เช่น controller) ในไฟล์ main.go ของเรา อย่างไรก็ตาม วิธีการนี้ทำให้โค้ดของเราดูแลรักษายากขึ้นมาก แทนที่จะทำเช่นนั้น เราสามารถใส่คอนโทรลเลอร์ของเราลงในโมดูลแยกต่างหากที่เรียกว่า controller

สร้างโฟลเดอร์ controllers แล้วสร้างไฟล์ books.go

package controllers

import (
  "net/http"

  "github.com/gin-gonic/gin"
  "gin-framework/models" 
)

// GET /books
// Get all books
func FindBooks(c *gin.Context) {
  var books []models.Book
  models.DB.Find(&books)

  c.JSON(http.StatusOK, gin.H{"data": books})
}


ที่นี่ เรามีฟังก์ชัน FindBooks ที่จะส่งคืนหนังสือทั้งหมดจากฐานข้อมูลของเรา ในการเข้าถึงโมเดลและอินสแตนซ์ DB ของเรา เราต้องนำเข้าโมดูลรุ่นที่ด้านบน

ต่อไป เราสามารถลงทะเบียนฟังก์ชันของเราเป็นตัวจัดการเส้นทางใน main.go

package main

import (
  "github.com/gin-gonic/gin"

  "gin-framework/models" 
  "gin-framework/controllers" // new
)

func main() {
  r := gin.Default()

  models.ConnectDatabase()

  r.GET("/books", controllers.FindBooks) // new

  r.Run()
}


ตอนนี้ มาเปิดเซิร์ฟเวอร์ของเรา go run main.go แล้วไปที่ /books

หากคุณเห็นผลลัพธ์เป็นอาร์เรย์ว่าง แสดงว่าแอปพลิเคชันของคุณกำลังทำงาน เราได้รับสิ่งนี้เพราะเรายังไม่ได้สร้างหนังสือ ในการทำเช่นนั้น มาสร้างตัวควบคุมหนังสือกัน

ในการสร้างหนังสือ เราต้องมีสคีมาที่สามารถตรวจสอบความถูกต้องของข้อมูลที่ผู้ใช้ป้อน เพื่อป้องกันไม่ให้เราได้รับข้อมูลที่ไม่ถูกต้อง

type CreateBookInput struct {
  Title  string `json:"title" binding:"required"`
  Author string `json:"author" binding:"required"`
}


สคีมาคล้ายกับโมเดลของเรามาก เราไม่ต้องการคุณสมบัติ ID เนื่องจากฐานข้อมูลจะถูกสร้างขึ้นโดยอัตโนมัติ

ตอนนี้เราสามารถใช้สคีมานั้นใน controller ของเราได้

// POST /books
// Create new book
func CreateBook(c *gin.Context) {
  // Validate input
  var input CreateBookInput
  if err := c.ShouldBindJSON(&input); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    return
  }

  // Create book
  book := models.Book{Title: input.Title, Author: input.Author}
  models.DB.Create(&book)

  c.JSON(http.StatusOK, gin.H{"data": book})
}


ขั้นแรก เราตรวจสอบความถูกต้องของเนื้อหาคำขอโดยใช้เมธอด ShouldBindJSON และส่งสคีมา หากข้อมูลไม่ถูกต้อง จะส่งคืนข้อผิดพลาด 400 ให้กับลูกค้าและแจ้งว่าช่องใดไม่ถูกต้อง มิฉะนั้น มันจะสร้างหนังสือเล่มใหม่ บันทึกลงในฐานข้อมูล และส่งคืนหนังสือ

ตอนนี้ เราสามารถเพิ่มตัวควบคุม CreateBook ใน main.go ได้แล้ว

func main() {
  // ...

  r.GET("/books", controllers.FindBooks)
  r.POST("/books", controllers.CreateBook) // new

  r.Run()
}


ดังนั้น หากเราพยายามส่งคำขอ POST ไปยัง /books endpoint ด้วยเนื้อหาคำขอนี้:

{
  "title": "Start with Why",
  "author": "Simon Sinek"
}


คำตอบควรมีลักษณะดังนี้:

{
  "data": {
    "id": 1,
    "title": "Start with Why",
    "author": "Simon Sinek"
  }
}


เราสร้างหนังสือเล่มแรกของเราสำเร็จแล้ว มาเพิ่มตัวควบคุมที่สามารถดึงหนังสือเล่มเดียวได้

// GET /books/:id
// Find a book
func FindBook(c *gin.Context) {  // Get model if exist
  var book models.Book

  if err := models.DB.Where("id = ?", c.Param("id")).First(&book).Error; err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"})
    return
  }

  c.JSON(http.StatusOK, gin.H{"data": book})
}



และนี่คือโค้ดทั้งหมด ของไฟล์ books.go

package controllers

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"gin-framework/models"
)

type CreateBookInput struct {
	Title  string `json:"title" binding:"required"`
	Author string `json:"author" binding:"required"`
}

type UpdateBookInput struct {
	Title  string `json:"title"`
	Author string `json:"author"`
}

// GET /books
// Find all books
func FindBooks(c *gin.Context) {
	var books []models.Book
	models.DB.Find(&books)

	c.JSON(http.StatusOK, gin.H{"data": books})
}

// GET /books/:id
// Find a book
func FindBook(c *gin.Context) {
	// Get model if exist
	var book models.Book
	if err := models.DB.Where("id = ?", c.Param("id")).First(&book).Error; err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"data": book})
}

// POST /books
// Create new book
func CreateBook(c *gin.Context) {
	// Validate input
	var input CreateBookInput
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Create book
	book := models.Book{Title: input.Title, Author: input.Author}
	models.DB.Create(&book)

	c.JSON(http.StatusOK, gin.H{"data": book})
}

// PATCH /books/:id
// Update a book
func UpdateBook(c *gin.Context) {
	// Get model if exist
	var book models.Book
	if err := models.DB.Where("id = ?", c.Param("id")).First(&book).Error; err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"})
		return
	}

	// Validate input
	var input UpdateBookInput
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	models.DB.Model(&book).Updates(input)

	c.JSON(http.StatusOK, gin.H{"data": book})
}

// DELETE /books/:id
// Delete a book
func DeleteBook(c *gin.Context) {
	// Get model if exist
	var book models.Book
	if err := models.DB.Where("id = ?", c.Param("id")).First(&book).Error; err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"})
		return
	}

	models.DB.Delete(&book)

	c.JSON(http.StatusOK, gin.H{"data": true})
}


ตัวควบคุม FindBook ของเราค่อนข้างคล้ายกับตัวควบคุม FindBooks อย่างไรก็ตาม เราได้รับหนังสือเล่มแรกที่ตรงกับ ID ที่เราได้รับจากพารามิเตอร์คำขอเท่านั้น เราต้องตรวจสอบด้วยว่าหนังสือเล่มนี้มีอยู่หรือไม่โดยเพียงแค่ใส่ไว้ในคำสั่ง if

ต่อไป ลงทะเบียนใน main.go ของคุณ

package main

import (
	"gin-framework/controllers"
	"gin-framework/models"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	// Connect to database
	models.ConnectDatabase()

	// Routes
	r.GET("/books", controllers.FindBooks)
	r.GET("/books/:id", controllers.FindBook)
	r.POST("/books", controllers.CreateBook)
	r.PATCH("/books/:id", controllers.UpdateBook)
	r.DELETE("/books/:id", controllers.DeleteBook)

	// Run the server
	r.Run()
}


ในการรับพารามิเตอร์ id เราจำเป็นต้องระบุจากเส้นทางที่แสดงด้านบน

เรียกใช้เซิร์ฟเวอร์และดึงข้อมูล /books/1 เพื่อรับหนังสือที่เราเพิ่งสร้างขึ้น

ทดสอบการเพิ่ม-ลบ-แก้ไข ข้อมูล ด้วย Insomnia


Insomnia เป็นโปรแกรมที่ขาดไม่ได้เลยสำหรับการทำ API , Insomnia คือ โปรแกรมเอาไว้สำหรับทดลองยิง request ไปยัง Service หรือเว็บไซต์ต่างๆ


Download ได้ที่  https://insomnia.rest/download

การใช้งานเบื้องต้น https://www.youtube.com/watch?v=WhCRjd043gE


เพิ่มข้อมูล (CreateBook)


ทดสอบเพิ่มข้อมูลด้วยโค้ดด้านล่าง เลือก POST ด้านล่างเลือกเป็น JSON แล้วป้อน URL http://localhost:8080/books กดปุ่ม Send

{
  "title": "Start with Why",
  "author": "Simon Sinek"
}


คุณจะเห็นข้อมูลที่ 1 เพิ่มที่ช่อง Preview ด้านขวา


ไปที่เว็บบราวเซอร์ ป้อน URL http://localhost:8080/books จะแสดงข้อมูลที่เราเพิ่มเข้าไป


ทดสอบเพิ่มอีก 4 ข้อมูล

{
  "title": "Go Web Programming",
  "author": "Sau Sheong Chang"
}

{
  "title": "Go in Action",
  "author": "William Kennedy"
}

{
  "title": "Black Hat Go",
  "author": "Tom Steele"
}

{
  "title": "Learning Go Programming",
  "author": "Vladimir Vivien"
}


ไปที่เว็บบราวเซอร์ ป้อน URL http://localhost:8080/books จะแสดงข้อมูล 1-5 ข้อมูล ที่เราเพิ่มเข้าไป


คิวรี่สตริง


ไปที่เว็บบราวเซอร์ เช่นป้อน URL http://localhost:8080/books/2 จะแสดงข้อมูล id ที่ 2


แก้ไขข้อมูล (UpdateBook)


ไปที่โปรแกรม Insomnia เลือก PATCH ด้านล่างเลือกเป็น JSON แล้วป้อน URL http://localhost:8080/books/3 กดปุ่ม Send

{
  "title": "Level Up Your Web Apps With Go",
  "author": "Mal Curtis"
}


ไปที่เว็บบราวเซอร์ เช่นป้อน URL http://localhost:8080/books/3 จะแสดงข้อมูล id ที่ 3 ที่แก้ไขแล้ว


ลบข้อมูล (DeleteBook)


เช่นต้องการลบข้อมูล id ที่ 4


ไปที่โปรแกรม Insomnia เลือก DELETE ด้านล่างเลือกเป็น Body แล้วป้อน URL http://localhost:8080/todos/4 กดปุ่ม Send


เมื่อไปที่เว็บบราวเซอร์ ป้อน URL http://localhost:8080/books/ ข้อมูล id ที่ 4 จะถูกลบออกไปแล้ว


เมื่อใช้ DB Browser (SQLite) เปิดดูข้อมูล ในไฟล์ test.db

credit : https://blog.logrocket.com/how-to-build-a-rest-api-with-golang-using-gin-and-gorm/

Leave a Reply

Your email address will not be published. Required fields are marked *