【Go】BeegoでAPI構築してみる

# これはなに？

- Webフレームワーク[Beego](https://beego.vip/)を使ってAPIサーバを立ててみる記事
- 本記事では、BeegoインストールからbeeコマンドによるAPI構築まで試す

# 環境

- Go: 1.18.3
- Beego: 2.0.1
- Bee: 2.0.4

# 注意

2022年8月現在、beegoの公式Docsのドメインが以前の `beego.me` から `beego.vip` に変更になっているようなので注意。
以前のドメイン踏むと全く関係ないサイトに誘導されるので要注意。

[v2.0.0以前のREADMEのCommunity欄](https://github.com/beego/beego/tree/v2.0.0#community) を見ると以前のドメインとなっている。
[v2.0.3以降のREADMEのCommunity欄](https://github.com/beego/beego/tree/v2.0.3#community) では現在（2022年8月）のドメイン。


# 前準備

## Docker環境整備

```:ディレクトリ構成
.
├── api
│   └── docker
│       └── Dockerfile
└── docker-compose.yml
```

```Dockerfile:Dockerfile
FROM golang:1.18.3-alpine3.16

ENV ROOT /go
ENV CGO_ENABLED 0

WORKDIR ${ROOT}

RUN apk update && apk add --no-cache git
```

```yml:docker-compose.yml
version: '3'
services:
  api:
    container_name: practice-beego-api
    build:
      context: .
      dockerfile: ./api/docker/Dockerfile
    ports:
      - 18080:8080
    env_file: ./api/.env
    working_dir: /go
    volumes:
      - ./api:/go
    command: >
      sh
    tty: true
```

## コンテナ構築

```sh
# 構築
$ docker-compose up -d

# 確認
$ docker-compose ps
       Name          Command   State            Ports         
--------------------------------------------------------------
practice-beego-api   sh        Up      0.0.0.0:18080->8080/tcp
```

# Beego導入

## Beegoインストール

コンテナ入って作業開始

```sh
$ docker exec practice-beego-api
```

Beegoのインストール

```sh
# インストール
$ go install github.com/beego/bee/v2@latest

# 確認
$ bee version
2022/06/15 00:05:14.990 [D]  init global config instance failed. If you do not use this, just ignore it.  open conf/app.conf: no such file or directory
2022/06/15 00:05:15 INFO     ▶ 0001 Getting bee latest version...
2022/06/15 00:05:15 INFO     ▶ 0002 Your bee are up to date
______
| ___ \
| |_/ /  ___   ___
| ___ \ / _ \ / _ \
| |_/ /|  __/|  __/
\____/  \___| \___| v2.0.4

├── GoVersion : go1.18.3
├── GOOS      : linux
├── GOARCH    : amd64
├── NumCPU    : 6
├── GOPATH    : /go
├── GOROOT    : /usr/local/go
├── Compiler  : gc
└── Date      : Wednesday, 15 Jun 2022
```

## API作成

下記ドキュメントに従ってAPI作成

https://github.com/beego/bee/tree/v2.0.4#bee-api

```sh
$ bee api myapi
2022/06/15 00:11:13.922 [D]  init global config instance failed. If you do not use this, just ignore it.  open conf/app.conf: no such file or directory
______
| ___ \
| |_/ /  ___   ___
| ___ \ / _ \ / _ \
| |_/ /|  __/|  __/
\____/  \___| \___| v2.0.4
2022/06/15 00:11:13 INFO     ▶ 0001 Generate api project support go modules.
2022/06/15 00:11:13 INFO     ▶ 0002 Creating API...
        create   /go/myapi/go.mod
        create   /go/myapi
        create   /go/myapi/conf
        create   /go/myapi/controllers
        create   /go/myapi/tests
        create   /go/myapi/conf/app.conf
        create   /go/myapi/models
        create   /go/myapi/routers/
        create   /go/myapi/controllers/object.go
        create   /go/myapi/controllers/user.go
        create   /go/myapi/tests/default_test.go
        create   /go/myapi/routers/router.go
        create   /go/myapi/models/object.go
        create   /go/myapi/models/user.go
        create   /go/myapi/main.go
2022/06/15 00:11:14 SUCCESS  ▶ 0003 New API successfully created!
```

起動してみる

```sh
# ディレクトリ移動
$ cd myapi/
$ pwd
/go/myapi

# 起動
$ bee run
______
| ___ \
| |_/ /  ___   ___
| ___ \ / _ \ / _ \
| |_/ /|  __/|  __/
\____/  \___| \___| v2.0.4
2022/06/15 00:15:07 WARN     ▶ 0001 Running application outside of GOPATH
2022/06/15 00:15:07 INFO     ▶ 0002 Using 'myapi' as 'appname'
2022/06/15 00:15:07 INFO     ▶ 0003 Initializing watcher...
main.go:6:2: missing go.sum entry for module providing package github.com/beego/beego/v2/server/web (imported by myapi); to add:
        go get myapi
2022/06/15 00:15:07 ERROR    ▶ 0004 Failed to build the application: main.go:6:2: missing go.sum entry for module providing package github.com/beego/beego/v2/server/web (imported by myapi); to add:
        go get myapi
```

`go.sum` が存在しないと怒られた
エラー文見ると`go get myapi`コマンドを実行すると必要がありそうなので、プロセス中断してコマンド実行

```sh
# 指定コマンド実行
$ go get myapi

# go.sumが生成されていること確認
$ ls
conf         go.mod       main.go      routers
controllers  go.sum       models       tests
```

再度起動

```sh
$ bee run
______
| ___ \
| |_/ /  ___   ___
| ___ \ / _ \ / _ \
| |_/ /|  __/|  __/
\____/  \___| \___| v2.0.4
2022/06/15 00:18:29 WARN     ▶ 0001 Running application outside of GOPATH
2022/06/15 00:18:29 INFO     ▶ 0002 Using 'myapi' as 'appname'
2022/06/15 00:18:29 INFO     ▶ 0003 Initializing watcher...
# (実行結果中略)
2022/06/15 00:18:56 SUCCESS  ▶ 0004 Built Successfully!
2022/06/15 00:18:56 INFO     ▶ 0005 Restarting 'myapi'...
2022/06/15 00:18:56 SUCCESS  ▶ 0006 './myapi' is running...
2022/06/15 00:19:00.944 [I] [parser.go:413]  generate router from comments

2022/06/15 00:19:00.953 [I] [server.go:241]  http server Running on http://:8080
```

良さそうなので、ブラウザで確認
ホスト側の`18080`ポート開放しているのでそこで確認

![スクリーンショット 2022-06-15 0.21.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363036/133e4d11-7ae3-b016-7532-88a19c878caa.png)

良さそう
一応ログも確認

```sh
2022/06/15 00:21:11.733 [D] [router.go:955]  |     172.25.0.1| 404 |      419.1µs| nomatch| GET      /
```

404となっているので画面の表記と一致している
OK

## 少しコード覗いてみる

404画面は悲しいので200系統返ってこないか少し調査
まずはrouter見てみる

```go:routers/router.go
package routers

import (
	"myapi/controllers"

	beego "github.com/beego/beego/v2/server/web"
)

func init() {
	ns := beego.NewNamespace("/v1",
		beego.NSNamespace("/object",
			beego.NSInclude(
				&controllers.ObjectController{},
			),
		),
		beego.NSNamespace("/user",
			beego.NSInclude(
				&controllers.UserController{},
			),
		),
	)
	beego.AddNamespace(ns)
}
```

おそらく `/v1/object` や `/v1/user` のエンドポイントが用意されてそう
今回は `/v1/user` を深堀ってみる

routerからcontroller呼んでいるのでcontroller見てみる

```go:controllers/user.go
package controllers

import (
	"myapi/models"
	"encoding/json"

	beego "github.com/beego/beego/v2/server/web"
)

// Operations about Users
type UserController struct {
	beego.Controller
}

// (略)

// @Title GetAll
// @Description get all Users
// @Success 200 {object} models.User
// @router / [get]
func (u *UserController) GetAll() {
	users := models.GetAllUsers()
	u.Data["json"] = users
	u.ServeJSON()
}

// (略)
```

`@router / [get]` とあるので、 `/v1/user` にGETリクエスト投げれば `models.User` が返されそう
modelも見てみる

```go:models/user.go
package models

import (
	"errors"
	"strconv"
	"time"
)

var (
	UserList map[string]*User
)

func init() {
	UserList = make(map[string]*User)
	u := User{"user_11111", "astaxie", "11111", Profile{"male", 20, "Singapore", "astaxie@gmail.com"}}
	UserList["user_11111"] = &u
}

type User struct {
	Id       string
	Username string
	Password string
	Profile  Profile
}

type Profile struct {
	Gender  string
	Age     int
	Address string
	Email   string
}

// (略)

func GetAllUsers() map[string]*User {
	return UserList
}

// (略)
```

`GetAllUsers()` メソッド呼ぶと `UserList` が返されそう
`init()` 部分で `UserList` に初期値突っ込まれているので、その辺のデータが返されそう

ということで、 `/v1/user` にGETリクエスト送ってみる

GETリクエスト試したいのでシンプルに `http://localhost:18080/v1/user` にアクセスしてみる
下記内容が返されるので想定通り

![スクリーンショット 2022-06-17 16.21.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363036/f2d3ccf9-245a-42d0-2474-3a3e8bcbff34.png)

ログ↓

```
2022/06/17 16:18:58.652 [D] [router.go:955]  |   192.168.32.1| 200 |      231.6µs|   match| GET      /v1/user   r:/v1/user/
```

## gitignore設定

起動するたびに？コードに変更が入るたびに？内容変わってgit管理下に含めなくて良さそうなファイルをgitignoreに追加しておく
→`api/myapi/lastupdate.tmp`や`api/myapi/myapi`を不要と判断したが、公式ドキュメント確認できていないため自己責任で :bow: 

```:.gitignore
.env
api/pkg

# beego
api/bin/myapi
api/myapi/lastupdate.tmp
api/myapi/myapi
```

## Swagger導入

Swaggerも使えるようなので下記ドキュメントを参考に使える状態に持っていく

https://beego.vip/docs/advantage/docs.md#generate-documentation-automatically

何も下準備してないのでたぶん見れないが、`/swagger`でSwaggerドキュメント確認できるっぽいのでとりあえずアクセス

![スクリーンショット 2022-06-15 0.29.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363036/8a68a909-20a2-8f89-b997-92e9abba5cf5.png)

しっかり404

```sh
2022/06/15 00:29:38.858 [D] [router.go:955]  |     172.25.0.1| 404 |     1.6767ms| nomatch| GET      /swagger/
```

ドキュメントを参考にコマンド実行してみる
`bee run`コマンドのオプションで`-downdoc=true`と`-gendoc=true`をつければ良さそう

```sh
$ bee run -downdoc=true -gendoc=true
______
| ___ \
| |_/ /  ___   ___
| ___ \ / _ \ / _ \
| |_/ /|  __/|  __/
\____/  \___| \___| v2.0.4
2022/06/15 00:33:49 WARN     ▶ 0001 Running application outside of GOPATH
2022/06/15 00:33:49 INFO     ▶ 0002 Using 'myapi' as 'appname'
2022/06/15 00:33:49 INFO     ▶ 0003 Downloading 'https://codeload.github.com/beego/swagger/zip/refs/tags/v4.6.1' to 'swagger.zip'...
2022/06/15 00:33:51 SUCCESS  ▶ 0004 3065067 bytes downloaded!
2022/06/15 00:33:51 INFO     ▶ 0005 Unzipping 'swagger.zip'...
2022/06/15 00:33:52 SUCCESS  ▶ 0006 Done! Deleting 'swagger.zip'...
2022/06/15 00:33:52 INFO     ▶ 0007 Initializing watcher...
2022/06/15 00:33:58 INFO     ▶ 0008 Generating the docs...
2022/06/15 00:34:03 SUCCESS  ▶ 0009 Docs generated!
2022/06/15 00:34:10 SUCCESS  ▶ 0010 Built Successfully!
2022/06/15 00:34:10 INFO     ▶ 0011 Restarting 'myapi'...
2022/06/15 00:34:10 SUCCESS  ▶ 0012 './myapi' is running...
2022/06/15 00:34:10.401 [I] [parser.go:85]  /go/myapi/controllers no changed

2022/06/15 00:34:10.401 [I] [server.go:241]  http server Running on http://:8080
```

確認してみる

![スクリーンショット 2022-06-15 0.39.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363036/c1dfce14-6cfd-e6de-9697-f1c94913bdfe.png)

```
2022/06/15 00:39:13.542 [D] [router.go:955]  |     172.25.0.1| 200 |     5.6745ms|   match| GET      /swagger/

2022/06/15 00:39:13.562 [D] [router.go:955]  |     172.25.0.1| 200 |     9.7623ms|   match| GET      /swagger/swagger-ui.css

2022/06/15 00:39:13.574 [D] [router.go:955]  |     172.25.0.1| 200 |    15.9754ms|   match| GET      /swagger/swagger-ui-standalone-preset.js

2022/06/15 00:39:13.582 [D] [router.go:955]  |     172.25.0.1| 200 |    25.6023ms|   match| GET      /swagger/swagger-ui-bundle.js

2022/06/15 00:39:13.795 [D] [router.go:955]  |     172.25.0.1| 200 |     2.8292ms|   match| GET      /swagger/favicon-32x32.png
```

良さそう！

## テスト確認

`tests/default_test.go`ファイルが存在していたのでテスト実行してみる

```sh
go test -v ./...
# myapi/tests
tests/default_test.go:13:2: missing go.sum entry for module providing package github.com/smartystreets/goconvey/convey (imported by myapi/tests); to add:
        go get -t myapi/tests
FAIL    myapi/tests [setup failed]
?       myapi   [no test files]
?       myapi/controllers       [no test files]
?       myapi/models    [no test files]
?       myapi/routers   [no test files]
FAIL
```

失敗
どうやら`go get -t myapi/tests`コマンドを実行する必要がありそうなので実行

```
$ go get -t myapi/tests
```

再度テスト実行

```
go test -v ./...
?       myapi   [no test files]
?       myapi/controllers       [no test files]
?       myapi/models    [no test files]
?       myapi/routers   [no test files]
2022/06/15 00:56:20.122 [W]  init global config instance failed. If you donot use this, just ignore it.  open conf/app.conf: no such file or directory

=== RUN   TestGet
2022/06/15 00:56:20.131 [I] [default_test.go:28]  testing TestGet Code[%d]
%s 200 {
  "hjkhsbnmn123": {
    "ObjectId": "hjkhsbnmn123",
    "Score": 100,
    "PlayerName": "astaxie"
  },
  "mjjkxsxsaa23": {
    "ObjectId": "mjjkxsxsaa23",
    "Score": 101,
    "PlayerName": "someone"
  }
}


  Subject: Test Station Endpoint
 
    Status Code Should Be 200 ✔
    The Result Should Not Be Empty ✔


2 total assertions

--- PASS: TestGet (0.00s)
PASS
ok      myapi/tests     0.018s
```

OK