
使用這些基本 REST API 最佳實踐構建出色的 API
cd fitness-api
go mod init fitness-api
接下來,我們將使用命令來安裝 Echo 框架。我們將使用 flag 來更新依賴項,并使用另一個 flag 來下載依賴項而不進行安裝。命令為?go get -u -d
。
go get -u -d github.com/labstack/echo/v4
接下來,我們將安裝適用于 Go 的 PostgreSQL 驅動程序。
go get -u -d github.com/lib/pq
接下來,我們將安裝適用于 Go 的 Echo 中間件。
go get -u -d github.com/labstack/echo/v4/middleware
使用命令輸入 PostgreSQL shell。然后,我們將創建一個名為 using the query 的新數據庫。然后,我們將使用命令連接到數據庫。
psql
create database fitness;
\c fitness
我們將創建一個名為“users”的新表來存儲用戶信息。我們將使用該命令來創建這個新表,并設定一個名為“id”的主鍵,該主鍵將具備自動遞增的特性。此外,我們還將增加“name”、“email”以及“password”這幾個列,以便存儲用戶的具體信息。最后,為了記錄用戶的創建和更新時間,我們還將加入“created_at”和“updated_at”這兩個列。
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
然后,我們將創建一個新表,用于存儲用戶的健身數據,表名為 measurements
。我們將通過執行 SQL 命令來創建這個表,并且該表將包含一個外鍵,該外鍵關聯到 users
表。
CREATE TABLE measurements (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
weight FLOAT NOT NULL,
height FLOAT NOT NULL,
body_fat FLOAT NOT NULL,
created_at TIMESTAMP NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
);
要檢查表是否已成功創建,我們將使用命令列出數據庫中的所有表。
\dt
在項目的根目錄中,我們將創建一個名為?main.go
?的新文件。然后,在這個文件中,我們將導入所需的包,并初始化 Echo 框架。
package main
import (
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
e.Logger.Fatal(e.Start(":8080"))
}
這將在端口 8080 上啟動 Echo 服務器。現在,我們可以通過運行命令來測試服務器,然后在瀏覽器中導航到runhttp://localhost:8080
go run main.go
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.10.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
? http server started on [::]:8080
如您所見,我們收到了一個錯誤,這是因為我們還沒有定義任何路由。讓我們定義一個新路由來處理終端節點:“404 page not found”。
在項目根目錄中創建一個新目錄。然后,在該目錄內創建一個名為?cmd
?的新目錄。接著,在?cmd
?目錄下創建一個名為?handlers
?的新目錄。在?handlers
?目錄中,我們將創建一個名為?rootHandler.go
?的新文件。
在 rootHandler.go
文件中,我們將定義一個名為 handleHome
的新函數。
package handlers
import (
"net/http"
"github.com/labstack/echo/v4"
)
func Home(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
注意:
在將新包添加到項目之后,我們需要定義一個函數。這個函數將接收一個 echo.Context
作為參數,該參數包含了請求和響應對象。我們可以將這個函數命名為 Home
。
在 Home
函數中,我們將返回一個狀態代碼為 200(表示 OK)以及消息 “Hello, World!” 的響應。
接下來,我們將在 main.go
文件中導入所需的包,并將 Home
處理程序注冊到一個端點上。此外,我們還需要在 handlers
包(或相應的目錄結構下)中實現 Home
函數。
import (
"github.com/labstack/echo/v4"
"fitness-api/cmd/handlers"
)
func main() {
e := echo.New()
e.GET("/", handlers.Home)
e.Logger.Fatal(e.Start(":8080"))
}
運行命令時,我們將在瀏覽器上收到消息。
go run main.go
文件中發生了很多事情。讓我們來分析一下。首先,我們要導入 package,這是我們之前安裝的 Echo 框架。接下來,我們將導入之前創建的包。然后,我們使用 echo.New()
函數初始化 Echo 框架。之后,我們使用 e.GET()
函數將處理程序注冊到終端節點。最后,我們將使用 e.Start()
函數啟動 Echo 服務器。
創建一個名為?cmd
?的新目錄。然后,在該目錄下創建一個新的文件?user.go
?位于?models
?子目錄中。
此文件將包含模型。模型將具有 UserID
、Name
、Email
、Password
、CreatedAt
和 UpdatedAt
字段。
測量模型將具有 ID
、UserId
、Weight
、Height
、BodyFat
和 Date
字段。
package models
import "time"
type User struct {
Id int json:"id"
Name string json:"name"
Email string json:"email"
Password string json:"password"
CreatedAt time.Time json:"created_at"
UpdatedAt time.Time json:"updated_at"
}
type Measurements struct {
Id int json:"id"
UserId int json:"user_id"
Weight float64 json:"weight"
Height float64 json:"height"
BodyFat float64 json:"body_fat"
Created_at time.Time json:"created_at"
}
這些結構體將用于將數據庫表映射到 Go 結構體。將它們視為數據庫表的 Golang 表示形式。
使用命令安裝軟件包。
go get github.com/joho/godotenv
go mod tidy
在項目的根目錄中,我們將創建一個名為?.env
?的新文件(或者如果您希望將文件放在特定的子目錄下,如?fitness-api/
,則文件路徑為?fitness-api/.env
)。然后,我們將數據庫連接字符串添加到這個?.env
?文件中。
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=fitness
在 cmd
目錄中,我們將創建一個名為 storage
的新文件夾。接著,在這個 storage
文件夾內,我們將創建一個名為 db.go
的新文件。
在 db.go
文件中,我們將定義一個名為 Connect
的新函數,該函數用于連接到數據庫。需要注意的是,如果項目有一個特定的子目錄結構,例如 fitness-api/
,并且我們遵循這個結構,那么 db.go
文件的完整路徑將是 fitness-api/cmd/storage/db.go
。
package storage
import (
"database/sql"
"fmt"
"log"
"os"
"github.com/joho/godotenv"
_ "github.com/lib/pq"
)
var db *sql.DB
func InitDB() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
dbHost := os.Getenv("DB_HOST")
dbPort := os.Getenv("DB_PORT")
dbUser := os.Getenv("DB_USER")
dbPass := os.Getenv("DB_PASSWORD")
dbName := os.Getenv("DB_NAME")
db, err = sql.Open("postgres", fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", dbHost, dbUser, dbPass, dbName, dbPort))
if err != nil {
panic(err.Error())
}
err = db.Ping()
if err != nil {
panic(err.Error())
}
fmt.Println("Successfully connected to database")
}
func GetDB() *sql.DB{
return db
}
在這個函數中,我們使用 sql.Open()
函數連接到數據庫。sql.Open()
函數采用兩個參數:第一個參數是數據庫驅動程序名稱,在本例中為 postgres
;第二個參數是數據庫連接字符串。數據庫連接字符串包含數據庫主機、端口、用戶名、密碼、數據庫名稱和其他可能的連接參數。我們正在使用 fmt.Sprintf()
函數來格式化數據庫連接字符串。然后,我們使用 db.Ping()
函數檢查數據庫連接是否成功。
接下來,我們將在?main.go
?文件中導入必要的包,并調用?InitDB()
?函數來初始化數據庫連接。
import (
"github.com/labstack/echo/v4"
"fitness-api/cmd/handlers"
"fitness-api/cmd/storage"
)
func main() {
e := echo.New()
e.GET("/", handlers.Home)
// Add this line
storage.InitDB()
//----------------
e.Logger.Fatal(e.Start(":8080"))
}
運行命令時,我們將在終端上收到消息go run .Successfully connected to database
。
為了創建用戶存儲庫,我們首先需要創建一個名為 cmd
的新目錄。然后,在這個 cmd
目錄下,我們將創建一個名為 repositories
的新子目錄。接著,在 repositories
子目錄中,我們將創建一個名為 userDb.go
的新文件。
在 userDb.go
文件中,我們將定義一個名為 CreateUser
的新函數,該函數的功能是在數據庫中創建一個新用戶。需要注意的是,如果項目遵循特定的子目錄結構,例如 fitness-api/
,那么 userDb.go
文件的完整路徑將是 fitness-api/cmd/repositories/userDb.go
。
package repositories
import (
"fitness-api/cmd/models"
"fitness-api/cmd/storage"
)
func CreateUser(user models.User) (models.User, error) {
db := storage.GetDB()
sqlStatement := INSERT INTO users (name, email, password) VALUES ($1, $2, $3) RETURNING id
err := db.QueryRow(sqlStatement, user.Name, user.Email, user.Password).Scan(&user.Id)
if err != nil {
return user, err
}
return user, nil
}
在這個函數中,我們首先使用?storage.GetDB()
?函數來獲取數據庫連接。接著,我們使用?db.QueryRow()
?函數執行 SQL 查詢。db.QueryRow()
?函數采用兩個參數:第一個參數是 SQL 查詢語句,第二個參數是與查詢相關的參數(如果有的話)。這個函數返回一個?sql.Row
?對象。然后,我們使用?sql.Row.Scan()
?函數掃描返回的行,并將值分配給相應的字段,例如?user.Id
。
在 cmd/handlers
目錄下創建一個新文件 handleUsers.go
。然后,在這個文件中,我們將定義一個名為 CreateUser
的新函數,該函數的目的是在數據庫中創建一個新用戶。
package handlers
import (
"fitness-api/cmd/models"
"fitness-api/cmd/repositories"
"net/http"
"github.com/labstack/echo/v4"
)
func CreateUser(c echo.Context) error {
user := models.User{}
c.Bind(&user)
newUser, err := repositories.CreateUser(user)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusCreated, newUser)
}
在這個函數中,我們使用 c.Bind()
函數將請求體綁定到變量上。隨后,我們調用 userrepositories.CreateUser()
函數在數據庫中嘗試創建新用戶。如果過程中發生錯誤,我們將返回一個帶有錯誤消息的狀態碼 500
(內部服務器錯誤)。如果用戶成功創建,我們將返回一個狀態碼 201
(已創建)以及包含已創建用戶信息的響應。
接下來,我們將在?main.go
?文件中導入必要的包,并調用?CreateUser()
?函數來處理用戶創建請求。
import (
"github.com/labstack/echo/v4"
"fitness-api/cmd/handlers"
"fitness-api/cmd/storage"
"fitness-api/cmd/repositories"
)
func main() {
e := echo.New()
e.GET("/", handlers.Home)
storage.InitDB()
// Add this line
e.POST("/users", handlers.CreateUser)
//----------------
e.Logger.Fatal(e.Start(":8080"))
}
運行命令時,我們將在終端上收到消息go run .Successfully connected to database
。
如果您使用的是 Postman 或類似的 API 測試平臺,則可以通過使用以下請求正文向終端節點發送 POST 請求來測試 API-http://localhost:8080/users
。
{
"name": "John Doe",
"email": "john@mail.com",
"password": "password"
}
如果成功創建用戶,您將收到以下響應。
{
"id": 1,
"name": "John Doe",
"email": "john@mail.com",
"password": "password",
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
}
在指定的目錄中,我們將創建一個名為 measurementsDb.go
的新文件。然后,在這個文件中,我們將定義一個名為 CreateMeasurement
的函數,該函數的功能是在數據庫中創建一個新的測量記錄。需要注意的是,如果項目遵循特定的子目錄結構,例如 fitness-api/
,并且我們在該結構下操作,那么 measurementsDb.go
文件的完整路徑將是 fitness-api/cmd/repositories/measurementsDb.go
。
package repositories
import (
"fitness-api/cmd/models"
"fitness-api/cmd/storage"
"time"
)
func CreateMeasurement(measurement models.Measurements) (models.Measurements, error) {
db := storage.GetDB()
sqlStatement := INSERT INTO measurements (user_id, weight, height, body_fat, created_at) VALUES ($1, $2, $3, $4, $5) RETURNING id
err := db.QueryRow(sqlStatement, measurement.UserId, measurement.Weight, measurement.Height, measurement.BodyFat, time.Now()).Scan(&measurement.Id)
if err != nil {
return measurement, err
}
return measurement, nil
}
在 fitness-api/cmd/handlers
目錄下創建一個新文件,命名為 handleMeasurements.go
。然后,在這個新文件中,我們將定義一個名為 CreateMeasurement
的函數,該函數負責在數據庫中創建一個新的測量記錄。
package handlers
import (
"fitness-api/cmd/models"
"fitness-api/cmd/repositories"
"net/http"
"github.com/labstack/echo/v4"
)
func CreateMeasurement(c echo.Context) error {
measurement := models.Measurements{}
c.Bind(&measurement)
newMeasurement, err := repositories.CreateMeasurement(measurement)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusCreated, newMeasurement)
}
在 main.go
文件中,我們將調用 CreateMeasurement
函數。
...
import (
"github.com/labstack/echo/v4"
"fitness-api/cmd/handlers"
"fitness-api/cmd/storage"
"fitness-api/cmd/repositories"
)
func main(){
...
e.POST("/measurements", handlers.CreateMeasurement)
...
}
在運行命令時,然后向終端節點發送一個 POST 請求,其中包含以下請求正文,go run .http://localhost:8080/measurements
。
{
"user_id": 1,
"weight": 80,
"height": 180,
"body_fat": 20
}
如果測量創建成功,您將收到以下響應。
{
"id": 1,
"user_id": 1,
"weight": 80,
"height": 180,
"body_fat": 20,
"created_at": "0001-01-01T00:00:00Z"
}
要在 usersDb.go
文件中更新用戶信息,我們需要創建一個新的函數來執行此操作。
func UpdateUser(user models.User, id int) (models.User, error) {
db := storage.GetDB()
sqlStatement := `
UPDATE users
SET name = $2, email = $3, password = $4, updated_at = $5
WHERE id = $1
RETURNING id`
err := db.QueryRow(sqlStatement, id, user.Name, user.Email, user.Password, time.Now()).Scan(&id)
if err != nil {
return models.User{}, err
}
user.Id = id
return user, nil
}
在 handleUsers.go
文件中,我們將創建一個新的函數 HandleUpdateUser
。
func HandleUpdateUser(c echo.Context) error {
id := c.Param("id")
idInt, err := strconv.Atoi(id)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
user := models.User{}
c.Bind(&user)
updatedUser, err := repositories.UpdateUser(user, idInt)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, updatedUser)
}
此函數將從 URL 中提取參數 id
,并將其轉換為整數類型,隨后使用這個整數 ID 來更新數據庫中的用戶信息。
在 fitness-api/main.go
文件中,我們將調用一個名為 handleUpdateUser
的函數來處理用戶更新的邏輯。
...
func main(){
...
e.PUT("/users/:id", handlers.handleUpdateUser)
...
}
選擇要更新的用戶,然后使用以下請求正文向終端節點發送 PUT 請求–http://localhost:8080/users/:id
。
{
"name": "Jane Wanjiru",
"email": "jane@mail.com",
"password": "34jlse9,3"
}
這將使用 URL 中指定的?id
?來更新用戶信息。
到目前為止,我們已經為用戶介紹了基本的 CRUD(創建、讀取、更新、刪除)操作。接下來,您可以嘗試為用戶實現其他 CRUD 操作,例如刪除用戶、通過 ID 獲取單個用戶的信息以及獲取所有用戶的信息。
在更新度量方面,為了更新測量記錄,我們需要在 measurementsDb.go
文件中創建一個新的函數 UpdateMeasurement
。需要注意的是,如果項目遵循特定的子目錄結構,例如 fitness-api/
,并且我們在該結構下的 cmd/repositories
目錄中操作,那么 measurementsDb.go
文件的完整路徑將是 fitness-api/cmd/repositories/measurementsDb.go
。
func UpdateMeasurement(measurement models.Measurements, id int) (models.Measurements, error) {
db := storage.GetDB()
sqlStatement := `
UPDATE measurements
SET weight = $2, height = $3, body_fat = $4, created_at = $5
WHERE id = $1
RETURNING id`
err := db.QueryRow(sqlStatement, id, measurement.Weight, measurement.Height, measurement.BodyFat, time.Now()).Scan(&id)
if err != nil {
return models.Measurements{}, err
}
measurement.Id = id
return measurement, nil
}
在 fitness-api/cmd/handlers/handleMeasurements.go
文件中,我們將創建一個新的函數,命名為 HandleUpdateMeasurement
。這個函數將被用來處理更新測量記錄的邏輯。
func HandleUpdateMeasurement(c echo.Context) error {
id := c.Param("id")
idInt, err := strconv.Atoi(id)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
measurement := models.Measurements{}
c.Bind(&measurement)
updatedMeasurement, err := repositories.UpdateMeasurement(measurement, idInt)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, updatedMeasurement)
}
在 main.go
文件中,我們將調用 HandleUpdateMeasurement
函數。
...
func main(){
...
e.PUT("/measurements/:id", handlers.HandleUpdateMeasurement)
...
}
選擇要更新的度量,然后使用以下請求正文向終端節點發送 PUT 請求–http://localhost:8080/measurements/:id
。
{
"weight": 80,
"height": 180,
"body_fat": 20
}
您可以繼續執行其他 CRUD 操作進行測量。
在 fitness-api/cmd/handlers
文件夾中,我們將創建一個新的文件,命名為 middleware.go
。這個文件將用于定義 Echo 的中間件,中間件是在請求被處理程序處理之前和響應被發送回客戶端之后運行的一段代碼。在這個例子中,我們計劃創建一個中間件來記錄請求的詳細信息。
package handlers
import (
"fmt"
"net/http"
"time"
"github.com/labstack/echo/v4"
)
func LogRequest(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
start := time.Now()
err := next(c)
stop := time.Now()
fmt.Printf("Request: %s %s %s %s\n", c.Request().Method, c.Request().URL, stop.Sub(start), c.Response().Status)
return err
}
}
在 main.go
文件中,我們將調用 LogRequest
函數。
...
func main(){
...
e.Use(handlers.LogRequest)
...
}
運行應用程序并向終端節點發送請求。您應該會在終端中看到請求詳細信息–http://localhost:8080/users
。
Request: GET /users 1.0001ms 200
LogRequest 是一個自定義中間件函數,它把函數作為參數。該函數是將在 middleware 函數之后調用的處理程序函數。通過傳遞參數來調用該函數。參數是包含 request 和 response 對象的 context 對象。
此外,Echo 框架本身也提供了一系列中間件,其中就包括用于記錄日志的?logger
?中間件。在?main.go
?文件中,我們可以添加這個?logger
?中間件來增強我們的應用。
...
import (
"github.com/labstack/echo/v4/middleware"
)
...
func main(){
...
e.Use(middleware.Logger())
...
}
該函數?Use
?將中間件函數列表作為參數,允許您根據需要添加任意數量的中間件函數。
接下來,我們考慮添加 CORs(跨源資源共享)中間件,以允許從前端發出請求。為了實現這一點,我們將使用 Echo 框架提供的 CORs 中間件功能。在 fitness-api/main.go
文件中,我們將進行相應的配置。
...
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"http://localhost:3000"},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
}))
...
這將使得我們的應用能夠接收來自前端(運行在端口3000上)的請求。為了允許來自所有來源的請求,我們可以在 fitness-api/main.go
文件中配置 AllowOrigins
。
...
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
}))
...
在本教程中,我們深入了解了 Echo 框架的基礎知識。通過實踐,我們創建了一個功能完備的簡單 API,該 API 支持對用戶和測量數據的創建、讀取、更新及刪除操作。此外,我們還探討了 Echo 框架中的中間件概念及其應用。
Echo 無疑是構建 API 的理想選擇,它輕量級且操作簡便。無論您計劃構建下一個 API 還是微服務,Echo 都能提供強大的支持。敬請期待我們的下一教程,屆時我們將詳細介紹 Docker 的基礎知識,并演示如何將我們的 API 進行容器化處理。
原文鏈接:https://www.kelche.co/blog/go/golang-echo-tutorial/