Docker Compose: Redis, Flask and ReactJS – Part 2

In the last part, we set up a simple todo-API using Flask and Redis. We managed to dockerize both Redis and Flask, so starting the backend of our application is as easy as running docker-compose up. Now, it’s time to implement a simple web client for this application. For that, I’m going to use ReactJS and react-create-app.

Software

Docker
ReactJS
create-react-app

Additionally, you should install the following package using npm:

npm install es6-request

We will use it as a simple http-client to call our API.

Creating the app

To set up the client app, open a terminal, go to the client directory in the project and run:

create-react-app client

After the command is completed, you should have a project structure like this:

 

 

Before we get started on our App, let’s clean up our project a bit by removing the following files:
index.css
registerServiceWorker.js
logo.svg
App.test.js

leaving only App.js, App.css and index.js.
Then, remove the imports for registerServiceWorker and index.css from index.js and the logo.svg import from App.js. Finally, remove all code inside the render method and make it return an empty div:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
      </div>
    );
  }
}

export default App;

Now, if you run npm start from the terminal, an empty page should open up in your browser.

Implementing a todo-list

Now it’s time to implement our simple Web-App. We will load the items from the todo-list via our Flask-API and then display it as a list. Below that list, we will add a text box and a confirm button to add a new item to that list. Here is the full source code of the new App.js file:

import React, { Component } from 'react';
import './App.css';
const request = require("es6-request");

class App extends Component {
  constructor() {
    super();
    this.state = { todoList: [], newItem: null }
    this.addItem = this.addItem.bind(this);
    this.onNewItemChange = this.onNewItemChange.bind(this);
  }

  onNewItemChange(e) {
    this.setState({ newItem: e.target.value });
  }

  addItem(item) {
    let newItem = this.state.newItem;
    let newId = this.state.newId;

    request.put('http://localhost:5000/todos/item' + newId)
    .send(newItem)
    .then(([body, res]) => {
        this.updateTodoList();
    })
  }

  componentDidMount() {
    this.updateTodoList();
  }

  updateTodoList() {
    request.get('http://localhost:5000/todos/').then(([body, res]) => {
      let result = JSON.parse(body);
      this.setState({
        todoList: result,
        newId: Object.keys(result).length + 1
      })
    });
  }

  render() {
    let todos = [];
    for (let key in this.state.todoList) {
      todos.push(<p key={key}>- { this.state.todoList[key] }</p>);
    }

    return (
      <div className="App">
        <h2>Todo list:</h2>
        {todos}
        <input type="text" id="TX_NewItem" onChange={ this.onNewItemChange }/>
        <button onClick={this.addItem}>Add to list</button>
      </div>
    );
  }
}

export default App;

In the constructor, we initialize the state and bind two new functions: One for adding an item to the list, and another one for capturing changes in the input text box. The onNewItemChange method simply sets the current state of the App-component to the text-value of the text box.
addItem reads the input from that state and sends a PUT request to our API. Finally, on successful creation of a new todo-item, we call the updateTodoList method to get the updated todo list.
To populate the todo-list on the first page-load, this method is also called in the componentDidMount method.
Finally, the render function puts everything together by iterating through the todo-list and displaying each item in a p node.
Below all todo items, I added an input field and a button to add items to the todo-list.
If you run the app now, you will see that all items are clunked together. To fix this, I replaced the css with some simple styling for all the elements:

.App {
  text-align: left;
}

input {
  margin-left:5px;
  margin-right:5px;
  margin-top:10px;
}

span {
  display:inline-block;
  width:185px;
}

Now, if you reload the page, you should see something like this:

 

 

The text box allows us to add new items to our todo-list. Feel free to extend this app and add the code for deleting and updating items.

Dockerizing the client

Now it’s time to run this client (and the rest of the application) using only Docker and Docker-Compose. This is as simple as creating a Dockerfile that copies and install all required npm packages and runs the app using npm start. For this example, I used the Dockerfile from http://mherman.org/blog/2017/12/07/dockerizing-a-react-app/#.WnIhLqjT5PY.
To run this Dockerfile using Docker-Compose, simply add the following lines to the docker-compose.yml file we created in the last part of this tutorial:

  client:
    container_name: client
    build:
      context: ./client
      dockerfile: Dockerfile
    volumes:
      - './client/:/usr/src/app'
    ports:
      - '3000:3000'
    environment:
      - NODE_ENV=development

This service is responsible for running the Dockerfile we just created. Additionally, it adds a volume with our source code so we don’t have to rebuild the Docker-image after we change the code.
Now, to test the client, run docker-compose up from a Terminal and after the Docker-image is done building, you should be forwarded to the app we just created. Additionally, our other dependencies (Redis and the Flask API will be started as well.

Using Docker during development

The big advantage of this setup is, that we can now easily change parts of the application without having to worry about the other parts. For example, to make changes to and debug the React-app, you can run docker-compose up redis web.
This way, you don’t have to worry about configuring and running the underlying Python app. If you are concerned only about the Frontend, you won’t care if the API is running on Python, NodeJS or any other technology as long as the Dockerfiles work correctly.
Vice versa, if you just need a web-client to test the API, you can run docker-compose up client to initiate the web-client and then debug the Flask API from your favorite editor.

Conclusion

While this web-app is very simple, I hope you can now see the potential of using Docker for development. With some additional configuration effort, you can easily scale this Docker-configuration to bigger applications and even use it for deployment.
I hope you enjoyed this tutorial, if you have any questions, problems or feedback, please let me know.