发布于 2026-01-06 0 阅读
0

使用 Go、Docker 和 Postgres 从零开始构建 REST API

使用 Go、Docker 和 Postgres 从零开始构建 REST API。

使用 Go 和 Docker 从零开始构建 REST API

原文发布于divrhino.com

在本教程中,我们将学习如何使用 Go 和 Docker 从零开始创建一个简单的问答类 REST API。我们将从一个空文件夹开始,逐步构建。我们无需预先在机器上安装 Go,这可以说是这种方法最大的优势。教程结束时,我们将拥有一个连接到Postgres数据库的Go Fiber应用。

先决条件

要跟随教程操作,您需要安装 Docker。您可以访问 Docker 的下载页面,找到适合您环境的版本。

创建新项目

在终端中,我们可以切换到项目所在的目录。在我的例子中,这是项目Sites文件夹,你的可能不同。然后为我们的 REST API 项目创建一个新目录。之后,我们可以立即切换到这个新目录。



mkdir divrhino-trivia
cd divrhino-trivia


Enter fullscreen mode Exit fullscreen mode

Docker 入门

我们的项目文件夹目前是空的。由于我们使用 Docker 从头开始​​创建一个应用程序,因此我们要添加的第一个文件是 `.docker.js` Dockerfile。然后我们还会添加一个 `.docker.js`docker-compose.yml文件,因为我们需要管理多个容器。



touch Dockerfile
touch docker-compose.yml



Enter fullscreen mode Exit fullscreen mode

Dockerfile

首先我们将看一下Dockerfile

首先,我们要开始构建自己的容器,使用FROM官方golang镜像。而且我们希望使用特定版本的镜像。在本教程中,我们将使用版本号1.19.0为 10 ...

我们在此步骤中唯一要做的另一件事就是指定工作目录。Docker 容器运行在 Linux 系统上,所以这里我们指定应用程序位于/usr/srcLinux 文件系统中一个名为 `<project_name>` 的项目文件夹内app



FROM golang:1.19.0

WORKDIR /usr/src/app



Enter fullscreen mode Exit fullscreen mode

我们将继续完善我们的方案Dockerfile,但这已经足够让我们起步了。接下来,让我们进行初始docker-compose.yml配置。

docker-compose.yml

如果我们的应用只需要一项服务,那么以上内容就足够了Dockerfile。但是,我们最终还会添加一项服务。因此,如果能有一个文件来帮助我们管理容器集合,Postgres那就方便多了。docker-compose.yml

我们首先要介绍的是一项web服务。之所以这样命名,是web因为这是我们存放Go FiberWeb 应用的容器。以下是对各个字段的简要说明:

字段名称 描述
build 这是服务 Dockerfile 的路径。我们使用点号 (.),因为我们 Web 服务的 Dockerfile 与 docker-compose.yml 文件位于同一文件夹中。
ports 在这里,我们将容器的端口映射到主机。我们的 Web 服务将运行在 3000 端口上。
volumes 卷用于持久化服务生成的数据。我们希望将 Web 服务数据持久化到项目目录中,即 /usr/src/app。


version: '3.8'

services:
  web:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/usr/src/app



Enter fullscreen mode Exit fullscreen mode

这样,我们就可以打开终端,使用以下命令运行我们的应用程序:



docker compose up


Enter fullscreen mode Exit fullscreen mode

因为这是我们第一次运行这项服务,所以这条命令会拉取我们需要的镜像。你会注意到控制台中的日志与我们的命令相对应Dockerfile。现在让我们终止容器,然后进去看看。

我们可以通过运行以下命令进入容器。这里我们表示要打开服务bash内部的容器web



docker compose run --service-ports web bash


Enter fullscreen mode Exit fullscreen mode

现在,在我们的容器内,我们可以像在其他终端一样运行命令。让我们检查一下Go版本:



go version


Enter fullscreen mode Exit fullscreen mode

我们已经成功搭建了开始开发所需的最基本的 Docker 环境Go。在本教程的下一部分,我们将安装该Go Fiber框架。

