Combine Go and React in single Docker container

Anh Duong Viet
4 min readDec 30, 2020

--

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 the rice 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

main.go

This is a simple server I wrote by go using echo-framework .

Now create a simple API and call it’s post APIs.

post.go

and a utilities file.

utils.go

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 from frontend phases to builder . Rice will embed build 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 :(

--

--

Anh Duong Viet

I’m a Software / DevOps engineer. My main is focus on maintain and maintain and ensure the stability of the infrastructure.