从头开始使用 Golang 创建 Restful API

2025-05-26

从头开始使用 Golang 创建 Restful API

在本教程中,我们将通过一个示例,讲解如何使用 Golang 创建 Restful API。我们将介绍应用程序的初始设置、应用程序的架构、持久化处理、Docker 配置、HTTP 请求,甚至使用 vscode 进行调试。
本教程涵盖了从应用程序设置基础开始的诸多概念,因此我建议您查看下方的目录,确定如何学习本教程,并跳转到您更感兴趣的部分。

目录

为什么选择 Golang?

Golang 是一种极其轻量且快速的语言,可用于构建 CLI、DevOps 和 SRE、Web 开发、分布式服务、数据库实现等等。例如,Docker 和 Kubernetes 等应用程序都是用 Go 编写的。Go
以其性能、易学性(仅 25 个保留关键字)和改进现有代码库而闻名。

初始设置

创建文件夹并初始化应用程序



mkdir go-todo
cd go-todo
git init


Enter fullscreen mode Exit fullscreen mode

Go 有自己的包管理器,通过 go.mod 文件进行处理。要生成此基础文件,您可以运行以下命令:



go mod init <YOUR API NAME>


Enter fullscreen mode Exit fullscreen mode

你可以将 替换<YOUR API NAME>为你想要的任何应用名称,但请记住,惯例是使用你的应用可用的远程存储库的名称。
例如 github.com/pachecoio/go-todo



go mod init github.com/pachecoio/go-todo


Enter fullscreen mode Exit fullscreen mode

这一点尤其重要,因为这就是 Go 管理依赖项的方式。Go 不会将依赖项下载到你的本地项目目录,而是通过模块以分散的方式处理它们,我们将在接下来的章节中看到这一点。
如果你想深入了解这个概念,可以查看这里了解更多信息。

现在让我们为这个应用程序创建入口点文件。



touch main.go # Create a main.go file
code . # Open with vscode


Enter fullscreen mode Exit fullscreen mode

让我们包含一些样板代码来测试我们的初始设置:



package main

import "fmt"

func main() {
    fmt.Println("App running")
}


Enter fullscreen mode Exit fullscreen mode

您可以通过go main.go在终端中运行该应用程序,或者如果您使用 vscode,请按 F5 来运行该应用程序。

使用光纤创建您的第一个端点

在这个例子中,我们将使用 Fiber 来处理我们的 HTTP 请求并创建 API 的基本结构。

Fiber是一个使用 Fasthttp 构建的 Web 框架,其灵感来自 Express,旨在简化快速开发,零内存分配,并考虑性能。

首先,让我们获取光纤依赖项:



go get github.com/gofiber/fiber/v2


Enter fullscreen mode Exit fullscreen mode

现在我们可以将 fiber 模块导入到 main.go 文件中,并替换 main 函数内的代码。



package main

import (
    "log"

    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/cors"
)

func main() {
    app := fiber.New()
    app.Use(cors.New())

    api := app.Group("/api")

    // Test handler
    api.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("App running")
    })

    log.Fatal(app.Listen(":5000"))
}



Enter fullscreen mode Exit fullscreen mode

让我们来看看这里发生的事情:

首先,我们可以看到 Go 使用远程仓库的 URL 来导入依赖项/模块。
接下来,我们创建一个新的 Fiber 实例,其中包含一系列辅助函数,其主要功能是监听文件末尾指定的 5000 端口上的 HTTP 请求。
变量api是一个分组示例,可用于组织端点路径。在此示例中,我们使用了/api扩展名,这意味着我们访问端点的基本 URL 将是http://localhost:5000/api

此时,您可以再次运行该应用程序并尝试发出请求http://localhost:5000/api并接收以下响应:



App running


Enter fullscreen mode Exit fullscreen mode

您只需在浏览器中输入此 URL 即可进行测试,但我建议使用 HTTP 客户端(如Insomnia),因为本课的其余部分也需要它。

使用 Docker 运行

此时,我们已经可以运行应用程序并通过按 F5 使用 vscode 进行调试,但是随着依赖项和复杂性的增加,我们越来越难以获得可用于生产环境的应用程序,因为我们过于依赖本地设置(尤其是在开始添加数据库时)。
因此,一个好的解决方案是使用 Docker 将此应用程序容器化。