Dockerfile 与 docker-compose

Docker 与 Docker Compose

在我们继续之前,让我们稍微绕一下,简要讨论一下为什么我们既有 a 文件Dockerfile又有 adocker-compose.yml文件。

DockerfileDockerfile是一组用于设置容器的命令。有时,可以将其想象成在新电脑上配置特定技术开发所需的命令列表。在本教程中,我们可以将其视为在新电脑上配置 Go 开发所需的命令列表。一个项目可以包含一个或多个 Dockerfile。

docker-compose.yml文件是一个配置文件,用于管理我们所有的容器。正如前面提到的,一个项目可以包含一个或多个 Dockerfile,这意味着它可以由一个或多个容器组成。我们docker-compose.yml可以将该文件视为所有这些容器的统一项目管理器。

安装 Go Fiber

现在我们已经有了一个可正常运行的容器化 Go 环境,可以开始在其中安装必要的软件包了。我们决定Go Fiber使用 Go 框架,不过,您也可以用类似的方法安装其他软件包。

使用以下命令,我们可以进入我们服务的容器web



docker compose run --service-ports web bash



Enter fullscreen mode Exit fullscreen mode

在开始安装所有软件包之前,我们需要初始化 Go Modules 来管理我们的依赖项。

通常来说,最好使用项目下载地址的 URL 来命名你的项目。我将使用我的 GitHub 仓库 URL 作为项目名称。当然,你也可以将以下命令替换为你自己 GitHub 或网站的 URL。



go mod init github.com/divrhino/divrhino-trivia



Enter fullscreen mode Exit fullscreen mode

Go Modules 设置完成后,我们现在可以安装 Go Fiber 框架了:



go get github.com/gofiber/fiber/v2



Enter fullscreen mode Exit fullscreen mode

你好世界

Go FiberHello World的文档中提供了一个示例。我们可以以此为基础来开发我们自己的应用程序。在容器内,让我们创建cmd文件夹和main.go文件:



mkdir cmd
touch cmd/main.go


Enter fullscreen mode Exit fullscreen mode

然后我们可以将示例代码添加到cmd/main.go文件中。我们修改了字符串,但其余代码与 Go Fiber 文档中的示例相同。



package main

import "github.com/gofiber/fiber/v2"

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

    app.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Hello, Div Rhino!")
    })

    app.Listen(":3000")
}



Enter fullscreen mode Exit fullscreen mode

仍然在容器内,我们可以使用go run命令启动 Go Fiber Web 服务器并绑定到 localhost。



go run cmd/main.go -b 0.0.0.0



Enter fullscreen mode Exit fullscreen mode

我们可以在浏览器中访问http://localhost:3000/来查看我们的应用程序正在运行以及我们打印出来的字符串。

从主机启动您的应用程序

目前,每次启动 gofiber 应用时,我们都需要进入web服务容器运行命令。如果能直接从主机运行 Docker 化的应用就好了。

我们需要对我们的文件进行一些更改Dockerfile才能docker-compose.yml实现这一点。

更新 Dockerfile

在我们的配置中Dockerfile,我们将添加两行新代码。首先,我们将使用COPY指令将所有文件复制到容器的工作目录中。然后,我们将运行命令go mod tidy来安装和清理依赖项。



FROM golang:1.19.0

WORKDIR /usr/src/app

COPY . .
RUN go mod tidy



Enter fullscreen mode Exit fullscreen mode

docker-compose.yml

在我们的docker-compose.yml配置中,我们只需要添加要映射到的命令。您可能会注意到,它与我们在服务容器docker compose up中使用的命令相同。web



version: '3.8'

services:
  web:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/usr/src/app
    command: go run cmd/main.go -b 0.0.0.0


Enter fullscreen mode Exit fullscreen mode

如果容器仍在运行,我们可以将其关闭。然后在终端中,我们可以使用以下命令运行应用程序:



docker compose up



