UPD: Since this article has been published in 2018 a lot of changes were done to
create-react-app. Some of them were breaking an approach I described here. So recently I published a new article, which contains all the fixes required to make everything work again. Here it is: https://medium.com/@kimrgrey/integration-of-create-react-app-into-golang-server-in-2020-1aff6e93ee5a. It’s still better to start from this one, since it describes in details the idea itself and only after switch to the second one, to know what has to be changed to make it work in 2020. Have fun!
Nowadays create-react-app is de-facto official and definitely most popular way to initialize React.JS application. Truthfully speaking, it solved a lot of problems. So, let’s say thank you to guys from Facebook for it.
Yes, I know what do you want to say. Of course, approach it uses can be called… well… at least… debatable. Not all developers are agree that it was good idea to incapsulate all build logic inside of single dependency without officially provided way to override it. But it definitely has it’s benefits which are hard to doubt. For example, if you are not ejecting create-react-app it is pretty easy to get an upgrade to newest version for all infrastructure packages. It means a lot of possible optimisations and bug fixes done under the hood without any movements from developer’s side. Which is great taking into account how fast everything is moving in modern front-end world.
Another thing which is obviously trending now is Golang. This language can be easily used as a more performant and, in the same moment, still pretty friendly replacement for such brilliant things as Ruby / RoR or even Node.JS. As a plus, migration to Golang is also pretty good moment to consider some modern front-end stuff which probably was not used in legacy code. Let’s be honest, writing classical jQuery oriented UI on top of templates built into Golang standard library is not best idea ever. No, no, no…
So, in this article I’m going to experiment a little bit with both technologies to show how they can be easily integrated with each other without breaking some assumptions pretty common for existing code bases.
All information and code examples for this post can be found on Github: https://github.com/kimrgrey/go-create-react-app. Feel free to raise an issue or suggest a PR in case of any interesting problems, observations or ideas. Let’s get it up and running!
Let’s discuss what do we want to achieve first:
- We want to use create-react-app to create and build an application on React.JS. It should work in development using it’s own toolchain without the need to be served by Golang server. It’s mainly usability and development performance constraint. Of course, you can use Golang as development server. But rebuild process for Golang service on each change in JS files can take ages. And by going this way you will quickly find yourself without all those nice error pages and other debug stuff we have in create-react-app out of the box. Which is disappointing.
- In production it should, in opposite, be served by Golang server. Many services on Go are deployed using Docker. So, infrastructure can rely on the fact that container, for example, exposes only 80 port for both UI and API stuff. We don’t want to break it or have reverse proxy as part of our container. So, Go server should handle it nicely.
In short, Go and React should play good game being separated in development, but be nice to each other in production.
Let’s start from simple Golang server which is rendering some page. In this application I’m not going to use almost any external dependencies. Only standard library of Go, only hardcode. The only one exception here is kingpin which I like for it’s simplicity. So, let’s install it first:
$ go get gopkg.in/alecthomas/kingpin.v2
Ok, now we are ready to go, let’s create our brand new project:
$ mkdir -p $GOPATH/src/github.com/kimrgrey/go-create-react-app
$ cd $GOPATH/src/github.com/kimrgrey/go-create-react-app
$ mkdir cmd
$ touch cmd/main.go
Now we can run your favourite code editor for Golang and write some bootstrap code.
This code is not doing anything except definition of flags and arguments for CLI which we are going to use later. Let’s start our HTTP server and make it respect
CMD + C.
Ok, now we are ready to add logic. First of all we can write some kind of dummy API which will serve us as demonstration that routing is configured properly and we can add more API-s. Let’s create new package called
api and expose new
http.HandlerFunc from there.
Another thing we want to have is HTML layout. Why? Well, let’s say we have navigation bar on top of the page which was done long time ago and we don’t want to rebuild it on React.JS right now. Another important thing is that access to this page can be restricted to only specific users based on the session data stored in cookies. Of course, it’s better to use something like JWT tokens for client side driven authentication / authorisation process, but this cookie based approach in legacy code base can be populated across the system. And right now and here we just want to be able to write new UI with React.JS instead of fighting with infrastructure and dealing with dangerous bugs, right?
Here is an example of handler which renders one page with some predefined context on every request. I put it into
Now we can combine everything together:
It’s time to add some React.JS stuff finally. Let’s create our app first in directory
$ npx create-react-app ui
$ cd ui && yarn install && yarn start
Server with default application should be up, running and listening on address
127.0.0.1:3000 . Let’s stop it and build production version of the bundle.
$ yarn build && ls -l build/
-rw-r--r-- 1 kimrgrey staff 257 Jun 21 16:20 asset-manifest.json
-rw-r--r-- 1 kimrgrey staff 3870 Jun 21 16:20 favicon.ico
-rw-r--r-- 1 kimrgrey staff 548 Jun 21 16:20 index.html
-rw-r--r-- 1 kimrgrey staff 317 Jun 21 16:20 manifest.json
-rw-r--r-- 1 kimrgrey staff 3235 Jun 21 16:20 service-worker.js
drwxr-xr-x 5 kimrgrey staff 160 Jun 21 16:20 static
Looks great, but if you take a look at bundle static files list you should notice that they have suffix in form of hash right before extension:
$ ls -l build/static/js/
-rw-r--r-- 1 kimrgrey staff 118169 Jun 21 16:20 main.a0b7d8d3.js
-rw-r--r-- 1 kimrgrey staff 479769 Jun 21 16:20 main.a0b7d8d3.js.map
It’s called digest. It depends on content of the file and makes it easier to deal with caches. But in order to include such file into Golang layout we should communicate current digests to the server somehow. Luckily default build configuration provides thing which contains exactly what we need -
asset-manifest.json. Take a look:
$ cat build/asset-manifest.json
This file has mapping between original name of the asset and digested one. Cool! So, let’s teach Golang understand this format. It’s pretty easy task:
We just read the file and return a function which can provide file name based on original asset name. Now we can use this package to pass our mapper to the view context:
Information about build path can be passed to the view data through handler from root function:
We also need to teach our server that assets built by webpack should be served from
ui/build directory. Good news here is that standard library of Golang already supports static files serve:
So, only one thing left is few changes in our layout. Since we added function
Webpack to view data it can be used in templates. Here is how we can add scripts and stylesheets from our build with automated support of digesting:
Let’s check if it works…
Yay! Default application generated by
creare-react-app is working inside of our layout! Pretty cool! But there is a problem with SVG logo as you can see. Well, after closer look it’s easy to see where this problem comes from:
The URL is wrong because it’s supposed by default that build directory will be served as root folder. It’s not really true in our case. So, let’s tell webpack to fix it for us:
$ PUBLIC_URL=http://127.0.0.1:9999/ui/build yarn build
And here we are:
As you can see it’s not so hard to introduce modern front-end stack as part of existing legacy infrastructure: few tricks, couple of lines of code — and that’s it. There is no also need to make monolith out of it during development. It’s much more comfortable to use best things and tools out of 2 worlds instead of unifying and aligning them on average level.