如果您不想使用 Docker 运行此操作,可以跳过此部分,但请记住,对于以下部分,您需要启动并运行 Postgres 数据库才能继续本教程。

设置 Dockerfile

第一步是创建 Dockerfile:



FROM golang:1.16-alpine AS base
WORKDIR /app

ENV GO111MODULE="on"
ENV GOOS="linux"
ENV CGO_ENABLED=0

RUN apk update \
    && apk add --no-cache \
    ca-certificates \
    curl \
    tzdata \
    git \
    && update-ca-certificates

FROM base AS dev
WORKDIR /app

RUN go get -u github.com/cosmtrek/air && go install github.com/go-delve/delve/cmd/dlv@latest
EXPOSE 5000
EXPOSE 2345

ENTRYPOINT ["air"]

FROM base AS builder
WORKDIR /app

COPY . /app
RUN go mod download \
    && go mod verify

RUN go build -o todo -a .

FROM alpine:latest as prod

COPY --from=builder /app/todo /usr/local/bin/todo
EXPOSE 5000

ENTRYPOINT ["/usr/local/bin/todo"]


Enter fullscreen mode Exit fullscreen mode

让我们来看看这里发生的事情:如果您已经熟悉 Docker,
可以跳过本节。

  • 首先,我们引用基础图像golang:1.16-alpine并设置一个名为的别名base
  • 第 4 至 6 行我们设置了一些基本环境变量:
    • GO111MODULE=on即使项目位于 GOPATH 中,也强制使用模块。需要 go.mod 才能运行。
    • GOOS=linux告诉编译器需要为哪个操作系统构建。
    • CGO_ENABLED=0允许程序使用静态链接的二进制文件进行构建,而无需任何外部依赖。
  • 第 8 至 14 行用于更新本地依赖项并安装 ca 证书(如果您打算使用 SSL/TLS,这一点很重要)
  • dev第 16 行我们根据base舞台创建一个舞台
  • 第 19 行我们安装air,用于热重载
  • 第 20 行和 21 行我们公开主端口 5000 和调试端口 2345。
  • 第 23 行是 dev 阶段的入口点,它基本上运行air

  • 接下来,从第 25 行到第 32 行,我们创建一个构建器阶段,用于为生产运行创建已编译的应用程序。在此步骤中,我们基本上复制所有代码,安装依赖项并编译应用程序。

  • 最后但并非最不重要的一点是,从第 34 行到第 39 行,我们设置了我们的产品配置,我们从构建器阶段提取编译后的代码,公开端口 5000 并为这个编译后的应用程序设置入口点。

包括用于热重载的空气

我们将使用air来实现 dev/debug 模式下的热重载。Dockerfile 已经考虑到了这一点,但我们仍然需要添加 air 的配置。
让我们创建一个名为 .air.toml 的新文件,并包含以下内容:



# Config file for [Air](https://github.com/cosmtrek/air) in TOML format

# Working directory
# . or absolute path, please note that the directories following must be under root.
root = "."
tmp_dir = "tmp"

[build]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -gcflags='all=-N -l' -o ./tmp/main ."
# Binary file yields from `cmd`.
bin = "tmp/main"
# Customize binary.
full_bin = "dlv exec --accept-multiclient --log --headless --continue --listen :2345 --api-version 2 ./tmp/main"
# Watch these filename extensions.
include_ext = ["go", "tpl", "tmpl", "html"]


Enter fullscreen mode Exit fullscreen mode

创建 docker-compose.yaml 配置

现在让我们添加一个 docker-compose.yaml 文件,以便我们可以轻松地持续构建、运行和定制这个应用程序。



# docker-compose.yaml
version: "3.7"

services:
  go-todo:
    container_name: go-todo
    image: thisk8brd/go-todo:dev
    build:
      context: .
      target: dev
    volumes:
      - .:/app
    ports:
      - "5000:5000"
      - "2345:2345"
    networks:
      - go-todo-network

networks:
  go-todo-network:
    name: go-todo-network


Enter fullscreen mode Exit fullscreen mode

太棒了!
现在,你应该能够使用 docker 运行,并获得与上一节相同的结果。
要运行该应用程序,请在终端中执行以下命令:



docker compose up


Enter fullscreen mode Exit fullscreen mode