Enter fullscreen mode Exit fullscreen mode

我们可以打开浏览器查看应用是否正在运行。现在让我们更新cmd/main.go文件。刷新浏览器后,更改并没有立即生效。这是因为我们需要先重新构建应用才能看到更改。

热重装

如果我们有一种方法可以在每次修改代码后自动重新构建应用程序,那就太好了。我们可以使用一个名为 `returns` 的包air来帮助我们实现这一点。

我们需要将其作为 Docker 设置的一部分进行安装,所以让我们再次打开我们的文件Dockerfile,并添加RUN安装该air软件包的指令。

Dockerfile现在应该看起来像这样:



FROM golang:1.19.0

WORKDIR /usr/src/app

RUN go install github.com/cosmtrek/air@latest

COPY . .
RUN go mod tidy


Enter fullscreen mode Exit fullscreen mode

添加 .air.toml

我们还需要为该air软件包添加一个配置文件。首先,我们可以创建一个名为 的新点文件。.air.toml



touch .air.toml


Enter fullscreen mode Exit fullscreen mode

然后我们可以前往该air软件包的 GitHub 仓库,从那里复制示例配置文件。我们只需要修改其中的命令[build],使其指向我们的目录。我们的文件cmd就放在这里。main.go



[build]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -o ./tmp/main ./cmd"


Enter fullscreen mode Exit fullscreen mode

更新 docker-compose.yml 中的命令

然后我们需要修改文件中的命令,docker-compose.yml使其使用该命令air来运行我们的应用程序。

我们文件web中的服务docker-compose.yml目前应如下所示:



version: '3.8'

services:
  web:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/usr/src/app
    command: air ./cmd/main.go -b 0.0.0.0


Enter fullscreen mode Exit fullscreen mode

现在重建我们的容器



docker compose build


Enter fullscreen mode Exit fullscreen mode

然后运行该应用程序



docker compose up


Enter fullscreen mode Exit fullscreen mode

现在,如果我们对文件进行更改cmd/main.go,我们可以刷新浏览器并看到更改。

环境变量

将所有敏感密钥保存在一个.env文件中,而不是将其提交到版本控制系统中,这是一种良好的实践。我们可以使用docker-compose.yml该文件读取环境变量,而无需安装任何额外的软件包。在web服务配置中,我们可以添加env_file密钥并将其指向该.env文件。



version: '3.8'

services:
  web:
    build: .
    env_file:
      - .env
    ports:
      - "3000:3000"
    volumes:
            - .:/usr/src/app
    command: air ./cmd/main.go -b 0.0.0.0



Enter fullscreen mode Exit fullscreen mode

您可能已经注意到该文件尚不存在,所以让我们在项目根目录中创建它:



touch .env


Enter fullscreen mode Exit fullscreen mode

这就是使用环境变量所需的一切。

添加 Postgres

在本教程接下来的几节中,我们将设置Postgres我们选择的数据库。我们需要service为其设置第二个数据库,所以让我们直接回到docker-compose.yml文件并db:在下添加一个新键services



version: '3.8'

services:
  web:
    build: .
    env_file:
      - .env
    ports:
      - "3000:3000"
        volumes:
              - .:/usr/src/app
    command: air ./cmd/main.go -b 0.0.0.0
  db:


Enter fullscreen mode Exit fullscreen mode

然后,在这个db:键下,我们还需要添加几个字段。

字段名称 描述
image 我们将使用直接从 Docker Hub 获取的 postgres:alpine 镜像。由于我们不会添加任何额外的指令,因此不需要为数据库服务单独提供一个 Dockerfile 文件。
ports 在这里,我们将容器中的端口映射到主机。我们的数据库服务将运行在 5432 端口,这是 Postgres 的常用端口。
volumes 卷用于持久化服务生成的数据。我们希望将数据库服务持久化到 postgres-db:/var/lib/postgresql/data 目录下。


version: '3.8'

