Building a React App that connects to MySQL via NodeJS using Docker

Carlos Cuba
13 min readJun 29, 2019

The day I thought of giving web development an actual chance and decided to get good at it, I managed to create a responsive site with a simple login system that could pass as a very simple social network of sorts (minus instant messaging). While I was proud of it, little did I know the technologies I used were on the way out; ES5, jQuery, vanilla CSS and mySQLi, while good, could not offer the flexibility that more modern languages would.

After I landed an internship to work in a reputable team, I realized the skills that made me the most knowledgeable of my peers relegated me to the rightful place at which I would only ask for help, learn and adapt to what at first seemed like mountains of code. Little by little, struggle by struggle, my skills adapted and as the internship ended I realized the best way to prove to myself that I could truly call what I had learned my own would be to mix all of that into renovating my website.

Although the front-end development and styling would become a sort of guilty pleasure, I had yet to realize the back-end would present a few more bumps than I was ready to overcome. While true, I already had practical knowledge of PHP to access databases, the way I choose to develop before involved use of FTP; this limited me to a development experience that only included a production environment.

Rather than meshing a bunch of processes to have them run together, I was told — and rather encouraged — by friends to use the oh-so-popular Docker. While I had brief encounters with it in the past, its development was almost fully handled by my seniors, and thus I had little experience with it. But one has to sacrifice and bet what he does not have in order to make the dream of attaining what one could get a reality.

Below is the combined effort of a week of reading, experimenting, and troubleshooting, in which I have made proper use of several tutorials in here and the rest of the net to create a simple React application that connects to a NodeJS server, which in turn connects to a MySQL image to retrieve database information, thereby sending it to the React app that acts as a client.

What you will get from this tutorial: The app that will be created in this tutorial includes use of stateless components (to try and adhere to latest practices) and multiple docker images working together to form a single application.

What you need to (mostly) know: Basic knowledge of ES6, latest React code practices, NodeJS, and docker commands for image building.

The tutorial: We will be using React as client, NodeJS as server, and MySQL for database control. And, although I will explicitly specify what folder and where we’ll be working for each section, each of the steps and sections done here will be numbered to facilitate troubleshooting.

With that, let’s start coding!

I. Root files

  1. Create a folder to contain the whole app. For the purposes of the example, I named mine react-node-mysql-docker-boilerplate.
  2. Make two files inside this folder: .gitignore, and docker-compose.yml
  3. Insert the following code for docker-compose.yml and .gitignore:

Note that there are extension fields declared for MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, and REACT_APP_SERVER_PORT, which will be reused by the services to avoid writing variables over and over. (Note the importance of REACT_APP_SERVER_PORT, as this one is declared this way to be able to use it in the react app). The services used are 4: mysql, phpmyadmin, server, and client. I will explain each of the services:

  • mysql will be used to house our database. It will use the extension fields declared for host, database, user and password (and a root password of its own just in case) to enable connections for the other services. The port 3306 is exposed as that is the default port used in mysql connections with the mysql package. The unless-stopped restart policy is set to try and avoid losing database data when stopping the services. Make a note of the volume used; this will be explained as the db folder is created.
  • phpmyadmin is not required, but it is always nice to have to view the databases and manually edit if needed. It will depend on the mysql service so this service will run before it upon start, and it is linked to it to enable use of the credentials defined (host, database, user, password) when navigating to http://localhost:8080 to access the database. The PMA_HOST is very important, as it defines the address of the mysql server.
  • server refers to the nodeJS app. It will be built with its own Dockerfile (hence no mention of image), it will run before and force the mysql service to run in order for it to start. It will expose the port 8000 and use a variable, REACT_APP_SERVER_PORT , to allow the express server used within it to work. MYSQL_HOST_IP is also very important, as this allows us to resolve the mysql service’s host IP address to map it to the mysql connection. This environment also uses the variables in mysql, as they are necessary for connection to the database.
  • client refers to the React app. Its build attribute will search for a Dockerfile inside the client folder. The NODE_PATH environment variable is very important, as this one allows for the app to recognize its src folder as the start point for aliases to work. It also makes of the extension fields, but only to use REACT_APP_SERVER_PORT, to avoid repeatedly writing 8000wherever needed. Since the default port in a react app done with CRA is 3000 , this port will be exposed. We provide volumes for the src folder, since the code will be stored there.

