เว็บเฟรมเวิร์ค 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/