services:
  web:
    build: .
    env_file:
      - .env
    ports:
      - "3000:3000"
    volumes:
      - .:/usr/src/app
    command: air ./cmd/main.go -b 0.0.0.0
  db:
    image: postgres:alpine
    ports:
      - "5432:5432"
    volumes:
      - postgres-db:/var/lib/postgresql/data

volumes:
  postgres-db:


Enter fullscreen mode Exit fullscreen mode

命名卷

您可能已经注意到,我们添加了另一个volumes键,该键的字段为空postgres-db,并且单独占一行。然后,我们postgres-dbdb服务配置中使用它。这种类型的volumes配置被称为named volume.

命名卷即使在容器重启或移除后也能保留数据。其他容器也可以访问这些数据。实际卷的路径由 Docker 内部机制处理。以这种方式定义的卷需要手动移除。

对于数据库来说,这是有道理的,因为理想情况下,我们希望即使在我们关闭所有程序并睡觉后,数据也能持久保存。

数据库凭据

在接下来的 Postgres 相关工作中,我们需要将数据库凭据(例如DB_USERDB_PASSWORD)存储DB_NAME在安全的地方。我们不想将这些值推送到版本控制系统中,所以将它们保存在.env文件中。



DB_USER=divrhinotrivia
DB_PASSWORD=divrhinotrivia
DB_NAME=divrhinotrivia


Enter fullscreen mode Exit fullscreen mode

现在我们可以在docker-compose.yml文件中访问它们了:



version: '3.8'

services:
  web:
    build: .
    env_file:
      - .env
    ports:
      - "3000:3000"
    volumes:
      - .:/usr/src/app
    command: air ./cmd/main.go -b 0.0.0.0
  db:
    image: postgres:alpine
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=${DB_NAME}
    ports:
      - "5432:5432"
    volumes:
      - postgres-db:/var/lib/postgresql/data

volumes:
  postgres-db:



Enter fullscreen mode Exit fullscreen mode

现在我们已经配置好db服务以使用 Postgres,接下来需要开始与它通信。

使用 GORM 与数据库通信

对于小型应用程序,我们可以使用普通的 SQL 查询与数据库通信。但是,为了更深入地了解如何使用“模型”来表示数据库实体,让我们使用一个名为 GORM 的 ORM 库。

ORM(对象关系映射)是一种允许我们以面向对象的方式查询和操作数据库数据的技术。在我们的例子中,Go 结构体将作为“对象”来表示数据库实体。

要安装 GORM,我们将进入我们的web服务容器。



docker compose run --service-ports web bash


Enter fullscreen mode Exit fullscreen mode

然后运行以下命令



go get gorm.io/gorm


Enter fullscreen mode Exit fullscreen mode

趁此机会,我们也应该安装postgresGORM 的驱动程序。



go get gorm.io/driver/postgres


Enter fullscreen mode Exit fullscreen mode

现在我们准备开始建立 GORM 模型。

GORM模型

正如我们在本教程引言中提到的,我们正在构建一个问答应用。因此,我们需要将数据存储Facts在数据库中一个同名的表中(即一个facts表)。我们将创建一个 Go 结构体来表示我们的数据Facts,然后使用 GORM 将该结构体转换为数据库实体。 

首先,让我们models在项目根目录下创建一个文件夹。我们将把 GORM 模型存放在这里。



mkdir models


Enter fullscreen mode Exit fullscreen mode

让我们向文件夹中添加一个models.go文件models



touch models/models.go


Enter fullscreen mode Exit fullscreen mode

在内部models/models.go,我们添加Fact模型。一个 Fact 将包含一个 `Fact` 对象Question(类型为`Fact`)string和一个 `Fact`Answer对象(类型也为 `Fact` string)。在结构体的最顶部,我们将表明它是一个 `Fact` 对象gorm.Model。因此,我们还应该确保导入了 ` gormFact` 包。



package models

import "gorm.io/gorm"

type Fact struct {
    gorm.Model
    Question string
    Answer   string
}


Enter fullscreen mode Exit fullscreen mode