Now for the .gitignore :

Now, while there may be many more things you would wish to not push when finishing commits, these are the only ones necessary in this project.

II. The database file

  1. Create a folder called db at the root level (right inside the react-node-mysql-docker-boilerplate folder), and place a sample.sql file inside that will pass as an example database:

III. The front end: cleaning up

  1. Inside the react-node-mysql-docker-boilerplate folder, execute the following command on your terminal: npx create-react-app client This will create a simple CRA application that we will modify to suit our needs.
  2. To have the client and server communicate, add a proxy inside the client folder’s package.json , to contain this "proxy": “http://server:8000", , where server would be the name of the service with the nodeJS app (server in this case), and 8000 is the port this app uses (the one the service is exposing)
  3. In the src folder, delete the files index.css , logo.svg , App.css,App.test.js .gitignore, and README.md as those will not be necessary for the tutorial. Remember to also delete imports and mentions of it in index.js
  4. In the src folder, create a folder called app. Move App.js there.
  5. Rename App.js to app.jsx , and change its import in index.js
  6. In app.jsx , remove the import for logo and app.css, and all the code inside return . Let’s leave the file for now to modify later, but your app.jsx should look like this:

IV. The front end: Webpack

  1. The only two packages the front-end uses are webpack and axios . Install them like this: yarn add webpack@4.29.6 axios . Note that, at the time of creating this tutorial, the react-scripts expect to use this version of webpack, but depending on when you’re following this tutorial, you may need a different version.
  2. Create a file, webpack.config.js , at the root of the client folder (right inside it), which for the tutorial would only have declaration to resolve one alias, components. Since there are several tutorials involving webpack and react out there, I won’t delve too much into this to avoid sidetracking.

V. The front end: A simple component

  1. In the src folder, create a folder called components
  2. Inside components, create a folder called sample-components, and inside this a file called sample-component.jsx , with the following code:

Note that by itself, this component is not going to do any more than display this is a sample component . For now, we’ll leave the component like this and add the code to communicate with the server later

VI. The front end: Exports central

  1. In order to export the SampleComponent (and any other components you would create), we need an index.js file that takes them all, and facilitates the import with the alias. Create a file index.js right inside the components folder:

VII. The front end: Importing the component

  1. Now we go back into app.jsx , to import the component we just created:

Note that the components location from where we’re importing must match the one declared as alias in webpack.config.js .

VIII. The front end: Docker configuration

  1. In order to be able to create a docker image to run our app as client, we need a Dockerfile . So let’s create a simple one inside the client folder as follows:

For this, I choose to use the alpine version of the node image as it is very small. All we then do in this file is create a directory for the react app inside /app , set that as its working directory, copy package.json and yarn.lock and all the files within the directory where the Dockerfile is located, and run yarn install . For reference, it is possible to simply write this (line 10) as yarn , but I find that including the install helps out in quickly looking at the code and realizing what each line does. Lastly, we execute the command yarn start to start the react app.

IX. The front end: Ignoring

  1. If we leave the files as is, the client image may turn out to be large, and more so if you work based on this code and add your own dependencies. That’s because we’re indiscriminately adding everything to the /app folder of the image. What we need to do then is create a .dockerignore that excludes any unwanted folders from copying:

Thus, the node_modules folder (which gets created internally with yarn install ) and the build folder, which has no real purpose for this tutorial, are ignored.

With this, you should have a project structure in the client folder that looks like this:

X. The back end: Setting up

  1. Inside the react-node-mysql-docker-boilerplate folder, create a folder called server.
  2. Navigate to this folder in the terminal, then enter the following: npm init -y . This will create a very simple package.json file.

With the package.json , we can now install the following packages: cors, express, mysql and nodemon, with this command:

yarn add cors express mysql nodemon

XI. The back end: The express server

  1. Create a file index.js inside the server folder. This will house the app’s usage of express, which is necessary to utilize a proper server that will listen to get and post requests:

Note that we require three packages:

  • cors, to enable connections and bypass the ever-so-ugly error No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:8000' is therefore not allowed access.
  • express, to listen to the exposed port that enables use of a server in development mode, as well as catching the specified paths in get and post requests (such as /test )
  • mysql, to connect to the database with the credentials defined in docker-compose.yml

