2020-02-21T11:06:26Z

How To Create a React + Flask Project

One of the questions I get asked more often lately is how to create a project that combines a React frontend with a Flask backend. Instead of giving vague answers I decided to write a tutorial about it, including a video in which I go through the steps to create a simple but fully functional React+Flask project.

Requirements

You need to install three packages on your machine:

  • Node.js: The JavaScript runtime that you will use to run your frontend project.
  • Yarn: A package and project manager for Node.js applications.
  • Python: A recent Python 3 interpreter to run the Flask backend on.

Please install these three packages using the instructions for your operating system before continuing with the tutorial.

Creating a Starter React Project

There are several ways to create a combined project with React and Flask. I prefer to start from the frontend because the project structure is much more complex than the backend. For this example I used the create-react-app generator to create a simple React project to start from:

$ npx create-react-app react-flask-app
$ cd react-flask-app

The npx command comes with Node.js. It is a simple project runner that downloads the requested command if it isn't already available and in the system's PATH. The first argument is the command to execute. The second argument is the name of the project to create. When this command completes, you will have a react-flask-app directory with a complete and fully functional simple react project.

Since you will work on this project from now on, you can cd into react-flask-app so that it is your current directory. If you list the directory you should see the top-level structure, which should be more or less like this:

$ ls -l
total 912
-rw-r--r--     1 mgrinberg  staff    2884 Feb 10 14:54 README.md
drwxr-xr-x  1027 mgrinberg  staff   32864 Feb 10 15:03 node_modules
-rw-r--r--     1 mgrinberg  staff     890 Feb 10 15:04 package.json
drwxr-xr-x     8 mgrinberg  staff     256 Feb 10 14:54 public
drwxr-xr-x    10 mgrinberg  staff     320 Feb 10 23:50 src
-rw-r--r--     1 mgrinberg  staff  454962 Feb 10 14:54 yarn.lock

Creating a Flask API Backend

The next step is to create the Flask project. Since I want to have both the frontend and backend combined into a single project, my preference here is to add one more top-level subdirectory where the Flask project will live:

$ mkdir api
$ cd api

I always create a virtual environment called venv in my project directory, so let's do that now:

$ python3 -m venv venv
$ source venv/bin/activate
(venv) $ _

Note that the above is for Unix-based operating systems. If you are using Windows, then you will do this instead:

$ python -m venv venv
$ venv\Scripts\activate
(venv) $ _

For this simple example I need only two Python packages, the obvious Flask and also python-dotenv:

(venv) $ pip install flask python-dotenv

The Flask project can have any structure that you like, as long as its root is this new api subdirectory. In particular, you can use large and complex structures such as those in my Flask Mega-Tutorial or O'Reilly Flask book, as well as much simpler single file applications. In the spirit of keeping things simple, for this example I'm going to create a small, single file and single endpoint application. Here is my Flask API project, written as a single file called api.py:

import time
from flask import Flask

app = Flask(__name__)

@app.route('/time')
def get_current_time():
    return {'time': time.time()}

This little API responds to the /time URL with a JSON payload such as this:

{"time": 1581527730.5866282}

If you are surprised that you are not seeing a call to the jsonify() function from Flask, you may not be aware that in recent releases of Flask your view function can return a dictionary, which gets automatically JSONified by Flask.

As you probably know, Flask imports the application from the place indicated by the FLASK_APP environment variable. To avoid having to manually set this variable every time, I'm going to write a .flaskenv file, which Flask automatically imports into the environment on startup if it finds the python-dotenv package installed. Here is my .flaskenv file:

FLASK_APP=api.py
FLASK_ENV=development

Since I'm going to be setting up development environment, I also added the FLASK_ENV variable, with a setting of development, which enables Flask's debug mode.

At this point this basic Flask project is complete. To make sure that it is working well you can start it:

(venv) $ flask run
 * Serving Flask app "api.py" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 673-234-166

To stop the Flask server press Ctrl-C.

Now that the Flask part of this project is complete, let's leave the api subdirectory and go back to the root of the combined project.

(venv) $ cd ..

React Configuration Changes

The React project created by the create-react-app utility left a package.json file with the project's configuration. There are a couple of changes to make in this file to improve the integration between the React and the Flask sides.

The first change is to set up "proxy" redirection from React to Flask. You will see in a moment that the React project will run a web server on port 3000, while Flask runs its own server on port 5000. In most deployments, however, the frontend files and the API endpoints are all served from the same domain and port, which makes everything work seamlessly by avoiding cross-origin issues. The React project can be configured to redirect any requests it receives on its port 3000 that it does not understand into another server. This is configured simply by adding a proxy key at the bottom package.json:

{

  ... leave all other configuration options alone ...

  "proxy": "http://localhost:5000"
}

When you do this, do not forget to add a comma at the end of the previous line, as without that comma the file would not be a valid JSON file.

The other change I like to make is related to management commands. The React application uses yarn as a command manager. For example, the frontend server is started with the yarn start command. There is also a yarn test and a few more commands.

While this is entirely optional, the commands to manage the Flask app can be integrated with yarn as well. Somewhere in the middle of package.json you will find a scripts key. You can add any custom commands inside it:

  "scripts": {
    "start": "react-scripts start",
    "start-api": "cd api && venv/bin/flask run --no-debugger",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },    

Here I've added one new entry called start-api, which I'm going to use to run the Flask server. The value of this key is the command that needs to be executed, which in this case involves changing into the api subdirectory and then running the flask run command.

Note that I have used the virtual environment path for the flask command so that I don't need to have the virtual environment activated. The nice thing about running the command in this way is that in the context of the Flask process all imports will work in the same way as with an activated virtual environment.

The --no-debugger option that I added in the command also deserves a mention. Since this Flask backend is strictly an API server, we will never be serving complete pages, so having the browser-based debugger enabled serves no purpose, as it's just going to mess up the JSON responses that the API returns. You will see stack traces of your errors in the terminal.

Adding the Flask Project to Git

The React starter project includes a git repository, which is actually a nice feature. To make it more friendly to Python there are a couple of things that need to be added to the .gitignore file:

venv
__pycache__

This will prevent the virtual environment and the cache bytecode directories that Python 3 creates from ever getting added to source control. You can add any other things that you need here for your project as well.

Once your .gitignore is configured you can add all the new and modified files to git and commit the Flask backend:

$ git add .gitignore package.json api
$ git commit -m "flask backend"

Running the Combined Project

Okay, now the most exciting part. Let's run the application!

To do this you will need to use two terminal windows. One for the frontend server, and another for the backend. On the first terminal, start the frontend:

$ yarn start

This will take a few seconds and then a browser window will open with the example application from React loaded from http://localhost:3000:

React application screenshot

When you have the frontend running, switch to your second terminal and start the Flask backend at http://localhost:5000:

$ yarn start-api

Now both the frontend and backend are running. The frontend will redirect any requests it does not recognize to the backend. Both are watching their source code files and will restart when changes are made. I find this setup very convenient, because now I can just concentrate on writing code and the two servers refresh automatically as the code changes.

Invoking a Flask Endpoint from React

To complete this project, I'm going to expand the React application with a call to the /time endpoint that I defined in the Flask side. The main source file for the React application is src/App.js. This is this file after I put my changes:

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

function App() {
  const [currentTime, setCurrentTime] = useState(0);

  useEffect(() => {
    fetch('/time').then(res => res.json()).then(data => {
      setCurrentTime(data.time);
    });
  }, []);

  return (
    <div className="App">
      <header className="App-header">

        ... no changes in this part ...

        <p>The current time is {currentTime}.</p>
      </header>
    </div>
  );
}

export default App;

As soon as you save the src/App.js file with the above changes, the application should update and show the current Unix time:

React application screenshot

There are a few small changes here. In the first line I added two new imports, useState and useEffect. I'm going to use the former to add the current time as state within the React application. The latter is used to create a callback that will be invoked when the application renders to the page, which is the time the Flask endpoint needs to be invoked.

In case you are used to writing React applications using classes and components, you should note that you can still do that, but the currently recommended practice is to use a function based approach. JavaScript projects tend to change often, sometimes in drastic ways. You should keep in mind that this isn't the only way to write a React application.

To add state to the application you use the useState() function:

const [currentTime, setCurrentTime] = useState(0);

The function returns two values, a getter and a setter for the new state. The getter is a simple variable, while the setter is a function. Using a setter function is necessary because by invoking the setter React is able to trigger updates in the parts of the application that depend on this state. The 0 that I'm passing as an argument to useState() is the initial value for this state variable.

Now that the state variable exists, it can be added to the template portion of the application:

<p>The current time is {currentTime}.</p>

This can be added anywhere in the template, and in fact, if you prefer you can erase the content used by the default React application and replace it with this.

The final part of this example application is to issue a request from the frontend to the backend:

  useEffect(() => {
    fetch('/time').then(res => res.json()).then(data => {
      setCurrentTime(data.time);
    });
  }, []);

This needs to happen at a specific time when the React application is about to be displayed on the page. The useEffect() function is used to set up a callback function to be invoked when the application needs to prepare itself to render.

