Developing A Flask and ReactJS Application in Okteto

Muhammed Ali is a Software Developer with a passion for technical writing and open source contribution. His area of expertise is Full-stack web development and DevOps.
Flask is an open source web microframework built with Python. It is straightforward, easy to understand, and is used to build web applications and also APIs.
In this article, you'll be building a TODO list application directly in Okteto using the Okteto Development Environment with the Flask framework and ReactJS library.
Remote development with Okteto doesn't require an active docker session. Instead, we provide you a persistent volume, instant file synchronization, and an accessible public endpoint to view your application changes as you develop your application locally.
ReactJS is an open-source front-end JavaScript library used to build user interfaces.
Okteto is an open-source tool that runs locally to synchronize application code changes to a running pod in a local or remote Kubernetes cluster. With Okteto you're able to see a deployment version of your app as you're developing it.
Instead of working locally, you'll use Okteto's development environment. Although your application can be built locally in your machine, using Okteto's development environment enables you to develop your application in a production ready environment.
Prerequisites
In order to follow this article, the following is required:
- Basic understanding of Python and the Flask framework
- Basic understanding of ReactJS
- Basic understanding of Docker
- NPM installed
Build Flask API
The first step is to create a project directory to house our todo list application:
$ mkdir flask-react-app
$ cd flask-react-app
Next, create the folder for the API application and create a route file:
$ mkdir api && cd api
$ touch todo_list_api.py
Next, create a requirements.txt
file where your application dependencies will be listed. In this case, you only have one, which is Flask
:
Flask==1.1.2
Flask-SQLAlchemy==2.5.0
Flask-SQLAlchemy will be used to handle the database in which the todo data will be stored.
Next, you're going to create a Dockerfile
. This Dockerfile will contain the instructions for building your application's image:
$ touch Dockerfile
Add the following to it:
FROM python:3.9
WORKDIR /api
ENV FLASK_ENV=development
COPY ./requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD [ "python", "todo_list_api.py" ]
Your file structure should now look like this:
├── flask-react-app
│ ├── api
│ │ ├── todo_list_api.py
│ | ├── Dockerfile
│ | ├── requirements.txt
Scaffolding the frontend application
For the frontend part of this application, you'll be using Create-React-App to scaffold a new application.
In the project directory, flask-react-app
, run the command to create a frontend application in React:
$ npx create-react-app frontend
$ cd frontend
The Okteto development environment listens to the port 8080
. Add a proxy and change the port value in start
under the scripts
section in your package.json
file:
"proxy": "http://api:5000",
"scripts": {
"start": "PORT=8080 react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
Install additional dependencies for the application:
$ npm i react-router react-router-dom web-vitals
Next, you're going to create a Dockerfile
for your frontend application:
$ touch Dockerfile
Add the following to your Dockerfile:
FROM node:14.17.3
WORKDIR /usr/src/app
COPY ./package.json ./
COPY ./package-lock.json ./
RUN npm install
COPY . .
EXPOSE 8080
ENTRYPOINT [ "npm" ]
CMD ["start"]
Create a docker-compose.yml
file so you can run both services at once.
In the docker-compose.yml
manifest, you're going to define two services: api
for the backend and web
for the frontend. The web
service is dependent on the api
service.
Add the following to your docker-compose.yml
manifest:
services:
api:
build: ./api
ports:
- "5000:5000"
volumes:
- ./api:/api
environment:
FLASK_ENV: development
web:
build: ./frontend
ports:
- "8080:8080"
volumes:
- ./frontend:/usr/src/app
depends_on:
- api
Now run the following commands to build and start your application:
$ docker-compose build
$ docker-compose up
Your React application is now running and can be accessed at http://localhost:8080
Build application in Okteto
First, you have to install Okteto CLI to access Okteto's development environment.
Next, login into Okteto Cloud from your CLI:
$ okteto login
You'll be prompted to your browser. If it's successful, you'll see something similar to this:
Now let's deploy the application using the docker-compose.yml
file you created earlier:
$ okteto stack deploy
After executing that, your docker-compose file will be deployed and built on Okteto cloud.
Go to Okteto cloud and you'll see that your application has been deployed successfully:
The endpoints for your application have been automatically generated.
Set up your Okteto development environment for Flask
In your terminal, create an Okteto manifest in your api
folder:
$ okteto init
After running it, you'll be prompted to select the deployment you want to develop and select "use default".
You'll see that okteto.yml
and .stignore
were created at the root of your project.
#okteto.yml
name: api
autocreate: true
image: okteto/python:3
command: bash
volumes:
- /root/.cache/pip
sync:
- .:/usr/src/app
forward:
- 8080:8080
reverse:
- 9000:9000
You'll have to change the forward
route to 5000
because the frontend will be using port 8080
:
forward:
- 5000:5000
Description of the files created
Okteto.yml
okteto.yml
tells Okteto CLI how your project's development environment should be run.
name
: the name of your project.command
: handles the command that is used to run your project.sync
: this handles the folders that will be synchronized between your local machine and the development container.forward
: used to provide a list of ports to forward from your development container.reverse
: a list of ports to connect development container to your local machine.volumes
: paths in your development container to be mounted as persistent volumes.sync
: the folders that will be synchronized between your local machine and the development container.
.stignore
.stignore
tells Okteto CLI which files not to synchronize to your development environment. If you're familiar with .gitignore
, .stignore
has a similar function.
Next, activate your development container:
$ okteto up
i Using khabdrick @ cloud.okteto.com as context
✓ Images successfully pulled
✓ Files synchronized
Context: cloud_okteto_com
Namespace: khabdrick
Name: api
Forward: 5000 -> 5000
Reverse: 9000 <- 9000
Welcome to your development container. Happy coding!
Now install the requirements:
pip install -r requirements.txt
Now your development environment is currently running on Okteto cloud.
Remember the todo_list_api.py
file you created earlier? Navigate to it and add the following code:
from flask import Flask, jsonify, request, json
from flask_sqlalchemy import SQLAlchemy
#instantiate Flask functionality
app = Flask(__name__)
# set sqlalchemy URI in application config
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///example.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
# instance of SQL
db = SQLAlchemy(app)
class TodoList(db.Model):
# This class will handle the models for the todo list
id = db.Column(db.Integer, primary_key=True)
detail = db.Column(db.Text, nullable=False)
def __str__(self):
return f"{self.id} {self.detail}"
def todo_serializer(todo):
# covert the object coming from the database into JSON
return{
"id":todo.id,
"detail":todo.detail
}
@app.route('/api', methods=['GET'])
def home():
# Display all the todos in the database
return jsonify([*map(todo_serializer, TodoList.query.all())])
@app.route('/api/todocreate', methods=['POST'])
def todo_create():
request_data = json.loads(request.data)
todo = TodoList(detail=request_data['detail'])
db.session.add(todo)
db.session.commit()
return{'201':'todo created successfully'}
@app.route('/api/<int:id>')
def detail_view(id):
# Detail view of each todo
return jsonify([*map(todo_serializer, TodoList.query.filter_by(id=id))])
@app.route('/api/<int:id>', methods=['PUT'])
def update_todo(id):
# Updates the todo with the id
todo = TodoList.query.get(id)
detail = request.json['detail']
todo.detail = detail
db.session.commit()
return {"200":"Updated successfully"}
@app.route('/api/<int:id>', methods=['POST'])
def delete_todo(id):
# Deletes todo from the database based on id
request_data = json.loads(request.data)
TodoList.query.filter_by(id=request_data['id']).delete()
db.session.commit()
return {"204":"Updated successfully"}
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
The code above contains the routes and functions that performs the CRUD operations in the todolist application.
The #
before the command symbolizes that you're working with the development environment provided by Okteto.
To test your API, add a todo. To do that, start the python
shell in your development container terminal.
Next, run the following commands to create an example.db
file in your project:
> from todo_list_api import db
> db.create_all()
Next, run the following commands to add a todo list entry:
> from todo_list_api import TodoList
> todo = TodoList(detail="Learn Okteto")
Save the todo entry:
> db.session.add(todo)
> db.session.commit()
Exit the Python shell with ctrl + d
Start your Flask server by running python todo_list_api
. You'll be able to access your API from the URL for your API provided by Okteto.
Set up your Okteto development environment for React
In another terminal window, navigate to your project's directory root and initialize an Okteto manifest:
$ okteto init
Select the web deployment:
Select the deployment you want to develop:
▸ web
▸ api
Use default values
You'll notice an okteto.yml
and .stignore
file have been created in the root of your project.
Next, activate your development container:
$ okteto up
✓ Images successfully pulled
✓ Files synchronized
Context: cloud_okteto_com
Namespace: khabdrick
Name: web
Forward: 8080 -> 8080
Reverse: 9000 <- 9000
Welcome to your development container. Happy coding!
Build the front end
In frontend/src/components
folder, create a file form.js
. This file will house the Form
component responsible for form handling:
import React from "react"
export const Form = ({input, onFormChange, onFormSubmit}) => {
const handleChange = (event)=>{
// handle what the form does when you type in it
onFormChange(event.target.value)
}
const handleSubmit = (event)=>{
// handle what the form does when it is submitted
event.preventDefault()
onFormSubmit()
}
return(
<>
<form onSubmit={handleSubmit}>
<input className='form-class' type='text' required value={input} onChange={handleChange}></input>
<input type='submit'></input>
</form>
</>
)
}
In the same folder, create a file cards.js
to handle the rendering of todos.
Add the following to it:
import React from "react";
import {Link} from "react-router-dom"
export const Card = ({ listOfTodos }) => {
return (
<>
{listOfTodos.map(todo => {
return (
<ul key={todo.id}>
<li><Link to={`${todo.id}`}>{todo.detail}</Link></li>
</ul>
);
})}
</>
);
};
Nevermind the listOfTodos
prop, you will create it next.
In the same folder, create a new fileTodoList.js
. This file houses the components responsible for retrieving and sending todos from the API:
import React, { useState, useEffect } from "react";
import { Card } from "../components/card";
import { Form } from "../components/form";
export const TodoPage = () => {
const [todo, setTodo] = useState([]);
const [addTodo, setAddTodo] = useState("");
useEffect(() => {
// Fetch the list of todos API
fetch("/api")
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((data) => setTodo(data));
}, []);
const handleFormChange = (inputValue) => {
setAddTodo(inputValue);
};
const handleFormSubmit = () => {
// Creates todo suing the API
fetch("/api/todocreate", {
method: "POST",
body: JSON.stringify({
detail: addTodo,
}),
headers: {
"Content-type": "application/json; charset=UTF-8",
},
})
.then((response) => response.json())
.then((message) => {
console.log(message);
setAddTodo("");
getUpdate();
});
};
const getUpdate = () => {
// Automatically update the list as you submit the form
fetch("/api")
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((data) => setTodo(data));
};
return (
<>
<Form
input={addTodo}
onFormChange={handleFormChange}
onFormSubmit={handleFormSubmit}
/>
<Card listOfTodos={todo} />
</>
);
};
Next, create the detail page, this page will display each todo.
First create a new file detailView.js
in src/components
folder, and add the following:
import React, { useState, useEffect } from "react";
import { Link, useParams } from "react-router-dom";
import { useHistory } from "react-router-dom";
export const Detail = () => {
const history = useHistory();
const [todo, setTodo] = useState([]);
const { id } = useParams();
useEffect(() => {
fetch(`/api/${id}`)
.then((response) => response.json())
.then((data) => setTodo(data));
}, [id]);
.then((response) => response.json())
.then((message) => {
history.push("/"); //redirect to homepage
})
};
return (
<div>
<br/>
{todo.map((data) => (
<div key="id">Detail: {data.detail}</div>
))}
<br />
<Link to="/">Go Back to Todo List</Link>
</div>
);
};
Moving forward, let's update App.js
so that we can test out what we have.
import React from "react";
import "./App.css";
import { TodoPage } from "./components/TodoList";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { Detail } from "./components/detailView";
function App() {
return (
<div className="App">
<header className="App-header">
<Router>
<Switch>
<Route exact path="/">
<TodoPage />
</Route>
<Route path="/:id">
<Detail />
</Route>
</Switch>
</Router>
</header>
</div>
);
}
export default App;
In your Okteto dashboard, select the second endpoint:
You should see "Invalid Host header" on the browser. This is because you have not permitted Okteto to run your React application.
To fix this, you'll create a .env
and disable host check. In the frontend folder, run the command from your terminal:
$ touch .env
Add the following to your .env
file:
DANGEROUSLY_DISABLE_HOST_CHECK=true
While doing all these updates, Okteto automatically synchronizes the changes with your application deployed on Okteto.
Next, update your application style in App.css
:
*{
text-decoration: none;
}
body {
font-size: 1.6rem;
background-color: #efefef;
color: #324047
}
.App-header{
padding-left: 500px;
}
.form-class{
margin-top: 100px;
}
.links{
position: relative;
list-style-type: square;
padding-left: 1rem;
margin-bottom: 0.5rem;
}
In your okteto development container, start your application:
npm run start
You'll see that you can view and create todos.
Next, you'll build the update and delete functionality. In the src/components
folder create a new file deletePage.js
.
import React from "react"
import {useHistory} from 'react-router-dom'
export const Delete = ({id}) =>{
const history = useHistory()
const deleteTodo = () =>{
fetch(`/api/${id}`,{
method: 'POST',
body:JSON.stringify({
id:id
})
}).then(response => response.json())
.then(data => {
history.push('/')
})
}
return(
<>
<button onClick={deleteTodo}>Delete</button>
</>
)
}
The code above is just fetching the delete function and applying it to the delete
button.
For update
you'll have to make some changes to the detail view, and add a form to use for updating. Update your detailView.js
file to look like below.
import React, { useState, useEffect } from "react";
import { Link, useParams } from "react-router-dom";
import { Delete } from "../components/deletePage";
import { Form } from "../components/form";
import { useHistory } from "react-router-dom";
export const Detail = () => {
const history = useHistory();
const [todo, setTodo] = useState([]);
const [updateTodo, setUpdateTodo] = useState("");
const { id } = useParams();
useEffect(() => {
fetch(`/api/${id}`)
.then((response) => response.json())
.then((data) => setTodo(data));
}, [id]);
const handleFormChange = (inputValue) => {
setUpdateTodo(inputValue);
};
const handleFormSubmit = () => {
fetch(`/api/${id}`, {
method: "PUT",
body: JSON.stringify({
detail: updateTodo,
}),
headers: {
"Content-type": "application/json; charset=UTF-8",
},
})
.then((response) => response.json())
.then((message) => {
history.push("/");
})
};
return (
<div>
<Form
input={updateTodo}
onFormChange={handleFormChange}
onFormSubmit={handleFormSubmit}
/>
<br/>
{todo.map((data) => (
<div key="id">Detail: {data.detail}</div>
))}
<br />
<Delete id={id} /><p>Fill the above to update</p>
<Link to="/">Go Back to Todo List</Link>
</div>
);
};
Now click on a particular todo, and you should see something like this:
Conclusion
In this article, you learned how to build a Flask and React application by developing directly in Okteto's development environment. You also learned how to deploy docker-compose applications to Okteto. While you could develop locally, by developing directly in Okteto's development environment, you can avoid having to install Docker locally, and can develop your app in a production-like environment. You can find the code used in this article on GitHub.
Have any questions about Okteto? Come join the conversation on the #okteto channel in the Kubernetes Slack workspace.