所有文章 > API开发 > Golang Echo教程:PostgreSQL的REST API(通过)

Golang Echo教程:PostgreSQL的REST API(通过)

欢迎阅读本教程,了解如何在 Go 中使用 Echo 框架创建健身 APIEcho 是一个高性能的 Web 框架,专为使用 Go 编程语言构建 RESTful API 而设计。它以其速度和轻量级特性而闻名,使其成为 Web 开发的绝佳选择。在本教程中,我们将指导您完成使用 Echo 构建 RESTful API 的过程,以便在 PostgreSQL 数据库中存储和管理与健身相关的信息,例如体重、身高和体脂百分比。

在教程中,我们将深入探讨 Echo 的基本概念,涵盖路由、中间件以及 HTTP 请求和响应的处理。我们将演示如何执行 CRUD(创建、读取、更新、删除)操作,以在 API 中有效地管理用户数据。无论您是新手还是经验丰富的 Go 开发人员,本教程都将为您提供使用 Echo 框架构建强大的健身 API 的知识和技能。

现在,让我们开始使用 Echo 框架,共同创建一个既简单又强大的健身 API,帮助您开发用于健身跟踪和管理的强大 Web 应用程序。

先决条件

在我们开始之前,您需要在您的计算机上安装以下内容:

  • Go 1.16 或更高版本
  • PostgreSQL 13 或更高版本
  • 您偏好的文本编辑器或集成开发环境(IDE)

设置项目

首先,我们将为项目创建一个新目录并初始化一个新的 Go 模块。我们将使用该命令初始化一个新的 Go 模块,并使用该命令下载并安装所需的依赖项。

mkdir fitness-api
cd fitness-api
go mod init fitness-api

安装 Echo

接下来,我们将使用命令来安装 Echo 框架。我们将使用 flag 来更新依赖项,并使用另一个 flag 来下载依赖项而不进行安装。命令为 go get -u -d

go get -u -d github.com/labstack/echo/v4

安装 PostgreSQL 驱动程序

接下来,我们将安装适用于 Go 的 PostgreSQL 驱动程序。

go get -u -d github.com/lib/pq

安装 Echo 中间件

接下来,我们将安装适用于 Go 的 Echo 中间件。

go get -u -d github.com/labstack/echo/v4/middleware

创建数据库

使用命令输入 PostgreSQL shell。然后,我们将创建一个名为 using the query 的新数据库。然后,我们将使用命令连接到数据库。

psql
create database fitness;
\c fitness

创建 Users 表

我们将创建一个名为“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

创建 API

在项目的根目录中,我们将创建一个名为 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 子目录中。

此文件将包含模型。模型将具有 UserIDNameEmailPasswordCreatedAt 和 UpdatedAt 字段。

测量模型将具有 IDUserIdWeightHeightBodyFat 和 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 表示形式。

Dotenv for the database creatdetials

使用命令安装软件包。

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

CRUD 操作

创建用户存储库

为了创建用户存储库,我们首先需要创建一个名为 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

创建 User Handler

在 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"
}

创建 Measurements 存储库

在指定的目录中,我们将创建一个名为 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
}

创建 Measurements 处理程序

在 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)
}

创建 Measurements 路线

在 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 操作进行测量。

Echo 中间件

在 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 中间件

接下来,我们考虑添加 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/