Combine Go and React in single Docker container
Introduction
Recently, I created my personal blog and here is it. I read some articles about combine “Go and React” or “Serve static files on Go”, etc… They’re pretty good, but they lack some points. So I decided to write this article to show you “How can I combine Go and React in single docker container”.
Prerequisites
- Go: In this article I using Go 1.14 and echo-framework to create APIs.
- React: I using React 17.
- Docker: You need install docker to build image.
- Docker-compose (optional): You can use docker-compose to start you container. Docker-compose is convenient. You can start, stop, restart and trace log your container more easy than docker.
Let’s create a simple project
Run go mod init github.com/<username>/go-n-reactjs
and create some files
go-n-reactjs/
├── frontend/
├── go.mod
├── docker-compose.yml
├── Dockerfile
├── main.go
├── post.go
└── utils.go
Create Go Server with Echo-framework
Let’s install some libraries:
go get github.com/labstack/echo/v4 // echo-framework
go get github.com/GeertJohan/go.rice // go rice
What is Go rice?
go.rice is a Go package that makes working with resources such as html, js, css, images and templates easy. During development
go.rice
will load required files directly from disk. Upon deployment it's easy to add all resource files to a executable using therice
tool, without changing the source code for your package. go.rice provides methods to add resources to a binary in different scenarios.
Now we are going to create server
This is a simple server I wrote by go using echo-framework
.
Now create a simple API and call it’s post
APIs.
and a utilities file.
After done 2 files. We going to register post APIs to group API /api/v1
created above.
/**
* Init group API and register API
*/
v1 := server.Group("/api/v1")
RegisterPostAPI(v1)
Now you can start server by command to see the result:
go run main.go
Create ReactJS client
After create a simple server. Now we going to create a React project by run:
cd ./frontend && npx create-react-app .
Note: You should go inside the frontend
folder and create react app in there.
frontend
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── README.md
├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── reportWebVitals.js
│ └── setupTests.js
└── yarn.lock
After you ran the command. You create some files follow the structure bellows
frontend
├── jsconfig.json
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.js
│ ├── components
│ │ ├── 404.jsx
│ │ ├── home.jsx
│ │ └── post.jsx
│ ├── index.css
│ ├── index.js
│ ├── reportWebVitals.js
│ └── utils
│ ├── api.js
│ └── history.js
└── yarn.lock
You can copy my files in source I place below.
Now is build time
yarn build
and you will receive and build
folder. So we done with frontend.
Back to server side
In main.go
you create a function is RegisterFrontend
/**
* Register frontend to Server
*/
func RegisterFrontend(e *echo.Echo) {
frontend := rice.MustFindBox("./frontend/build")
fe := http.FileServer(frontend.HTTPBox())
e.GET("/static/*", echo.WrapHandler(fe))
// IMPORTANT STEP
e.GET("/*", func(c echo.Context) error {
index, err := frontend.Open("index.html")
if err != nil {
return err
}
content, err := ioutil.ReadAll(index)
if err != nil {
return err
}
return c.HTMLBlob(http.StatusOK, content)
})
}
the complete main.go
Try to run it.
And this is result
Docker time
This is Dockerfile I wrote
So we have to create 3 phases
- First, we build frontend
- Second, copy
build
folder fromfrontend
phases tobuilder
. Rice will embedbuild
folder to single go file. - Finally, we copy binary file to
alpine
image and run it.
To run it with docker:
docker build -t go-n-react:latest .
docker run -p 8080:8080 go-n-react:latest
With docker-compose:
// docker-compose.yml
version: "3"
services:
app:
build: .
ports:
- 8080:8080
Run it:
docker-compose up --build
You can now visit http://localhost:8080
and enjoy it.
And this is my source code: Github
Thanks for reading.
P/S: I’m sorry about my english :(