Vercel × Go で開発するときの Tips
はじめに
Vercel に Go が無料でデプロイできることを下記の記事にて紹介しました。
今回はそんな Vercel × Go における開発 Tips についてツラツラとご紹介します。
version
go version
go version go1.22.5 darwin/arm64
vercel --version
Vercel CLI 34.3.1
34.3.1
build and ignore
Vercel は添付した図のようなビルドプロセスとなっています。
Vercel CLI にてストレージにアップロードを行い、リモート環境のコンテナにてビルド(デプロイ)が実行されます。
そのためアップロードするファイルを除外しておくことはビルド(デプロイ)時間を短縮するときに有効となります。
.vercelignore
ファイルをルートに作成し除外したいファイルを記載することで可能となります。
.gitignore
と使い方は同じです。
routes
パスルーティングは vercel.json
にて routes
を設定することで可能となります。
ルーティング
{
"routes": [
{
"src": "/",
"dest": "/api",
"methods": [
"GET"
]
}
]
}
上記記述によって
- ルートパス
/
に対してディレクトリ/api
に配置したindex.go
を設定index.go
package api import ( "fmt" "net/http" ) func Handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "<h1>Hello from Go!</h1>") }
- method として
GET
のみを許可
が設定されます。
以下実際にアクセスした結果です。
curl -X GET -I https://<project>.vercel.app/
HTTP/2 200
# ...
curl -X POST -I https://<project>.vercel.app/
HTTP/2 404
# ...
POST
でアクセスした場合は 405 Method Not Allowed
ではなく 404 Not Found
が返ってくることに注意が必要です。
パスパラメータ
vercel.json
を以下のように記載するとパスパラメータ込みでルーティングすることが可能になります。
{
"routes": [
{
"src": "/todos/(?<id>[^/]*)",
"dest": "/api/todos/id",
"methods": [
"GET",
"PUT",
"DELETE"
]
}
]
}
index.go
では以下のようにしてパスパラメータを取得しています。
(無理矢理取得してます。もう少し良い方法はあると思います。)
index.go
func Handler(w http.ResponseWriter, r *http.Request) {
id := strings.TrimPrefix(r.URL.Path, "/todos/")
// ...
}
クエリパラメータ
vercel.json
を以下のように記載するとクエリパラメータ込みでルーティングすることが可能になります。
{
"routes": [
{
"src": "/todos(?<query>[^/]*)",
"dest": "/api/tosos",
"methods": [
"GET"
]
},
]
}
index.go
では以下のようにしてクエリパラメータを取得しています。
index.go
func Handler(w http.ResponseWriter, r *http.Request) {
value := r.URL.Query().Get("key") // key 値を指定して value を取得する
// ...
}
test
標準ライブラリの net/http/httptest
を使えば Handler
に対して直接テストが実行できます。
実装は以下のようなイメージになります。
index_test.go
package api_test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/google/uuid"
api "github.com/otakakot/sample-go-vercel-tips/api/todos/id"
)
func TestHandler(t *testing.T) {
t.Parallel()
t.Run("GET_/todos/{id}", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/todos/"+uuid.NewString(), nil)
api.Handler(w, r) // 処理実行
t.Cleanup(func() {
r.Body.Close()
})
// ステータスコードの比較
if w.Code != http.StatusOK {
t.Errorf("got: %v\nwant: %v", w.Code, http.StatusOK)
}
// レスポンスボディの検証
got := api.GetResponse{}
if err := json.NewDecoder(w.Body).Decode(&got); err != nil {
t.Fatal(err)
}
// 比較処理 ...
})
}
ステータスコードによってレスポンスボディが変わったりするのであえてテーブル駆動テストで書いていないです。
internal package
Go は internal
ディレクトリ以下に配置することで外部からは参照させないことができます。
しかし、Vercel では internal
パッケージを使うと下記のようなログが出力されビルドエラーとなりデプロイができません。
package command-line-arguments
imports api/api/todos
index.go:10:2: use of internal package github.com/otakakot/<project>/internal/log not allowed
failed to `go build`
Error: Command failed: go build -ldflags -s -w -o /tmp/62080c8d/bootstrap /vercel/path0/main__vc__go__.go
原因はあまりわかっていません。解決策知っている方いらっしゃいましたら教えてください。
おまけ
api/index.go
をルートとして設定して隣に openapi.yaml
を配置して embed
を使えば petstore
から参照させたりなんかもできます。
index.go
package api
import (
_ "embed"
"net/http"
)
//go:embed openapi.yaml
var yaml []byte
func Handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.Header().Set("Content-Type", "application/yaml")
w.Write(yaml)
}
あと、型が欲しいので oapi-codegen
を自動生成のために使ってたりもしてます。
おわりに
無料版だと 1 アカウントあたり 1 Postgres となるのでマイグレーションツールに sqldef
を使っていると管理がめんどくさそうと気づきました。
(うまいことやらないとほかプロジェクトで管理しているテーブルが消されそう ...... )
sqlc
と sqldef
の組み合わせがマイブームなのでどうしようかなって感じです。
今回実装したコードは以下に置いておきます。
Discussion