再次访问http://localhost:5000/api,应用程序应该就能正常运行了。
你还可以修改文件,并在终端日志中看到热重载的反馈。

设置 VSCode 调试器

应用程序运行起来并查看日志已经很不错了,但如果我们能在容器内使用 vscode 调试功能就更好了。所以我们来添加一下:

在项目的根目录中,创建一个名为 .vscode 的文件夹,并在其中添加一个名为 launch.json 的文件,其内容如下:



// .vscode/launch/json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Delve into Docker",
      "type": "go",
      "request": "attach",
      "mode": "remote",
      "substitutePath": [
        {
          "from": "${workspaceFolder}/",
          "to": "/app/"
        }
      ],
      "port": 2345,
      "host": "127.0.0.1",
      "showLog": true,
      "apiVersion": 2,
      "trace": "verbose"
    }
  ]
}


Enter fullscreen mode Exit fullscreen mode

现在,由于您的应用程序已经在运行,您只需按 F5,VSCode 调试器就会附加到您正在运行的容器中。

您可以在端点处理程序内设置一个断点,尝试访问http://localhost:5000/api并查看其是否正常工作。

创建 Todo API

现在我们已经完成了初始设置并运行,可以开始实现 API 了。
在本例中,我们将创建一个 Todo API,其中包含 Todo 实体的基本 CRUD 操作。

包括数据库连接

我们将使用 Postgres 及其依赖项来建立gorm连接。
那就让我们开始编写代码吧!

首先,让我们创建一个.env包含我们将要使用的数据库的凭据的文件:



DB_HOST=go-todo-db
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=root
DB_NAME=go-todo-db


Enter fullscreen mode Exit fullscreen mode

go-todo现在,让我们使用以下代码在我们的服务之后立即将新服务包含到我们的 docker-compose.yaml 文件中:



  go-todo-db:
    container_name: go-todo-db
    image: postgres
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=${DB_NAME}
    volumes:
      - postgres-db:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    networks:
      - go-todo-network


Enter fullscreen mode Exit fullscreen mode

完成此服务后,让我们添加一个名为的新卷postgres-db



volumes:
  postgres-db:


Enter fullscreen mode Exit fullscreen mode

完整的 docker-compose.yaml 文件应如下所示:



version: "3.7"

services:
  go-todo:
    container_name: go-todo
    image: thisk8brd/go-todo:dev
    build:
      context: .
      target: dev
    volumes:
      - .:/app
    ports:
      - "5000:5000"
      - "2345:2345"
    networks:
      - go-todo-network

  go-todo-db:
    container_name: go-todo-db
    image: postgres
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=${DB_NAME}
    volumes:
      - postgres-db:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    networks:
      - go-todo-network

volumes:
  postgres-db:

networks:
  go-todo-network:
    name: go-todo-network


Enter fullscreen mode Exit fullscreen mode

创建一个名为 config 的文件夹,并config.go在其中添加一个文件,内容如下:



// config/config.go
package config

import (
    "fmt"
    "os"

    "github.com/joho/godotenv"
)

func Config(key string) string {
    err := godotenv.Load(".env")
    if err != nil {
        fmt.Print("Error loading .env file")
    }
    return os.Getenv(key)
}


Enter fullscreen mode Exit fullscreen mode

此代码将负责加载我们刚刚创建的 .env 内容。

创建一个名为 database 的模块,并在其中创建两个文件:connect.go 和 database.go。

在database.go文件中包含以下内容:



// database/database.go
package database

import "github.com/jinzhu/gorm"

// DB gorm connector
var DB *gorm.DB


Enter fullscreen mode Exit fullscreen mode

接下来,在 connect.go 文件中包含以下内容:



// database/connect.go
package database

import (
    "fmt"
    "strconv"

    "github.com/pachecoio/go-todo/config"

    "github.com/jinzhu/gorm"
    _ "github.com/lib/pq"
)

func ConnectDB() {
    var err error
    p := config.Config("DB_PORT")
    port, err := strconv.ParseUint(p, 10, 32)

    configData := fmt.Sprintf(
        "host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
        config.Config("DB_HOST"),
        port,
        config.Config("DB_USER"),
        config.Config("DB_PASSWORD"),
        config.Config("DB_NAME"),
    )

    DB, err = gorm.Open(
        "postgres",
        configData,
    )

    if err != nil {
        fmt.Println(
            err.Error(),
        )
        panic("failed to connect database")
    }

    fmt.Println("Connection Opened to Database")
}