结构标签

结构体标签是附加到结构体字段上的小型元数据片段。它们用于向其他 Go 代码提供指令,告知如何使用结构体字段。

在以下代码片段中,我们使用json结构体标签中的关键字来描述我们希望与结构体的每个字段关联的相应 JSON 键。

我们所有处理 JSON 的 Go 代码都会看到这些结构体标签并理解它们的含义:

  • Question字段在 JSON 中由键表示question
  • 该字段在 JSON 中Answer由键表示。answer

JSON 键遵循一定的命名约定。例如,JSON 键通常使用小写字母,但在某些情况下也可能使用蛇形命名法(snake_case)。这就是为什么我们经常会在大量使用 JSON 的代码库(例如 API 和 Web 应用程序)中看到这种“映射”的原因。



package models

import "gorm.io/gorm"

type Fact struct {
    gorm.Model
    Question string `json:"question"`
    Answer   string `json:"answer"`
}


Enter fullscreen mode Exit fullscreen mode

现在我们再添加一些 GORM 使用的结构体标签。我们将使用gorm关键字为每个字段指定一些初始数据库规则。

我们向GORM传达以下信息:

  • 在数据库中,这两个question列都answer将是类型TEXT
  • 在数据库中,这两个列都不应该有NULL值。
  • 我们还为每一列设置了默认值,NULL以便在用户创建新对象时未提供默认值时返回错误。Fact


package models

import "gorm.io/gorm"

type Fact struct {
    gorm.Model
    Question string `json:"question" gorm:"text;not null;default:null`
    Answer   string `json:"answer" gorm:"text;not null;default:null`
}


Enter fullscreen mode Exit fullscreen mode

请注意结构体标签的语法。我们可以在同一个结构体标签内使用多个关键字类型,但所有内容都必须用反引号括起来。

现在我们可以继续进行下一步,建立数据库连接了。

创建数据库连接

我们需要建立数据库连接,以便进行读写操作Facts。首先,我们创建一个新database目录。



mkdir database


Enter fullscreen mode Exit fullscreen mode

然后database,我们将在该目录下创建一个新文件



touch database/database.go


Enter fullscreen mode Exit fullscreen mode

在文件中database/database.go,我们指明该文件属于某个database包。然后,我们导入 GORM 包,并设置一个名为 `<struct type>` 的自定义结构体类型Dbinstance来表示我们的数据库实例。

我们还将创建一个新的包级变量来保存全局数据库。该变量的名称为 `<variable_name>`,DB类型为 `<type>` Dbinstance。我们将其放在包级,是因为我们需要在应用程序的任何地方访问它。



package database

import "gorm.io/gorm"

type Dbinstance struct {
    Db *gorm.DB
}

var DB Dbinstance


Enter fullscreen mode Exit fullscreen mode

现在我们来创建一个名为 `connect` 的函数ConnectDb()。顾名思义,我们将使用这个函数将我们的应用程序连接到数据库。



package database

import "gorm.io/gorm"

type Dbinstance struct {
    Db *gorm.DB
}

var DB Dbinstance

func ConnectDb() {}


Enter fullscreen mode Exit fullscreen mode

在函数体内部ConnectDb(),我们将使用一个名为 `get()` 的 GORM 方法Open()。该方法gorm.Open()接受两个参数。第一个参数的类型为 `T` gorm.Dialector,第二个参数的类型为 `T`。gorm.Options



package database

import "gorm.io/gorm"

type Dbinstance struct {
    Db *gorm.DB
}

var DB Dbinstance

func ConnectDb() {
    gorm.Open(gorm_dialector, gorm_options)
}


Enter fullscreen mode Exit fullscreen mode

第一个参数:gorm_dialect

让我们开始准备第一个参数所需的内容,也就是方言器。由于我们使用的是postgres,因此需要导入驱动程序包。

然后我们将调用postgres.Open()接受一个参数的方法,该参数是一个DSN(数据源名称)字符串。

