【Go入門】Beegoの主要機能を一通りさわってみた

本記事ではGoの人気なフレームワークの1つである[Beego](https://github.com/beego/beego)を用いていろんなタイプの簡単なアプリを作成し、その過程でBeegoの特徴を解説します。最後に使用感とBeegoが向いている開発について考察します。

# 対象読者
- Beegoとはなんぞやの人
- Beegoの主要機能を一通り理解したい人
- Beegoを軽く動かしてみたい人

# Beegoとは
[Beego](https://github.com/beego/beego)はGoで書かれたフルスタックのフレームワークです。

RESTful API、Webアプリ、バックエンドサービスなどのアプリケーションを迅速に作成することができます。

また、Beegoにはbeeというcliツールがあります。beeを用いることで、プロジェクトを生成したりホットリロード機能をオンにしたりと、より効率的にアプリケーションを作成することができます。


# 環境構築
## Beego
まずはインストール。以下のコマンドを実行します。
```shell
go get github.com/beego/beego/v2@latest
```
Beegoはこれで終わり。
## bee
cliツールも使えるようにしていきます。
まずは同様にインストール。これにより`<your_main_gopath>/bin`配下にbeeバイナリがインストールされます。
```shell
go install github.com/beego/bee/v2@latest
```


続いて、以下のコマンドを実行してbeeバイナリがあるディレクトリを環境変数に登録します。

```shell
export PATH=$PATH:<your_main_gopath>/bin
```

:::note info
**GOPATHの確認方法**
`your_main_gopath`の部分は以下のコマンドで確認できます。
```shell
$ go env GOPATH
```
:::

最後に、以下のコマンドにて現在のプロジェクトに依存関係を追加します。
```shell
go get github.com/beego/bee/v2
```

`bee version`を実行して以下のようになればOK。
```
$ bee version
2025/05/15 12:34:07.408 [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.3.0

├── GoVersion : go1.23.4
├── GOOS      : linux
├── GOARCH    : amd64
├── NumCPU    : 16
├── GOPATH    : 
├── GOROOT    : /usr/local/go
├── Compiler  : gc
└── Date      : Thursday, 15 May 2025
```

# 開発
## GETリクエストに「Hello World!」を返すだけのアプリ
まずはcliツールであるbeeを用いずに開発してみます。
GETリクエストを投げると「Hello World!」を返す簡単なアプリを作成します。

プロジェクト直下に`main.go`を作成し、以下の内容を記述します。
```main.go
package main

import (
	"log"

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

func logMiddleware(ctx *beecontext.Context) {
	log.Printf("Request: %s %s", ctx.Request.Method, ctx.Request.URL)
	log.Printf("IP: %s", ctx.Input.IP())
}

func main() {
	web.InsertFilter("/*", web.BeforeRouter, logMiddleware)
	web.Get("/", func(ctx *beecontext.Context) {
		ctx.Output.Body([]byte("Hello, World!"))
	})
	web.Run()
}

```
注目すべきはインスタンス生成がない部分です。

Goの他のフレームワークであるGinやEchoと違い、Beegoでは**デフォルトでサーバインスタンス[`web.BeeApp`](https://pkg.go.dev/github.com/beego/beego/v2@v2.3.7/server/web#BeeApp)がグローバルに用意されています。** 

そのため、1つのサーバを用いる場合はGinやEchoのように最初に`New()`を実行してサーバインスタンスを作成する必要がありません。

（よりコード量が少なくなるのでいいなと思いつつ、Beegoを触る前にGinやEchoを触っていた自分からするとちょっと違和感あった。）

ただ必要ないだけでGinやEchoの`New()`に相当する関数である[`web.NewHttpServer()`](https://pkg.go.dev/github.com/beego/beego/v2@v2.3.7/server/web#NewHttpSever)があります。

つまり
```go
web.Get("/", handler)
web.Run()
```
は、実際には以下のような操作をしています。
```go
server := web.BeeApp.Handlers
server.Get("/", handler)
web.BeeApp.Run()
```

その他の部分は他のフレームワークとよく似ています。
`Get()`でルートパスに対してハンドラを設定しています。

また、今回はルーティング前に送信元のIPアドレスをログとして表示するミドルウェア的な処理を挟むのに`InsertFiler()`を用いています。
Beegoではミドルウェア的な処理の実現に[`Filter`](https://pkg.go.dev/github.com/beego/beego/v2@v2.3.7/server/web#FilterFunc)を用いる方法と[`Middleware`](https://pkg.go.dev/github.com/beego/beego/v2@v2.3.7/server/web#MiddleWare)を用いる方法があります。

それぞれの定義は以下の通りです。
#### Filter
```go
type FilterFunc = HandleFunc

type HandleFunc func(ctx *beecontext.Context)
```

#### Milldeware
```go
type MiddleWare func(http.Handler) http.Handler
```

Filterの登録には`InsertFiler()`を用い、第一引数に与えたルートへのリクエストにのみ処理が実行されます。

一方でMiddlewareの登録は`Run()`を用います。（`InsertFiler("*")`でもできます。）これによりアプリ全体の共通処理として処理が登録されます。

| 種類         | 用途                                                                 | 特徴                                                                                   | 主な用途例                                   |
|--------------|----------------------------------------------------------------------|----------------------------------------------------------------------------------------|----------------------------------------------|
| **Filter**   | ルーティング前後やコントローラ実行前後など、リクエスト処理の特定のタイミングで処理を挟みたい場合に使う | URLパターンやタイミング（BeforeRouter, BeforeExec, AfterExecなど）を細かく指定できる<br><br>InsertFilterで登録し、FilterFunc型で実装<br><br>Beego独自のルーティングやコントローラの仕組みと密接に連携 | 認証・認可<br>パラメータのバリデーション<br>レスポンスの加工<br>特定のパスだけに適用したい処理 |
| **Middleware** | Goの標準的なhttp.Handlerチェーンとして、アプリ全体のリクエスト処理の前後に共通処理を挟みたい場合に使う | サーバ起動時（Runメソッド）にMiddleWare型で登録<br><br>すべてのリクエストに対して一律に適用される<br><br>Goの標準的なミドルウェアパターンと互換性が高い | ロギング<br>CORS対応<br>全体的なリクエスト・レスポンスのラッピング<br>サードパーティ製ミドルウェアの利用 |

使い分け方として**特定のURLやタイミングで処理を挟みたい場合はFilter**、**アプリ全体に共通の処理を挟みたい場合はMiddleware**を用いると覚えておけば問題ないかと思います。

テストしてみます。アプリを起動したのちにcurlコマンドでGETリクエストを投げてみます。
```shell
$ curl localhost:8080
Hello, World!
```
正しいレスポンスが返ってきました。

## MVCアプリ
続いてbeeを用いて簡単なMVCアプリを作成します。
具体的には/users/:idにGETリクエストを投げると該当するユーザの情報を含むHTMLを返すMVCアプリを作成します。


以下のコマンドを実行してプロジェクトを新規作成します。
```shell
bee new beegoapp
```
作成したプロジェクトにはすでにMVCアプリのひな型が作成されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3933650/f359b146-76db-49de-8df4-2c02bc3c038d.png)

次に、以下のコマンドを実行し、User Modelを作成します。
```shell
bee generate model User -fields="Name:string,Age:int,Email:string"
```
今回はユーザ情報として以下の情報を想定しています。
- 名前：Name（string）
- 年齢：Age（int）
- メールアドレス：Email（string）

`-fields`オプションを用いることで、自動でこれらのフィールドを持つモデルを作成してくれます。

今回は`GetUserById()`だけ用います。
DBは用いないのでダミーデータを返すように修正します。
```models/user.go
func GetUserById(id int64) (v *User, err error) {
	users := map[string]*User{
		"1":{Id: 1, Name: "John", Age: 30, Email: "john@example.com"},
		"2":{Id: 2, Name: "Bob", Age: 25, Email: "bob@example.com"},
	}
	key := fmt.Sprintf("%d", id)
	if user, ok := users[key]; ok {
		return user, nil
	}
	return nil, errors.New("User not found")
}
```

続いて、以下のコマンドを実行し、User Controllerを作成します。
```shell
bee generate controller User
```
`controllers`配下に`user.go`が作成されているはずです。便利ですね。

今回は`GetOne()`だけ用います。
以下のように修正します。
```controllers/user.go
func (c *UserController) GetOne() {
    idStr := c.Ctx.Input.Param(":id")
    id, err := strconv.ParseInt(idStr, 10, 64)
    if err != nil {
        c.CustomAbort(400, "Invalid user id")
        return
    }

    user, err := models.GetUserById(id)
    if err != nil {
        c.CustomAbort(404, err.Error())
        return
    }
	c.Data["User"] = user
	c.TplName = "user/show/show.tpl"
}
```

ルータの設定を行います。`init()`を以下のように修正します。
```routers/router.go
func init() {
    beego.Router("/users/:id", &controllers.UserController{}, "get:GetOne")
}

```

最後にビューを作成します。
以下のコマンドを実行し、`show.tpl`を作成します。
```
bee generate view user/show
```

tplというのはtempleteの略です。
BeegoではGo標準の html/template パッケージを使って、動的にHTMLを生成するためのテンプレートファイルとして .tpl 拡張子を使うのが慣例とされています。

`show.tpl`を以下のように修正します。
```views/user/show/show.tpl
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>User Info</title>
</head>
<body>
    <h1>User Info</h1>
    <p><strong>ID:</strong> {{.User.Id}}</p>
    <p><strong>Name:</strong> {{.User.Name}}</p>
    <p><strong>Age:</strong> {{.User.Age}}</p>
</body>
</html>
```
これで完成です。
ブラウザから`localhost:8080/users/1`にアクセスしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3933650/f21fa87f-cde4-4915-af62-4b9fc76ffa03.png)

ユーザ情報が取得できました。

実は`bee generate scaffold`を用いれば、今回のように個別でmodel, view, controllerを作らなくても一気に作成することもできます。ぜひ試してみて下さい。

またMVCアプリと同様の手順でRESTful APIも簡単に作ることもできます。こちらもぜひ試してみてください。

# 感想

今回は簡単なアプリを作りながらBeegoの使い方について記載しました。
以下、実際に使ってみて感じたことです。

**cliツールの自動生成が便利**
- bee new、bee generate scaffold などで MVC構造を一発で作れるのは、Gin や Echo にはない大きな利点
- CRUDベースのWebアプリの原型を数分で構築可能

**グローバルなwebオブジェクトによる手軽な定義**
- Beego では web.Get() などで直接ルーティングを定義可能
- gin.New() や echo.New() でインスタンスを生成する必要がないため、コード量が少なくすむ

**MVC志向が強く、他のフレームワークと差別化されている**
- Gin や Echo は Router ベース（ミニマル志向）で、自分で設計を組む必要がある
- Beego は 明確なMVC設計（Model / Controller / View） が用意されていて、フルスタック志向


# 結論：こんなときにBeegoが向いている！
- CRUDベースの管理アプリをすばやく作りたいとき
- Goでフルスタック（MVC + HTMLテンプレート）開発をしたいとき
- CLIでベースアプリを一発生成して時短したいとき
- Laravel や Rails のような構造に慣れている人