The first argument is the callback function. In this function I used fetch() to send the request over to the Flask API. Because of the proxy configuration I don't have to use the port 5000 URL, I can just use /time, which will make the request go to http://localhost:3000/time, before it gets redirected to port 5000. If you decide to not use the proxy feature and send the requests directly to port 5000, you will also need to configure CORS in the Flask server to allow this, since now you will be issuing the request across different origins.

The fetch() function returns a promise, so I set up a completion callback with the then() method. This callback receives the response as res, and then calls the res.json() method to convert the payload to a JavaScript object. This is yet another promise, so I have to chain a second then() with one more callback function. This final callback receives the JSON data from the request, which is an object with a single time attribute. Here I can use the setCurrentTime() setter function to update the currentTime state, which is referenced in the template, so as soon as the state changes the new value will be rendered.

The second argument to useEffect() is optional and can be set to the list of state variables on which this callback depends. In this case I wanted to display the time when the page appears initially and then stay fixed to that, so I'm sending an empty list to eliminate all dependencies. This means that this callback will be invoked on initial rendering and never again. If this argument isn't set, the default is to make this callback dependent on all state variables. This means that when the setCurrentTime() setter is called there will be another call into this function, which will cause the state to change again and keep causing recursive invocations in an endless loop.

Update: I have now written a second part to this tutorial where I show how to deploy this project to a production server.

Conclusion

So that's it, now you know how to create a project that combines React with Flask. As I hinted earlier, this isn't the only way to combine these two frameworks, so I encourage you to experiment with them and find what works best for you.

Do you use a different method? I'd love to know how you think it compares to mine, so let me know below in the comments!