为了构建DSN字符串,我们需要导入 `string`fmt和 ` osstring` 包。我们将使用 ` os.Getenvstring` 方法访问之前在文件中设置的环境变量docker-compose.yml。然后,我们将使用 ` fmt.Sprintf()string` 方法将相关变量插入到字符串中。



package database

import (
    "fmt"
    "os"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

type Dbinstance struct {
    Db *gorm.DB
}

var DB Dbinstance

func ConnectDb() {
    dsn := fmt.Sprintf(
        "host=db user=%s password=%s dbname=%s port=5432 sslmode=disable TimeZone=Asia/Shanghai",
        os.Getenv("DB_USER"),
        os.Getenv("DB_PASSWORD"),
        os.Getenv("DB_NAME"),
    )

    gorm.Open(postgres.Open(dsn), gorm_options)
}


Enter fullscreen mode Exit fullscreen mode

第二个参数:gorm_options

第二个参数gorm.Open是一个 GORM 配置对象。在我们的配置中,我们设置要使用的日志记录器类型。此外,我们还需要记得导入相应的gorm/logger包。



package database

import (
    "fmt"
    "os"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

type Dbinstance struct {
    Db *gorm.DB
}

var DB Dbinstance

func ConnectDb() {
    dsn := fmt.Sprintf(
        "host=db user=%s password=%s dbname=%s port=5432 sslmode=disable TimeZone=Asia/Shanghai",
        os.Getenv("DB_USER"),
        os.Getenv("DB_PASSWORD"),
        os.Getenv("DB_NAME"),
    )

    gorm.Open(postgres.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })
}


Enter fullscreen mode Exit fullscreen mode

gorm.Open()方法会返回一个数据库和一个错误信息。所以在继续之前,我们先来快速处理一下错误。

如果这里出现错误,我们需要记录致命错误并退出。如果数据库无法连接,那就根本无法开始,所以我认为在这里使用 `--failure` 是可以接受的。此外,由于操作未成功完成,log.Fatal()我们也使用 `--exit` 退出代码。2



package database

import (
    "fmt"
    "log"
    "os"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

type Dbinstance struct {
    Db *gorm.DB
}

var DB Dbinstance

func ConnectDb() {
    dsn := fmt.Sprintf(
        "host=db user=%s password=%s dbname=%s port=5432 sslmode=disable TimeZone=Asia/Shanghai",
        os.Getenv("DB_USER"),
        os.Getenv("DB_PASSWORD"),
        os.Getenv("DB_NAME"),
    )

    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })

    if err != nil {
        log.Fatal("Failed to connect to database. \n", err)
        os.Exit(2)
    }
}


Enter fullscreen mode Exit fullscreen mode

但如果没有错误,我们会记录一条消息,表明我们已连接,并将 Logger 值设置为我们的db



package database

import (
    "fmt"
    "log"
    "os"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

type Dbinstance struct {
    Db *gorm.DB
}

var DB Dbinstance

func ConnectDb() {
    dsn := fmt.Sprintf(
        "host=db user=%s password=%s dbname=%s port=5432 sslmode=disable TimeZone=Asia/Shanghai",
        os.Getenv("DB_USER"),
        os.Getenv("DB_PASSWORD"),
        os.Getenv("DB_NAME"),
    )

    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })

    if err != nil {
        log.Fatal("Failed to connect to database. \n", err)
        os.Exit(1)
    }

    log.Println("connected")
    db.Logger = logger.Default.LogMode(logger.Info)
}


Enter fullscreen mode Exit fullscreen mode

接下来,我们要用它AutoMigrate来创建所需的表。我们将所有 GORM 模型传递给它AutoMigrate。在本教程中,我们只有一个 GORM 模型,即Facts模型本身。



package database

