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 is time to implement a simple web client for this application. For that, I’m going to use ReactJS and react-create-app.
Software
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 installs all required npm
packages and runs the app using npm start
. For this example, I used the Dockerfile from here.
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.