So, with the mysql package in use, we use it to create a connection pool via createPool, where the credentials are specified. It is important to use createPool instead of createConnection as the latter expects a connection “on the fly”, as in doing so as the path is caught, and not once the server starts (at least with this configuration). Using createConnection within the path causes a PROTOCOL_ENQUEUE_HANDSHAKE_TWICE once the app is refreshed (but not on the first load), and using it outside requests causes PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR , which only gets solved when code in index.js is changed, as the app reloads thanks to nodemon (to be discussed next). Either way, neither is an efficient option for a production environment.

Lastly, all this get request does is receive a parameter called table , then execute a simple query, select * from sample (in this case), via pool.query . If there is an error, it will be sent back. Otherwise, the listing obtained by the query will be sent back.

XII. The back end: hot server load

  1. In package.json , delete the test script and add one called start :

"start": "nodemon index.js"

This allows for the server to reload as soon as changes are made to it, to try to keep the flow of development and viewing changes in browser as seamlessly as possible.

XII. The back end: Docker configuration

  1. All we have to do to enable the creation of a docker image out of this server app is to create the same Dockerfile that the client folder has.
  2. Also, a .dockerignore must be created. Since we do not have a build script, only the node_modules folder is necessary.

Now you should have a structure of the server folder that looks like this:

XIII. Back to the front end: Communication

  1. Open the file sample-component.jsx again.
  2. Add the following line, {callServer()} , right below the text inside the div. This will not render anything, but rather execute a function defined within the file.
  3. Add the function callServer , which will take care of performing a get request as a promise to call the route specified in the index.js of the server folder, sending the table parameter, and write the response received in the console. For this, you can import axios(although you could probably do this with fetch, I prefer to use this package). The final version of this file for the tutorial will look like this:

Everything should work now. As a last check, you should have a complete project structure that looks like this:

XIV. Preparing and running the Docker images

  1. On the terminal, run docker-compose build . This will skip both images for mysql and phpmyadmin as these depend on images, and will build the images for client and server . Whether you’re doing this on Linux, Mac, or Windows, it may take between 1–2 minutes to build each image and probably a maximum of 5 minutes for both images to be built. Note that, although some of the packages may display errors of installation (similar to the image above), this does not necessarily mean the build process has failed, unless the last line mentions that there have been errors and terminates.
  2. Now that the images are prepared, run docker-compose up -d to start the four images. Note that the -d flag allows for the containers to run in detached mode, which is simply a fancy way to specify that the containers run in the background, thereby leaving the console available for commands. Should you prefer to run in foreground mode — without this flag — you can do it as well as the flag is optional. If you’re running in detached mode though, and wish to see what each container is outputting (such as in the case of trying to view any possible errors upon starting), you may use docker-compose logs <container-name>. For example, typing docker-compose logs client Should output what a normal react app outputs when running, and docker-compose logs server. would output nodemon commands that indicate startup, as well as App server now listening on port 8000, which is the text corresponding with the app.listen code in index.js. Anyways, most successful processes will look like this:

However, it is very much possible to get problems with the server image. To check which containers (with images) are running, you can use the command docker ps. It may be that, because nodemon works in a different way than react-scripts , it still depends on a node_modules folder to exist locally. To diagnose, try running docker-compose up server . Without the node_modules folder, you would get something like this:

And running like this will give you an ERR_CONNECTION_REFUSEDupon trying to access the server app (right as axios invokes the get request). This is easily fixed by navigating into the server folder, then running yarn install .

Another error, though far less common, is to get the following upon running docker-compose up -d : Cannot start service mysql: driver failed programming external connectivity on endpoint. This is no fault of the code; rather of Docker. All we need to do to fix this is restart it.

XV. The results

  1. Now that the images are running in containers, open your favorite browser and navigate to http://localhost:3000 , which is the address of the react app. You should only see this:

But, if you open the developer console, if everything went right, you should see the table that was fetched on the output:

And done! Although having the database output in the console is pointless, this was done for the sake of the tutorial. In your case, and for more complex applications, you should be able to use the data received to modify the component’s state, triggering a render that could get the data from the state and output the table in any way you desire.

Should you need it, I have created a boilerplate here https://github.com/gfcf14/react-node-mysql-docker-boilerplate . Please feel free to comment or open issues on github or here about problems with following the code and/or improvements.

--

--