import (
    "fmt"
    "log"
    "os"

    "github.com/divrhino/divrhino-trivia/models"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

type Dbinstance struct {
    Db *gorm.DB
}

var DB Dbinstance

func ConnectDb() {
    dsn := fmt.Sprintf(
        "host=db user=%s password=%s dbname=%s port=5432 sslmode=disable TimeZone=Asia/Shanghai",
        os.Getenv("DB_USER"),
        os.Getenv("DB_PASSWORD"),
        os.Getenv("DB_NAME"),
    )

    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })

    if err != nil {
        log.Fatal("Failed to connect to database. \n", err)
        os.Exit(1)
    }

    log.Println("connected")
    db.Logger = logger.Default.LogMode(logger.Info)

    log.Println("running migrations")
    db.AutoMigrate(&models.Fact{})
}


Enter fullscreen mode Exit fullscreen mode

最后,我们将全局变量的值设置DB为我们刚刚建立的数据库。



package database

import (
    "fmt"
    "log"
    "os"

    "github.com/divrhino/divrhino-trivia/models"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

type Dbinstance struct {
    Db *gorm.DB
}

var DB Dbinstance

func ConnectDb() {
    dsn := fmt.Sprintf(
        "host=db user=%s password=%s dbname=%s port=5432 sslmode=disable TimeZone=Asia/Shanghai",
        os.Getenv("DB_USER"),
        os.Getenv("DB_PASSWORD"),
        os.Getenv("DB_NAME"),
    )

    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })

    if err != nil {
        log.Fatal("Failed to connect to database. \n", err)
        os.Exit(2)
    }

    log.Println("connected")
    db.Logger = logger.Default.LogMode(logger.Info)

    log.Println("running migrations")
    db.AutoMigrate(&models.Fact{})

    DB = Dbinstance{
        Db: db,
    }
}


Enter fullscreen mode Exit fullscreen mode

我们将打开数据库连接,func main()以便整个应用程序都可以访问数据库:



// cmd/main.go

package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/divrhino/divrhino-trivia/database"
)

func main() {
    database.ConnectDb()

    app := fiber.New()

    app.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Div Rhino Trivia App!")
    })

    app.Listen(":3000")
}


Enter fullscreen mode Exit fullscreen mode

路线和终点

我们的应用程序现在可以连接到数据库了。接下来我们可以设置一些接口。

要设置我们的第一个端点,我们可以进入我们的cmd/main.go文件。在这里,我们可以在创建setupRoutes()新应用之后立即调用该函数。gofiber



package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/divrhino/divrhino-trivia/database"
)

func main() {
    database.ConnectDb()
    app := fiber.New()

    setupRoutes(app)

    app.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Div Rhino Trivia App!")
    })

    app.Listen(":3000")
}


Enter fullscreen mode Exit fullscreen mode

这个setupRoutes()函数还不存在,所以我们现在就创建它。我们可以把这个函数放在一个单独的文件中,但它仍然属于这个main包。让我们创建一个新文件来存放所有的路由。



touch cmd/routes.go


Enter fullscreen mode Exit fullscreen mode

我们可以将现有路由迁移到该文件中cmd/routes.go并清理该文件。cmd/main.go



// cmd/routes.go

package main

import (
    "github.com/gofiber/fiber/v2"
)

func setupRoutes(app *fiber.App) {
    app.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Div Rhino Trivia App!")
    })

}


Enter fullscreen mode Exit fullscreen mode


// cmd/main.go

package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/divrhino/divrhino-trivia/database"
)

func main() {
    database.ConnectDb()
    app := fiber.New()

    setupRoutes(app)

    app.Listen(":3000")
}


Enter fullscreen mode Exit fullscreen mode

我们可以将处理程序代码移到一个单独的包中,从而进一步简化代码。

处理程序:家

为了保持条理清晰,我们可以创建一个新handlers文件夹来存放处理程序代码。



mkdir handlers


Enter fullscreen mode Exit fullscreen mode

然后为所有相关的处理程序创建一个新文件facts



touch mkdir handlers/facts.go


Enter fullscreen mode Exit fullscreen mode

我们可以将现有的处理器迁移到handlers/facts.go



package handlers