134 comments

  • #101 Miguel Grinberg said 2021-02-16T10:59:35Z

    @Sunny: CORS configuration is required when the application running in the browser sends a request to a server that is not the origin server. The proxy solution makes everything work within the origin server, so CORS is not necessary. When you deploy for production you may or may not need CORS, depending on how you configure your deployment.

  • #102 Dave L said 2021-02-24T16:47:11Z

    Hello, Miguel. Thank you for this excellent post!

    I'm using cygwin on win10, which unfortunately does not allow me to use "cd api && venv/bin/flask run --no-debugger" as the start-api target (the forward slashes get mangled to backslashes, and I'm guessing at that point that doesn't resolve to a proper path). So I'm manually activating the venv and running flask from the 'api' directory:

    (venv) dcl@Dave-PC api 553 > flask run --no-debugger * Serving Flask app "api.py" (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat

    So far, so good!

    But when the first GET /time call comes in, it throws a ModuleNotFoundError. I've trimmed out the paths for readability:

    127.0.0.1 - - [24/Feb/2021 11:21:23] "GET /time HTTP/1.1" 500 - Error on request: Traceback (most recent call last): File "[...path]/internal-server/api/venv/lib/python3.8/site-packages/flask/cli.py", line 240, in locate_app import(module_name) ModuleNotFoundError: No module named 'api'

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last): File "[..path]/internal-server/api/venv/lib/python3.8/site-packages/werkzeug/serving.py", line 323, in run_wsgi execute(self.server.app) File "[...path]/internal-server/api/venv/lib/python3.8/site-packages/werkzeug/serving.py", line 312, in execute application_iter = app(environ, start_response) File "[...path]/internal-server/api/venv/lib/python3.8/site-packages/flask/cli.py", line 338, in call self._flush_bg_loading_exception() File "[...path]/internal-server/api/venv/lib/python3.8/site-packages/flask/cli.py", line 326, in _flush_bg_loading_exception reraise(*exc_info) File "[...path]/internal-server/api/venv/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise raise value File "[...path]/internal-server/api/venv/lib/python3.8/site-packages/flask/cli.py", line 314, in _load_app self._load_unlocked() File "[...path]/internal-server/api/venv/lib/python3.8/site-packages/flask/cli.py", line 330, in _load_unlocked self._app = rv = self.loader() File "[...path]/internal-server/api/venv/lib/python3.8/site-packages/flask/cli.py", line 388, in load_app app = locate_app(self, import_name, name) File "[...path]/internal-server/api/venv/lib/python3.8/site-packages/flask/cli.py", line 250, in locate_app raise NoAppException('Could not import "{name}".'.format(name=module_name)) flask.cli.NoAppException: Could not import "api".

    I suspect I've made a basic error here, but I'm stumped. Do you have any insight?

  • #103 Miguel Grinberg said 2021-02-24T18:08:11Z

    @Dave: Cygwin uses a bash shell, so everything should be forward slashes. May I ask why your prompt has a ">" if you are using Cygwin? Seems to me you are using a Windows command prompt, not bash/Cygwin. Make sure you work on a bash shell from Cygwin and I think things will work better.

  • #104 Tomas said 2021-03-01T13:11:03Z

    Can I do kinda the same for a ready existing microblog project? just copy paste all files from mciroblog inside API folder?

  • #105 Miguel Grinberg said 2021-03-01T15:37:11Z

    @Tomas: I guess you can, but what would be the benefit of doing that? The microblog tutorial implements a traditional web application, using templates and server-side rendering.

  • #106 tomas ambrulaitis said 2021-04-06T20:29:39Z

    In the microblog tutorial we used a bit of javascript and I got wondering, does it mean that MPAs are mostly used with javascript and jquery? Meanwhile in this tutorial its all about SPAs, which are built with vue, react etc?

  • #107 Miguel Grinberg said 2021-04-06T22:47:06Z

    @tomas: A traditional site has the bulk of the page layout implemented by the server through templates. The little bit of interactivity on top of that is added via small JavaScript snippets. Use of jQuery is optional. Single-page apps have the bulk of the application in the client, built with a JavaScript frontend framework, with the server taking a supporting role and providing mostly data and authentication services.

  • #108 John said 2021-04-19T05:51:32Z

    This is awesome Miguel, thank you!

  • #109 Dennis said 2021-05-07T08:04:59Z

    Great tutorial, thanks a lot!

  • #110 Livingstone Mumelo said 2021-06-11T13:12:01Z

    I have an error when I try to run npm start-api. I am on windows when I run its asks me if I meant start which is the frontend. When I use concurrently to run both its gives an error that venv is not recognized as internal or external command

  • #111 Miguel Grinberg said 2021-06-11T14:44:31Z

    @Livingstone: I recommend that you work with the repository for this project on GitHub. My guess is that you have not entered the changes in your package.json file correctly.

  • #112 Amir said 2021-06-23T15:48:17Z

    Thanks for this tutorial! Was super helpful. Do you know how I would be able to create a button that would invoke useEffect so I could update the time without having to refresh the whole page?

    Thanks!

  • #113 Miguel Grinberg said 2021-06-23T17:03:59Z

    @Amir: you don't need useEffect for that. Just use the same function in the useEffect and your button click handler.

  • #114 Mike Heavers said 2021-07-12T21:34:28Z

    I am developing on a mac, and it would not recognize my .flaskenv file unless I ran: python -m flask run which allows you to load and execute your modules.

  • #115 Miguel Grinberg said 2021-07-12T22:20:27Z

    @Mike: Not sure what can cause this. Are you sure when you type "flask" the flask binary from the virtual environment is being invoked?

  • #116 uma said 2021-07-13T23:54:14Z

    When i run yarn start ,it says Yarn.ps1 is not loaded,it is not digitally signed..any solution for this

  • #117 Miguel Grinberg said 2021-07-14T09:59:51Z

    @uma: Try following this tutorial on the WSL so that you can use the Unix version of Node and yarn.

  • #118 uma said 2021-07-14T22:22:30Z

    thanks a lot ,it works,i tried from git bash.

  • #119 Azat385 said 2021-07-26T16:59:43Z

    How to make updating time every second?

  • #120 MOHAMED FAIYAZ said 2021-07-26T18:15:40Z

    cd api && venv/bin/flask run

    when i run npm run start-api i am getting the error error.

    'venv' is not recognized as an internal or external command, operable program or batch file.

  • #121 Miguel Grinberg said 2021-07-26T19:24:29Z

    @Azat385: Use an interval timer.

  • #122 Miguel Grinberg said 2021-07-26T19:25:48Z

    @MOHAMED: Your start api command must have a typo, or maybe if you are doing this on Windows it needs to be adapted to use back slashes.

  • #123 Yasmin Amran said 2021-07-27T07:18:59Z

    genius as usual. What would you suggest for writing socketio games such as multiplayers memory game? Flask + react with blueprints or not. What are the best tools to use in your opinion? should react use also NodeJS or use just flask as the backend? It is important to mansion that socketio must be used.

  • #124 Miguel Grinberg said 2021-07-27T10:23:42Z

    @Yasmin: Questions about "what are the best tools" are impossible to answer objectively. You should pick the tools that you like, not the tools that I like.

  • #125 Ben said 2021-08-26T19:15:50Z

    Thanks so much for writing this and providing a video. I'm exploring ways to escape the typical Flask 'return render_template' to update my HTML files!

    Is it possible for you to share the complete folder/directory structure of Flask and React? I normally install my Flask app environment and dependencies with pipenv, is this possible when incorporating React?

Leave a Comment