Remote Developmentを使って爆速でGo&PostgreSQLの環境構築を行う
こんにちは!
Remote Developmentを使ったGoの開発に関してまとめましたので参考になれば幸いです
github リポジトリ
以下をcloneすることでこの記事に書かれてることが再現できます!
こんな方にオススメ!
- これからGoを学習したい方
- Goを使ってデータベースとのやりとりを行いたい方
- ローカルマシンを汚したくない方(brewなど)
- Dockerは使いたいけど詳しい設定はよくわからない方
- Githubを使ってGoのコードをリモートにpushしたい方
この記事で扱わないこと
- VSCodeのインストール方法
- Docker(Docker Desktop)のインストール方法
- Githubのアカウント登録
- SQLの解説や使用したGoのパッケージの解説
- SSHを使用したGitHubへの接続
- 最速で開発できることをを目的としてますのでビジネスで開発する場合はSSHの使用を検討してください
Remote Developmentのインストール
Remote Developmentをインストールしてください。
Remote Developmentを1行で解説すると
開発に必要なDocker(Docker Compose)を提供してくれるスターターセット
です。詳細は公式ドキュメントをご覧ください。
コンテナを新規作成する
VSCodeの左下の青い部分をクリック
「新しい開発コンテナ」を選択
「Go&PostgreSQL」を選択
「Dev Containerの作成」を選択。コンテナを作成するのに5分 ~ 10分ぐらいかかると思います
Goファイルを作成する
コンテナが作成されると新しい新しいVSCodeのウインドが立ち上がると思います(下記画像)。
このVSCodeのウインド(下記画像)はコンテナの中に入ってますのでここでインストールした拡張機能はローカルマシンには反映されないのが特徴です
Goファイル作成
vscode ➜ /workspaces/go-postgres $ touch main.go
Hello, world!を表示させるだけのGoファイルを記述
package main
import (
"fmt"
)
func main() {
fmt.Printf("Hello, world!")
}
「Go 初期化」でググるとgo mod init
が沢山ヒットしたので実行してみる
vscode ➜ /workspaces/go-postgres $ go mod init
go: cannot determine module path for source directory /workspaces/go-postgres (outside GOPATH, module path must be specified)
Example usage:
'go mod init example.com/m' to initialize a v0 or v1 module
'go mod init example.com/m/v2' to initialize a v2 module
公式のwikiによると解説がありましたので日本語に訳した上で引用します
go mod init引数を指定しないと、VCS メタデータなどのさまざまなヒントに基づいて適切なモジュール パスを推測しようとします。go mod initただし、常に適切なモジュール パスを推測できるとは限りません。
このエラーが発生した場合はgo mod init、ヒューリスティックでは推測できなかったため、モジュール パス ( などgo mod init github.com/you/hello) を自分で指定する必要があります。
モジュールパスって何?と思って調べてみたら公式ドキュメントに書かれていたので日本語に訳した上で引用します。
モジュールのパスは、モジュール内のパッケージ パスのプレフィックスです。
モジュール パスでは、モジュールが何を行うか、およびモジュールがどこにあるかを説明する必要があります。通常、モジュール パスは、リポジトリのルート パス、リポジトリ内のディレクトリ (通常は空)、およびメジャー バージョンのサフィックス (メジャー バージョン 2 以降のみ) で構成されます。
他の言語やフレームワークでimport './xxx'
みたいなことをする場合、相対パスではなく自分で決めたモジュール名からimportする必要があるっぽいですね。今回はメッセージ通りexample.com/m
を採用します(ここは自由に決めて大丈夫です)
vscode ➜ /workspaces/go-postgres $ go mod init example.com/m
以下のメッセージが表示されると思います
go: creating new go.mod: module example.com/m
go: to add module requirements and sums:
go mod tidy
以下のようなgo.modファイルが作成されました。
module example.com/m
go 1.21.2
公式ドキュメントの説明を日本語に訳した上で引用します。
各Goモジュールはgo.modファイルで定義され、他のモジュールやGoのバージョンへの依存関係など、モジュールのプロパティを記述します。
この状態でgoファイルを起動させてみましょう
vscode ➜ /workspaces/go-postgres $ go run main.go
Hello, world!
リポジトリにファイルをpushする
コンテナの中でgitの設定を行います。
冒頭で説明した通り、sshの設定を行わないので今回はHTTPS接続でpushします。
今回はチュートリアルなので.env
もpushしていますが、実際は.gitignoreで除外してください
vscode ➜ /workspaces/go-postgres $ git init
vscode ➜ /workspaces/go-postgres (master) $ git add .
vscode ➜ /workspaces/go-postgres (master) $ git commit -m "first commit"
vscode ➜ /workspaces/go-postgres (master) $ git remote add origin https://github.com/${ユーザー名}/${リポジトリ名}.git
vscode ➜ /workspaces/go-postgres (master) $ git push origin HEAD
PostgreSQLを導入する
マイグレーションとAPIのエンドポイントの準備としてmain.goを以下のようにします。
package main
import (
"database/sql"
"fmt"
"net/http"
"os"
_ "github.com/lib/pq"
)
func main() {
host := os.Getenv("POSTGRES_HOSTNAME")
database := os.Getenv("POSTGRES_DB")
user := os.Getenv("POSTGRES_USER")
password := os.Getenv("POSTGRES_PASSWORD")
_, err := sql.Open("postgres", fmt.Sprintf("host=%s user=%s password=%s dbname=%s sslmode=disable", host, user, password, database))
if err != nil {
panic(err)
}
// TODO: マイグレーションとAPIのエンドポイント作成
fmt.Println("Server is running on port 8080...")
http.ListenAndServe(":8080", nil)
}
再びgoファイル実行・・・と思ったらエラー出ましたね。
vscode ➜ /workspaces/go-postgres (master) $ go run main.go
main.go:9:2: no required module provides package github.com/lib/pq; to add it:
go get github.com/lib/pq
go getは非推奨らしいのでgo mod tidyでインストール。(詳細はこちら)
vscode ➜ /workspaces/go-postgres (master) $ go mod tidy
go: finding module for package github.com/lib/pq
go: downloading github.com/lib/pq v1.10.9
go: found github.com/lib/pq in github.com/lib/pq v1.10.9
再びgoファイル実行
vscode ➜ /workspaces/go-postgres (master) $ go run main.go
サーバーが立ち上がることが確認できればOKです!
Server is running on port 8080...
go.modファイルとgo.sumファイルが以下のようになるはずです
module example.com/m
go 1.21.2
require github.com/lib/pq v1.10.9
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
マイグレーションファイル作成
migrationsディレクトリとusers.goファイルを作成
vscode ➜ /workspaces/go-postgres (master) $ mkdir migrations
vscode ➜ /workspaces/go-postgres (master) $ cd migrations
vscode ➜ /workspaces/go-postgres/migrations (master) $ touch users.go
package migrations
import (
"database/sql"
_ "github.com/lib/pq"
)
func UsersMigrate(db *sql.DB) {
// users テーブルが存在するかどうかを調べる
var count int
err := db.QueryRow("SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'users'").Scan(&count)
if err != nil {
panic(err)
}
// users テーブルが存在する場合は処理終了
if count >= 1 {
return
}
if err != nil {
panic(err)
}
_, err = db.Exec("CREATE TABLE users (user_id serial PRIMARY KEY, user_name VARCHAR(50));")
if err != nil {
panic(err)
}
_, err = db.Exec(`
INSERT INTO
users (user_name)
VALUES
('Smith'),
('ohnson'),
('Brown')
`)
if err != nil {
panic(err)
}
}
APIファイルの作成
apiディレクトリとusers.goファイルを作成
vscode ➜ /workspaces/go-postgres (master) $ mkdir api
vscode ➜ /workspaces/go-postgres (master) $ cd api
vscode ➜ /workspaces/go-postgres/migrations (master) $ touch users.go
package api
import (
"database/sql"
"encoding/json"
"io"
"log"
"net/http"
)
type User struct {
Id int `db:"user_id"`
Name string `db:"user_name"`
}
func Users(db *sql.DB) {
http.HandleFunc("/users/get", func(w http.ResponseWriter, r *http.Request) {
rows, err := db.Query("SELECT * FROM users")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
users := []User{}
for rows.Next() {
user := User{}
err := rows.Scan(&user.Id, &user.Name)
if err != nil {
log.Fatal(err)
}
users = append(users, user)
}
json.NewEncoder(w).Encode(users)
})
http.HandleFunc("/users/create", func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
log.Fatal(err)
}
user := User{}
err = json.Unmarshal(body, &user)
if err != nil {
log.Fatal(err)
}
stmt, err := db.Prepare("INSERT INTO users (user_name) VALUES ($1) RETURNING user_id, user_name;")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
err = stmt.QueryRow(user.Name).Scan(&user.Id, &user.Name)
if err != nil {
log.Fatal(err)
}
json.NewEncoder(w).Encode(user)
})
}
main.goの修正
package main
import (
"database/sql"
"fmt"
"net/http"
"os"
// 追加
"example.com/m/api"
// 追加
"example.com/m/migrations"
_ "github.com/lib/pq"
)
func main() {
host := os.Getenv("POSTGRES_HOSTNAME")
database := os.Getenv("POSTGRES_DB")
user := os.Getenv("POSTGRES_USER")
password := os.Getenv("POSTGRES_PASSWORD")
db, err := sql.Open("postgres", fmt.Sprintf("host=%s user=%s password=%s dbname=%s sslmode=disable", host, user, password, database))
if err != nil {
panic(err)
}
// 追加
migrations.UsersMigrate(db)
// 追加
api.Users(db)
fmt.Println("Server is running on port 8080...")
http.ListenAndServe(":8080", nil)
}
動作確認
(実行していない場合は)goファイルを実行
vscode ➜ /workspaces/go-postgres (master) $ go run main.go
以降別タブのターミナルで実行
getの確認
vscode ➜ /workspaces/go-postgres/api (master) $ curl http://localhost:8080/users/get
[{"Id":1,"Name":"Smith"},{"Id":2,"Name":"Johnson"},{"Id":3,"Name":"Brown"}]
postの確認
vscode ➜ /workspaces/go-postgres/api (master) $ curl -X POST -d '{ "name": "Bob" }' http://localhost:8080/users/create
{"Id":4,"Name":"Bob"}
再度getの確認
vscode ➜ /workspaces/go-postgres/api (master) $ curl http://localhost:8080/users/get
[{"Id":1,"Name":"Smith"},{"Id":2,"Name":"Johnson"},{"Id":3,"Name":"Brown"},{"Id":4,"Name":"Bob"}]
終わりに
これでGoでAPI開発ができるようになると思います。
Docker周りの環境構築のヒントになれば幸いです。
ご覧頂きありがとうございます!
Discussion