import "github.com/gofiber/fiber/v2"

func Home(c *fiber.Ctx) error {
    return c.SendString("Div Rhino Trivia App!")
}


Enter fullscreen mode Exit fullscreen mode

我们的routes.go文件应该如下所示:



package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/divrhino/divrhino-trivia/handlers"
)

func setupRoutes(app *fiber.App) {
    app.Get("/", handlers.Home)
}


Enter fullscreen mode Exit fullscreen mode

现在让我们打开 API 客户端来测试这个首页路由。本教程中使用的是Insomnia,但您可以使用任何您喜欢的客户端。我们应该会在响应中看到我们的字符串。

API 客户端中的主页路由端点

事实

现在我们已经了解了如何设置端点,接下来让我们添加一个可用于创建新事实的端点。它将发出一个 POST 请求。



package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/divrhino/divrhino-trivia/handlers"
)

func setupRoutes(app *fiber.App) {
    app.Get("/", handlers.Home)

    app.Post("/fact", handlers.CreateFact)
}


Enter fullscreen mode Exit fullscreen mode

处理handlers.CreateFact程序尚不存在,所以我们现在就创建它:



package handlers

import (
    "github.com/gofiber/fiber/v2"
    "github.com/divrhino/divrhino-trivia/database"
    "github.com/divrhino/divrhino-trivia/models"
)

func Home(c *fiber.Ctx) error {
    return c.SendString("Div Rhino Trivia App!")
}

func CreateFact(c *fiber.Ctx) error {
    fact := new(models.Fact)
    if err := c.BodyParser(fact); err != nil {
        return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
            "message": err.Error(),
        })
    }

    database.DB.Db.Create(&fact)

    return c.Status(200).JSON(fact)
}


Enter fullscreen mode Exit fullscreen mode

使用失眠症来测试此端点

在 API 客户端中创建事实

列表信息

既然我们可以创建新的事实,我们也应该有一种方法来列出所有的事实。让我们更新首页路径并进行相应调整。ListFacts



package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/divrhino/divrhino-trivia/handlers"
)

func setupRoutes(app *fiber.App) {
    app.Get("/", handlers.ListFacts)

    app.Post("/fact", handlers.CreateFact)
}


Enter fullscreen mode Exit fullscreen mode

然后我们也把主页处理器改成ListFacts



package handlers

import (
    "github.com/gofiber/fiber/v2"
    "github.com/divrhino/divrhino-trivia/database"
    "github.com/divrhino/divrhino-trivia/models"
)

func ListFacts(c *fiber.Ctx) error {
    facts := []models.Fact{}
    database.DB.Db.Find(&facts)

    return c.Status(200).JSON(facts)
}

func CreateFact(c *fiber.Ctx) error {
    fact := new(models.Fact)
    if err := c.BodyParser(fact); err != nil {
        return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
            "message": err.Error(),
        })
    }

    database.DB.Db.Create(&fact)

    return c.Status(200).JSON(fact)
}


Enter fullscreen mode Exit fullscreen mode

我们可以使用 Insomnia 来获取所有事实的列表。目前我们只有一条事实。让我们再添加一些事实,并获取包含所有新事实的列表。

在 API 客户端中列出所有新事实

结论

好了,这就是全部内容。在本教程中,我们学习了如何使用 Go 和 Docker 从零开始创建一个简单的问答应用。我们从一个空白文件夹开始,逐步构建了一个包含 Postgres 数据库的多容器应用。

如果您喜欢这篇文章并想了解更多内容,请考虑在 YouTube 上订阅 Div Rhino频道。

恭喜你,你做得太棒了!继续学习,继续编程。再见啦,<3

GitHub 标志 divrhino / divrhino-trivia

使用 Go、Docker 和 Postgres 从零开始创建一个简单的问答类 REST API。视频教程可在 Div Rhino 的 YouTube 频道上找到。






文章来源:https://dev.to/divrhino/build-a-rest-api-from-scratch-with-go-and-docker-3o54