こんにちは。Tomoyuki(@tomoyuki65)です。
Go言語(Golang)には様々なフレームワークがありますが、現時点ではまだまともなフルスタックフレームワークはないため、Go言語を中心とした管理画面を作ったりしている方は少ないと思います。
そこでフロントエンドで人気のReact.jsを使い、ReactのアプリをGo言語のサーバーから配信することはできるのかが気になったので試してみることにしました。
React.jsを使う際は、Next.jsを除けば最近だとViteで開発することが多いと思うので、フロントエンドにはViteのReactを使い、バックエンドはGoとechoを使います。
この記事では、そんなViteとReactのアプリをGo言語(Golang)のサーバーから配信する方法についてご紹介します。
ViteとReactのアプリをGo言語(Golang)のサーバーから配信する方法
まずはViteのReactでアプリをさくっと作りますが、今回はDockerを使って開発環境を構築するため、必要に応じて事前にDocker Desktopなどを利用できるようにして下さい。
では以下のコマンドを実行し、各種ファイル等を作ります。
$ mkdir go-react && cd go-react
$ mkdir -p deploy/docker/local && touch deploy/docker/local/frontend.Dockerfile
$ touch compose.yml
$ mkdir frontend
次に作成したファイルをそれぞれ以下のように記述します。
・「deploy/docker/local/frontend.Dockerfile」
FROM node:22.19.0-alpine
WORKDIR /app
EXPOSE 5173
※今回はNodeのバージョン「22.19.0」を使います。
・「compose.yml」
services:
frontend:
container_name: frontend
build:
context: .
dockerfile: ./deploy/docker/local/frontend.Dockerfile
command: sh -c "npm ci && npm run dev -- --host"
volumes:
- ./frontend:/app
ports:
- "5173:5173"
tty: true
stdin_open: true
次に以下のコマンドを実行し、コンテナをビルドします。
$ docker compose build --no-cache
次に以下のコマンドを実行し、Viteを使ってReactのプロジェクトを作ります。
$ cd frontend
$ docker compose run --rm frontend npm create vite@latest ./frontend
コマンド実行後に選択肢が出るので、Ok to proceed? (y)には「y」、Select a frameworkには「React」、Select a variantには「TypeScript + SWC」を選択して実行します。
※SWCはRust製の高速なコンパイラです。
実行後、frontendディレクトリ配下に各種ファイルが作成されればOKです。
次に以下のコマンドを実行し、依存関係をインストールします。
$ cd ..
$ docker compose run --rm frontend npm i
次に以下のコマンドを実行し、コンテナを起動します。
$ docker compose up -d
次にブラウザで「http://localhost:5173」を開き、以下のような画面が表示されればOKです。
これでフロントエンド側のアプリは用意できたので、以下のコマンドを実行して一度コンテナを止めます。
$ docker compose down
Goのechoでサーバー構築
次にGoのechoでサーバーを構築するため、以下のコマンドを実行して各種ファイルを作成します。
$ touch deploy/docker/local/backend.Dockerfile
$ mkdir backend && touch backend/main.go
次に作成したファイルをそれぞれ以下のように記述します。
・「deploy/docker/local/backend.Dockerfile」
##############################
# ビルドステージ
##############################
FROM node:22.19.0-alpine AS builder
WORKDIR /build
COPY ./frontend .
RUN npm ci
RUN npm run build
##############################
# 実行ステージ
##############################
FROM golang:1.25.1-alpine3.22 AS runner
WORKDIR /app
COPY ./backend .
COPY --from=builder /build/dist ./static
# go.modがあれば依存関係をインストール
RUN if [ -f ./go.mod ]; then \
go install; \
fi
# 開発用のライブラリをインストール
RUN go install github.com/air-verse/air@v1.63.0
RUN go install honnef.co/go/tools/cmd/staticcheck@latest
RUN go install go.uber.org/mock/mockgen@latest
RUN go install github.com/swaggo/swag/cmd/swag@latest
EXPOSE 8080
※Goのバージョンは「1.25.1」を使います。ホットリロード用のairのバージョンも「v1.63.0」で固定します。
・「backend/main.go」
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello World !!")
}
次にファイル「compose.yml」を以下のように修正します。
services:
frontend:
container_name: frontend
build:
context: .
dockerfile: ./deploy/docker/local/frontend.Dockerfile
command: sh -c "npm ci && npm run dev -- --host"
volumes:
- ./frontend:/app
ports:
- "5173:5173"
tty: true
stdin_open: true
depends_on:
- backend
backend:
container_name: backend
build:
context: .
dockerfile: ./deploy/docker/local/backend.Dockerfile
command: air -c .air.toml
volumes:
- ./backend:/app
- /app/static
ports:
- "8080:8080"
tty: true
stdin_open: true
次に以下のコマンドを実行し、コンテナをビルドします。
$ docker compose build --no-cache
次に以下のコマンドを実行し、初期化します。
$ docker compose run --rm backend go mod init go-react
$ docker compose run --rm backend air init
次にDDD(ドメイン駆動設計)を参考に、フロントエンドのアプリを配信するためのエンドポイントを作るため、以下のコマンドを実行して各種ファイルを作成します。
$ mkdir -p backend/internal/presentation/handler/admin
$ touch backend/internal/presentation/handler/admin/admin_handler.go
$ mkdir -p backend/internal/registry
$ touch backend/internal/registry/registry.go
$ mkdir -p backend/internal/presentation/router
$ touch backend/internal/presentation/router/router.go
次に作成したファイルをそれぞれ以下のように記述します。
・「backend/internal/presentation/handler/admin/admin_handler.go」
package admin
import (
"io/fs"
"net/http"
"github.com/labstack/echo/v4"
)
type AdminHandler interface {
Frontend(c echo.Context) error
}
type adminHandler struct {
distFS fs.FS
}
func NewAdminHandler(distFS fs.FS) AdminHandler {
return &adminHandler{
distFS fs.FS
}
}
func (h *adminHandler) Frontend(c echo.Context) error {
// index.htmlを読み込む
indexHTML, err := fs.ReadF ile(h.distFS, "index.html")
if err != nil {
return err
}
return c.HTML(http.StatusOK, string(indexHTML))
}
※セキュリティの問題で保存できなかったので「fs.ReadF ile」のFとiの間にスペースを入れています。実際のコードではスペースを消去して下さい。
・「backend/internal/registry/registry.go」
package registry
import (
"io/fs"
handlerAdmin "go-react/internal/presentation/handler/admin"
)
// ハンドラーをまとめるコントローラー構造体
type Controller struct {
Admin handlerAdmin.AdminHandler
}
func NewController(distFS fs.FS) *Controller {
// adminドメインのハンドラー設定
adminHandler := handlerAdmin.NewAdminHandler(distFS)
return &Controller{
Admin: adminHandler,
}
}
・「backend/internal/presentation/router/router.go」
package router
import (
"io/fs"
"net/http"
"go-react/internal/registry"
"github.com/labstack/echo/v4"
echoMiddleware "github.com/labstack/echo/v4/middleware"
)
func SetupRouter(c *registry.Controller, distFS fs.FS) *echo.Echo {
r := echo.New()
// 共通ミドルウェア設定
r.Use(echoMiddleware.Recover())
// ルートパス"/"へのアクセスは404を返し、後続のStaticFSでindex.html を配信するのを防ぐ
r.GET("/", func(c echo.Context) error {
return echo.NewHTTPError(http.StatusNotFound, "Not Found")
})
// Adminのルーティング設定
admin := r.Group("/admin")
admin.GET("", c.Admin.Frontend)
admin.GET("/*", c.Admin.Frontend)
// 静的ファイルを配信
r.StaticFS("/", distFS)
return r
}
次に「backend/main.go」を以下のように修正します。
package main
import (
"embed"
"fmt"
"io/fs"
"log/slog"
"go-react/internal/presentation/router"
"go-react/internal/registry"
)
// ファイルの埋め込み
//go:embed static/*
var staticFiles embed.FS
func main() {
// 埋め込んだファイルから"static"ディレクトリを取り出す
distFS, err := fs.Sub(staticFiles, "static")
if err != nil {
slog.Error("埋め込んだファイルからstaticディレクトリの取り出しに失敗しました。")
}
// サーバー起動
startPort := fmt.Sprintf(":%s", "8080")
c := registry.NewController(distFS)
r := router.SetupRouter(c, distFS)
r.Logger.Fatal(r.Start(startPort))
}
$ docker compose run --rm backend go mod tidy
次に以下のコマンドを実行し、コンテナを再ビルドします。
$ docker compose down
$ docker compose build --no-cache
次に以下のコマンドを実行し、コンテナを起動します。
$ docker compose up -d
次にブラウザで「http://localhost:8080/admin」を開き、以下のような画面が表示されればOKです。
本番環境用のDockerコンテナを作る
上記ではdocker-composeを使ってフロントエンドとバックエンドでそれぞれコンテナを立てて開発しましたが、実際に本番環境にデプロイする際には一つのDockerfileでコンテナを構築する必要があります。
そこで本番環境用のDockerコンテナを作って試すため、以下のコマンドを実行してファイルを作成します。
$ mkdir -p deploy/docker/prod && touch deploy/docker/prod/Dockerfile
次に作成したファイルを以下のように記述します。
・「deploy/docker/prod/Dockerfile」
##############################
# ビルドステージ(node)
##############################
FROM node:22.19.0-alpine AS builder-node
WORKDIR /build
COPY ./frontend .
RUN npm ci
RUN npm run build
##############################
# ビルドステージ(go)
##############################
FROM golang:1.25.1-alpine3.22 AS builder-go
WORKDIR /build
COPY ./backend .
COPY --from=builder-node /build/dist ./static
RUN go install
RUN go build -o main .
##############################
# 実行ステージ
##############################
FROM alpine:3.22 AS runner
WORKDIR /app
# コンテナ用ユーザー作成
RUN addgroup --system --gid 1001 appuser && \
adduser --system --uid 1001 appuser
COPY --from=builder-go /build/main .
EXPOSE 8080
USER appuser
CMD ["./main"]
次に以下のコマンドを実行し、コンテナをビルドおよび起動します。
$ docker compose down
$ docker build --no-cache -f deploy/docker/prod/Dockerfile -t go-react-app:latest .
$ docker run -d -p 80:8080 go-react-app:latest
次にブラウザで「http://localhost/admin」を開き、以下のような画面が表示されればOKです。
開発時にReactのアプリからGoのAPIを実行させる方法について
上記ではGoで構築したサーバーからReactのアプリを配信できることまで確認できましたが、実際にはReactからGoで構築したAPIを実行させる必要も出てくると思います。
本番環境にデプロイする際は同一ドメインのため問題ありませんが、開発時はフロントエンドとバックエンドでポート番号が異なる(ドメインが異なる)ため、それに対応するためにViteのコンフィグでGoのAPIへのプロキシ設定が必要になります。
具体的な設定方法としては、「frontend/vite.config.ts」を以下のように修正して下さい。
・・・
export default defineConfig({
plugins: [react(),
// goのapiへのプロキシ設定を追加する
server: {
proxy: {
// '/api' という文字列を含むリクエストをプロキシする
'/api': {
// 転送先については、compose.ymlのコンテナ名を指定
target: 'http://backend:8080',
// オリジンを書き換える
changeOrigin: true
}
}
},
・・・
最後に
今回はViteとReactのアプリをGo言語(Golang)のサーバーから配信する方法について解説しました。
RDB用の管理画面を作る際は、RailsやLaravelでさくっと作ることが多かったりすると思うので、Go言語で作っているケースは少ないと思いますが、今回のようにReactを組み合わせて作ることも可能です。
Go言語のサーバーからReactなどのフロントエンドを配信したい場合は、ぜひ参考にしてみて下さい。
コメント