Enter fullscreen mode Exit fullscreen mode

Todo模块实现

现在让我们创建我们的待办事项模块:

创建一个名为的新文件夹todo并创建一个名为 models.go 的文件:



// todo/models.go
package todo

import "github.com/jinzhu/gorm"

const (
    PENDING  = "pending"
    PROGRESS = "in_progress"
    DONE     = "done"
)

type Todo struct {
    gorm.Model
    Name        string `gorm:"Not Null" json:"name"`
    Description string `json:"description"`
    Status      string `gorm:"Not Null" json:"status"`
}


Enter fullscreen mode Exit fullscreen mode

接下来,让我们创建存储库,它将负责处理刚刚创建的 Todo 模型的所有数据库交互。
在 todo 模块中创建一个名为 repositories.go 的文件:



// todo/repositories.go
package todo

import (
    "errors"

    "github.com/jinzhu/gorm"
)

type TodoRepository struct {
    database *gorm.DB
}

func (repository *TodoRepository) FindAll() []Todo {
    var todos []Todo
    repository.database.Find(&todos)
    return todos
}

func (repository *TodoRepository) Find(id int) (Todo, error) {
    var todo Todo
    err := repository.database.Find(&todo, id).Error
    if todo.Name == "" {
        err = errors.New("Todo not found")
    }
    return todo, err
}

func (repository *TodoRepository) Create(todo Todo) (Todo, error) {
    err := repository.database.Create(&todo).Error
    if err != nil {
        return todo, err
    }

    return todo, nil
}

func (repository *TodoRepository) Save(user Todo) (Todo, error) {
    err := repository.database.Save(user).Error
    return user, err
}

func (repository *TodoRepository) Delete(id int) int64 {
    count := repository.database.Delete(&Todo{}, id).RowsAffected
    return count
}

func NewTodoRepository(database *gorm.DB) *TodoRepository {
    return &TodoRepository{
        database: database,
    }
}


Enter fullscreen mode Exit fullscreen mode

此存储库文件提供了此待办事项模型的基本 CRUD 操作,以及一个用于实例化的辅助函数。接下来我们将介绍如何使用它。

现在让我们创建端点处理程序。
在 todo 模块中创建一个名为 handlers.go 的新文件。



// todo/handlers.go
package todo

import (
    "strconv"

    "github.com/gofiber/fiber/v2"
    "github.com/jinzhu/gorm"
)

type TodoHandler struct {
    repository *TodoRepository
}

func (handler *TodoHandler) GetAll(c *fiber.Ctx) error {
    var todos []Todo = handler.repository.FindAll()
    return c.JSON(todos)
}

func (handler *TodoHandler) Get(c *fiber.Ctx) error {
    id, err := strconv.Atoi(c.Params("id"))
    todo, err := handler.repository.Find(id)

    if err != nil {
        return c.Status(404).JSON(fiber.Map{
            "status": 404,
            "error":  err,
        })
    }

    return c.JSON(todo)
}

func (handler *TodoHandler) Create(c *fiber.Ctx) error {
    data := new(Todo)

    if err := c.BodyParser(data); err != nil {
        return c.Status(500).JSON(fiber.Map{"status": "error", "message": "Review your input", "error": err})
    }

    item, err := handler.repository.Create(*data)

    if err != nil {
        return c.Status(400).JSON(fiber.Map{
            "status":  400,
            "message": "Failed creating item",
            "error":   err,
        })
    }

    return c.JSON(item)
}

func (handler *TodoHandler) Update(c *fiber.Ctx) error {
    id, err := strconv.Atoi(c.Params("id"))

    if err != nil {
        return c.Status(400).JSON(fiber.Map{
            "status":  400,
            "message": "Item not found",
            "error":   err,
        })
    }

    todo, err := handler.repository.Find(id)

    if err != nil {
        return c.Status(404).JSON(fiber.Map{
            "message": "Item not found",
        })
    }

    todoData := new(Todo)

    if err := c.BodyParser(todoData); err != nil {
        return c.Status(400).JSON(fiber.Map{"status": "error", "message": "Review your input", "data": err})
    }

    todo.Name = todoData.Name
    todo.Description = todoData.Description
    todo.Status = todoData.Status

    item, err := handler.repository.Save(todo)

    if err != nil {
        return c.Status(400).JSON(fiber.Map{
            "message": "Error updating todo",
            "error":   err,
        })
    }

    return c.JSON(item)
}

