サーバサイドを Go で WebAPI として独立して作り、フロントは Vue.js で SPA チックに作り、Static な成果物(HTML/CSS/JS)を Go バイナリに内包して Heroku で動かすメモです。
サンプルのソースコードはこちらです。
前提条件
- Go 1.9+
- パッケージ管理: dep
- DBマイグレーション: pressly/goose
- Vue.js 2.5+
- vue-cli
- ビルドツール: webpack
- Heroku
- PostgreSQL
- Redis
デプロイの動作フロー
- git push heroku master で Heroku にデプロイ
- デプロイ
- dep で必要パッケージをインストール
- npm で必要パッケージをインストール
- Vue.js を webpack でビルド
- Go アプリをビルド
- マイグレーションコマンドをビルド
- マイグレーションコマンドを実行
- デプロイ完了
Go
初期設定
cd $GOPATH/your-name
mkdir go-vuejs-heroku
cd go-vuejs-heroku
dep パッケージ管理
dep init
dep ensure
Heroku でコマンドビルド
pressly/goose
のマイグレーションコマンドをデプロイ時に実行できるように、アプリのバイナリとは別にコマンドをビルドできる Heroku 用の設定を Gopkg.toml
に追記します。
[metadata.heroku]
root-package = "github.com/zaru/go-vuejs-heroku"
go-version = "go1.9.1"
install = [ ".", "./cmd/..." ]
ensure = "true"
ポイントは install = [ ".", "./cmd/..." ]
です。これは Heroku の環境変数である GO_INSTALL_PACKAGE_SPEC
と同じ役割になります。 dep 以外の glide などのパッケージ管理の場合はこちらの環境変数を修正します。
heroku config:set GO_INSTALL_PACKAGE_SPEC=". ./cmd/... "
goose コマンドスクリプトを配置
上記で設定したコマンドのスクリプト cmd/goose/main.go
に下記コードを配置します。これは公式で用意されているサンプルコードです。
package main
import (
"database/sql"
"flag"
"log"
"os"
_ "github.com/lib/pq"
"github.com/pressly/goose"
)
var (
flags = flag.NewFlagSet("goose", flag.ExitOnError)
dir = flags.String("dir", ".", "directory with migration files")
)
func main() {
flags.Usage = usage
flags.Parse(os.Args[1:])
args := flags.Args()
if len(args) > 1 && args[0] == "create" {
if err := goose.Run("create", nil, *dir, args[1:]...); err != nil {
log.Fatalf("goose run: %v", err)
}
return
}
if len(args) < 3 {
flags.Usage()
return
}
if args[0] == "-h" || args[0] == "--help" {
flags.Usage()
return
}
driver, dbstring, command := args[0], args[1], args[2]
switch driver {
case "postgres", "mysql", "sqlite3", "redshift":
if err := goose.SetDialect(driver); err != nil {
log.Fatal(err)
}
default:
log.Fatalf("%q driver not supported\n", driver)
}
switch dbstring {
case "":
log.Fatalf("-dbstring=%q not supported\n", dbstring)
default:
}
if driver == "redshift" {
driver = "postgres"
}
db, err := sql.Open(driver, dbstring)
if err != nil {
log.Fatalf("-dbstring=%q: %v\n", dbstring, err)
}
arguments := []string{}
if len(args) > 3 {
arguments = append(arguments, args[3:]...)
}
if err := goose.Run(command, db, *dir, arguments...); err != nil {
log.Fatalf("goose run: %v", err)
}
}
func usage() {
log.Print(usagePrefix)
flags.PrintDefaults()
log.Print(usageCommands)
}
var (
usagePrefix = `Usage: goose [OPTIONS] DRIVER DBSTRING COMMAND
Drivers:
postgres
mysql
sqlite3
redshift
Examples:
goose sqlite3 ./foo.db status
goose sqlite3 ./foo.db create init sql
goose sqlite3 ./foo.db create add_some_column sql
goose sqlite3 ./foo.db create fetch_user_data go
goose sqlite3 ./foo.db up
goose postgres "user=postgres dbname=postgres sslmode=disable" status
goose mysql "user:password@/dbname?parseTime=true" status
goose redshift "postgres://user:password@qwerty.us-east-1.redshift.amazonaws.com:5439/db" status
Options:
`
usageCommands = `
Commands:
up Migrate the DB to the most recent version available
up-to VERSION Migrate the DB to a specific VERSION
down Roll back the version by 1
down-to VERSION Roll back to a specific VERSION
redo Re-run the latest migration
status Dump the migration status for the current DB
version Print the current version of the database
create NAME [sql|go] Creates new migration file with next version
`
)
これによって dep の Gopkg.lock
に pressly/goose
が追記されるようになっていると思います。より明示的に指定したい場合は Gopkg.toml
で管理しても良いかもしれません。
[[constraint]]
name = "github.com/pressly/goose"
version = "2.1.0"
Vue.js
初期設定
cd $GOPATH/your-name/go-vuejs-heroku
vue init webpack vue-app
cd vue-app
npm install
Vue.js は特別なことはないです。 vue-cli を使って webpack なアプリをテンプレを作っています。
Heroku
Heroku にアプリを作成します。そして Go と Node.JS のビルドパックを追加します。
heroku create go-vuejs-heroku
heroku buildpacks:add heroku/go --app go-vuejs-heroku
heroku buildpacks:add heroku/nodejs --app go-vuejs-heroku
PostgreSQL と Redis のアドオンを追加します。
heroku addons:create heroku-postgresql:hobby-dev --app go-vuejs-heroku
heroku addons:create heroku-redis:hobby-dev --app go-vuejs-heroku
PostgreSQL と Redis の接続情報は環境変数に入っているので、アプリ側が参照するように修正しておきます。
heroku config
デプロイ時に webpack でビルドできるように NPM_CONFIG_PRODUCTION
を false にしておきます。そうしないと devDependencies
のパッケージがインストールされません。ついでに Go のバージョン指定もしておきます。
heroku config:set NPM_CONFIG_PRODUCTION=false
heroku config:set GOVERSION=go1.9
別ディレクトリの npm を動かす
今回のサンプルアプリでは Go アプリの中に Vue.js のアプリを内包するようにしているので、 package.json
はサブディレクトリにあります。そうすると Heroku の Node.JS ビルドパックではビルドしてくれません。
.
├── Procfile
├── server.go
└── vue-app
├── package.json
そこで、ルートディレクトリにも package.json
を配置してビルドコマンドを記述します。
touch package.json
.
├── Procfile
├── server.go
├── package.json
└── vue-app
├── package.json
postinstall
で vue-app ディレクトリの中で npm install と npm run build でビルドを実行しています。
{
"name": "go-vuejs-heroku",
"version": "0.0.1",
"engines": {
"node": "7.10.0",
"npm": "4.2.0"
},
"scripts": {
"postinstall": "npm --prefix ./vue-app install ./vue-app && cd ./vue-app && npm run build"
}
}
マイグレーションコマンドを実行
Procfile
にリリース時に実行するコマンドを指定できます。そこでマイグレーションコマンドを実行指定します。
release: goose postgres $DATABASE_URL up
web: go-vuejs-heroku
以上で、Heroku で Go と Vue.js を使った Web アプリケーションを動かすことができるようになりました。