post Image
Go と Vue.js なアプリを Heroku で動かす

サーバサイドを 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 に追記します。

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 に下記コードを配置します。これは公式で用意されているサンプルコードです。

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.lockpressly/goose が追記されるようになっていると思います。より明示的に指定したい場合は Gopkg.toml で管理しても良いかもしれません。

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 でビルドを実行しています。

package.json
{
  "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 アプリケーションを動かすことができるようになりました。


『 Go 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。