func (handler *TodoHandler) Delete(c *fiber.Ctx) error {
    id, err := strconv.Atoi(c.Params("id"))
    if err != nil {
        return c.Status(400).JSON(fiber.Map{
            "status":  400,
            "message": "Failed deleting todo",
            "err":     err,
        })
    }
    RowsAffected := handler.repository.Delete(id)
    statusCode := 204
    if RowsAffected == 0 {
        statusCode = 400
    }
    return c.Status(statusCode).JSON(nil)
}

func NewTodoHandler(repository *TodoRepository) *TodoHandler {
    return &TodoHandler{
        repository: repository,
    }
}

func Register(router fiber.Router, database *gorm.DB) {
    database.AutoMigrate(&Todo{})
    todoRepository := NewTodoRepository(database)
    todoHandler := NewTodoHandler(todoRepository)

    movieRouter := router.Group("/todo")
    movieRouter.Get("/", todoHandler.GetAll)
    movieRouter.Get("/:id", todoHandler.Get)
    movieRouter.Put("/:id", todoHandler.Update)
    movieRouter.Post("/", todoHandler.Create)
    movieRouter.Delete("/:id", todoHandler.Delete)
}



Enter fullscreen mode Exit fullscreen mode

此文件负责处理 HTTP 请求。
我们提供了一组函数来获取所有 Todo、获取单个 Todo、更新、创建和删除 Todo。
我们还提供了一个助手来注册此处理程序,它还涵盖了自动迁移数据库的功能,因此我们无需自行创建表。

总结

现在在我们的 main.go 文件中,让我们连接到数据库并注册我们的待办事项处理程序:

在 CORS 定义之后立即初始化 DB 连接:



// main.go
// ...
    app.Use(cors.New())
    database.ConnectDB()
    defer database.DB.Close()


Enter fullscreen mode Exit fullscreen mode

删除我们现有的测试端点并注册新的待办事项处理程序:



    todo.Register(api, database.DB)


Enter fullscreen mode Exit fullscreen mode

完整的 main.go 文件应如下所示:



// main.go
package main

import (
    "log"

    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/cors"
    "github.com/pachecoio/go-todo/database"
    "github.com/pachecoio/go-todo/todo"
)

func main() {
    app := fiber.New()
    app.Use(cors.New())
    database.ConnectDB()
    defer database.DB.Close()

    api := app.Group("/api")
    todo.Register(api, database.DB)

    log.Fatal(app.Listen(":5000"))
}



Enter fullscreen mode Exit fullscreen mode


此时,我们可以再次运行应用程序并测试端点。运行以下命令,使用 docker 重建并运行应用程序:



docker compose up --build


Enter fullscreen mode Exit fullscreen mode

我们传递该--build标志是为了确保重新构建并安装刚刚添加的依赖项,但您不需要每次运行时都包含此标志。仅当您更改任何依赖项或 Dockerfile 本身时才需要包含它。

按 F5 连接 VSCode 调试器并测试应用程序!

您可以使用此 swagger文件来检查和测试我们刚刚为此应用创建的端点。
为了测试端点,我建议您下载一个 HTTP 客户端,例如Insomnia。请按照以下步骤进行测试:

失眠测试

创建新的设计文档

转到“设计”选项卡,复制 Swagger 内容并粘贴到编辑器中

转到调试部分并检查生成的端点

测试创建待办事项端点

测试 Todo 集合端点

测试获取单个待办事项端点

测试更新待办事项端点

测试删除待办事项端点

我鼓励您添加其他功能,例如按状态和关键字过滤待办事项、验证允许的状态等等。请在评论区分享您的成果!

希望本教程对您有所帮助,如果您有任何疑问或建议,欢迎随时提出!源代码可在此处
找到

玩得开心,注意安全,下次再见!

文章来源:https://dev.to/pacheco/create-a-restful-api-with-golang-from-scratch-42g2
PREV
为 Node.js API 设计更好的架构设置数据库创建控制器创建路由
NEXT
推出 Handsfree.js - 将手势、面部表情和姿势手势集成到您的前端🖐👀🖐