Getting Started

Part 1, Chapter 4


In this chapter, we'll set up the base project structure.


To speed up development, we'll start with a pre-built project that features Flask, React, and Docker.

The code for this comes from the Flask and React microservices that were built in the Test-Driven Development with Python, Flask, and Docker and Authentication with Flask, React, and Docker courses. Be sure to review the courses for more details on how they were implemented.

Start by cloning down the project:

$ git clone https://gitlab.com/testdriven/flask-react-auth.git flask-react-aws
$ cd flask-react-aws

Main Commands

Set the following environment variable to define the base URL for AJAX requests from the React app:

$ export REACT_APP_API_SERVICE_URL=http://localhost:5004

Build the images:

$ docker-compose build

Take a quick look at the project structure, while the images are building:

├── .gitignore
├── .gitlab-ci.yml
├── Dockerfile.deploy
├── README.md
├── docker-compose.yml
├── release.sh
└── services
    ├── client
    │   ├── .dockerignore
    │   ├── .eslintrc.json
    │   ├── .gitignore
    │   ├── Dockerfile
    │   ├── Dockerfile.ci
    │   ├── README.md
    │   ├── coverage
    │   ├── package-lock.json
    │   ├── package.json
    │   ├── public
    │   │   ├── favicon.ico
    │   │   ├── index.html
    │   │   ├── logo192.png
    │   │   ├── logo512.png
    │   │   ├── manifest.json
    │   │   └── robots.txt
    │   └── src
    │       ├── App.jsx
    │       ├── components
    │       │   ├── About.jsx
    │       │   ├── AddUser.jsx
    │       │   ├── LoginForm.jsx
    │       │   ├── Message.jsx
    │       │   ├── NavBar.css
    │       │   ├── NavBar.jsx
    │       │   ├── RegisterForm.jsx
    │       │   ├── UserStatus.jsx
    │       │   ├── UsersList.jsx
    │       │   ├── __tests__
    │       │   │   ├── About.test.jsx
    │       │   │   ├── AddUser.test.jsx
    │       │   │   ├── App.test.jsx
    │       │   │   ├── LoginForm.test.jsx
    │       │   │   ├── Message.test.jsx
    │       │   │   ├── NavBar.test.jsx
    │       │   │   ├── RegisterForm.test.jsx
    │       │   │   ├── UserStatus.test.jsx
    │       │   │   ├── UsersList.test.jsx
    │       │   │   └── __snapshots__
    │       │   │       ├── About.test.jsx.snap
    │       │   │       ├── AddUser.test.jsx.snap
    │       │   │       ├── App.test.jsx.snap
    │       │   │       ├── LoginForm.test.jsx.snap
    │       │   │       ├── Message.test.jsx.snap
    │       │   │       ├── NavBar.test.jsx.snap
    │       │   │       ├── RegisterForm.test.jsx.snap
    │       │   │       ├── UserStatus.test.jsx.snap
    │       │   │       └── UsersList.test.jsx.snap
    │       │   └── form.css
    │       ├── index.js
    │       └── setupTests.js
    ├── nginx
    │   └── default.conf
    └── users
        ├── .coveragerc
        ├── .dockerignore
        ├── Dockerfile
        ├── Dockerfile.prod
        ├── entrypoint.sh
        ├── manage.py
        ├── requirements-dev.txt
        ├── requirements.txt
        ├── setup.cfg
        └── src
            ├── __init__.py
            ├── api
            │   ├── __init__.py
            │   ├── auth.py
            │   ├── ping.py
            │   └── users
            │       ├── __init__.py
            │       ├── admin.py
            │       ├── crud.py
            │       ├── models.py
            │       └── views.py
            ├── config.py
            ├── db
            │   ├── Dockerfile
            │   └── create.sql
            └── tests
                ├── __init__.py
                ├── conftest.py
                ├── pytest.ini
                ├── test_admin.py
                ├── test_auth.py
                ├── test_config.py
                ├── test_ping.py
                ├── test_user_model.py
                ├── test_users.py
                └── test_users_unit.py

Important things to note:

  1. The "services" directory holds the two main services: The server-side Flask API and the client-side React app.
  2. The docker-compose.yml file holds the configuration for building the following images:
    1. api: the Flask app
    2. api-db: the Postgres database
    3. client: the React app

Run the containers once the build is complete:

$ chmod +x services/users/entrypoint.sh
$ docker-compose up -d

Navigate to http://localhost:5004/ping in your browser.

You should see:

{
  "message": "pong!",
  "status": "success"
}

Review the Flask view for this route in the services/users/src/api/ping.py file.

Create the database:

$ docker-compose exec api python manage.py recreate_db

Seed the database:

$ docker-compose exec api python manage.py seed_db

Navigate to http://localhost:5004/users.

You should see:

[
    {
        "id": 1,
        "username": "michael",
        "email": "[email protected]",
        "created_date": "2022-01-20T13:17:54.940708"
    },
    {
        "id": 2,
        "username": "michaelherman",
        "email": "[email protected]",
        "created_date": "2022-01-20T13:17:54.940708"
    }
]

Review the Flask view for this route, along the routes associated with the users endpoints in the services/users/src/api/users/views.py file. Then, check out the Swagger UI at http://localhost:5004/doc.

Navigate to http://localhost:3007.

react user status

Manually test registering a new user, logging in, and logging out.

Review then run the pytest tests with code coverage:

$ docker-compose exec api python -m pytest "src/tests" -p no:warnings --cov="src"

You should see:

======================================= test session starts ========================================
platform linux -- Python 3.10.3, pytest-7.1.1, pluggy-1.0.0
rootdir: /usr/src/app/src/tests, configfile: pytest.ini
plugins: forked-1.4.0, cov-3.0.0, xdist-2.5.0
collected 51 items

src/tests/test_admin.py ..                                                               [  3%]
src/tests/test_auth.py .............                                                     [ 29%]
src/tests/test_config.py ...                                                             [ 35%]
src/tests/test_ping.py .                                                                 [ 37%]
src/tests/test_user_model.py ...                                                         [ 43%]
src/tests/test_users.py ...............                                                  [ 72%]
src/tests/test_users_unit.py ..............                                              [100%]

---------- coverage: platform linux, python 3.10.3-final-0 -----------
Name                        Stmts   Miss Branch BrPart  Cover
-------------------------------------------------------------
src/__init__.py                27      1      2      0    97%
src/api/__init__.py             8      0      0      0   100%
src/api/auth.py                94      6     22      4    91%
src/api/ping.py                 6      0      2      0   100%
src/api/users/__init__.py       0      0      0      0   100%
src/api/users/admin.py         11      1      2      0    92%
src/api/users/crud.py          22      0      0      0   100%
src/api/users/models.py        32      0      6      1    97%
src/api/users/views.py         65      0     14      0   100%
src/config.py                  23      1     10      1    94%
-------------------------------------------------------------
TOTAL                         288      9     58      6    96%


======================================== 51 passed in 1.35s ========================================

Lint the Python code with Flake8:

$ docker-compose exec api flake8 src

Run Black and isort with check options:

$ docker-compose exec api black src --check
$ docker-compose exec api isort src --check-only

Need to make changes based on Black and isort recommendations?

$ docker-compose exec api black src
$ docker-compose exec api isort src

Run the client-side tests with coverage:

$ docker-compose exec client react-scripts test --coverage

Lint the JavaScript:

$ docker-compose exec client npm run lint

Run Prettier:

$ docker-compose exec client npm run prettier:check
$ docker-compose exec client npm run prettier:write

Other Commands

To stop the containers:

$ docker-compose stop

To bring down the containers:

$ docker-compose down

Bring down the containers and volumes:

$ docker-compose down -v

Want to force a build?

$ docker-compose build --no-cache

Remove images:

$ docker rmi $(docker images -q)

Postgres

Want to access the database via psql?

$ docker-compose exec api-db psql -U postgres

Then, you can connect to the database and run SQL queries. For example:

# \c api_dev
# select * from users;

Project Structure

Make sure you have a solid grasp of the project structure along with the above Docker commands. Again, review the Test-Driven Development with Python, Flask, and Docker and Authentication with Flask, React, and Docker courses for more details.

Let's refactor the project structure before configuring AWS for deployment.

Remove the following files:

  • .gitlab-ci.yml
  • Dockerfile.deploy
  • release.sh
  • services/client/Dockerfile.ci
  • services/users/Dockerfile.prod

The structure should now look like:

├── .gitignore
├── README.md
├── docker-compose.yml
└── services
    ├── client
    │   ├── .dockerignore
    │   ├── .eslintrc.json
    │   ├── .gitignore
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── coverage
    │   ├── package-lock.json
    │   ├── package.json
    │   ├── public
    │   │   ├── favicon.ico
    │   │   ├── index.html
    │   │   ├── logo192.png
    │   │   ├── logo512.png
    │   │   ├── manifest.json
    │   │   └── robots.txt
    │   └── src
    │       ├── App.jsx
    │       ├── components
    │       │   ├── About.jsx
    │       │   ├── AddUser.jsx
    │       │   ├── LoginForm.jsx
    │       │   ├── Message.jsx
    │       │   ├── NavBar.css
    │       │   ├── NavBar.jsx
    │       │   ├── RegisterForm.jsx
    │       │   ├── UserStatus.jsx
    │       │   ├── UsersList.jsx
    │       │   ├── __tests__
    │       │   │   ├── About.test.jsx
    │       │   │   ├── AddUser.test.jsx
    │       │   │   ├── App.test.jsx
    │       │   │   ├── LoginForm.test.jsx
    │       │   │   ├── Message.test.jsx
    │       │   │   ├── NavBar.test.jsx
    │       │   │   ├── RegisterForm.test.jsx
    │       │   │   ├── UserStatus.test.jsx
    │       │   │   ├── UsersList.test.jsx
    │       │   │   └── __snapshots__
    │       │   │       ├── About.test.jsx.snap
    │       │   │       ├── AddUser.test.jsx.snap
    │       │   │       ├── App.test.jsx.snap
    │       │   │       ├── LoginForm.test.jsx.snap
    │       │   │       ├── Message.test.jsx.snap
    │       │   │       ├── NavBar.test.jsx.snap
    │       │   │       ├── RegisterForm.test.jsx.snap
    │       │   │       ├── UserStatus.test.jsx.snap
    │       │   │       └── UsersList.test.jsx.snap
    │       │   └── form.css
    │       ├── index.js
    │       └── setupTests.js
    ├── nginx
    │   └── default.conf
    └── users
        ├── .coverage
        ├── .coveragerc
        ├── .dockerignore
        ├── Dockerfile
        ├── entrypoint.sh
        ├── htmlcov
        ├── manage.py
        ├── requirements-dev.txt
        ├── requirements.txt
        ├── setup.cfg
        └── src
            ├── __init__.py
            ├── api
            │   ├── __init__.py
            │   ├── auth.py
            │   ├── ping.py
            │   └── users
            │       ├── __init__.py
            │       ├── admin.py
            │       ├── crud.py
            │       ├── models.py
            │       └── views.py
            ├── config.py
            ├── db
            │   ├── Dockerfile
            │   └── create.sql
            └── tests
                ├── __init__.py
                ├── conftest.py
                ├── pytest.ini
                ├── test_admin.py
                ├── test_auth.py
                ├── test_config.py
                ├── test_ping.py
                ├── test_user_model.py
                ├── test_users.py
                └── test_users_unit.py

Update the README:

# Deploying a Flask and React Microservice to AWS ECS

HTTPie

Test all the routes with HTTPie.

GET all users:

$ http GET http://localhost:5004/users

HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
Content-Length: 316
Content-Type: application/json
Date: Thu, 20 Jan 2022 13:23:58 GMT
Server: Werkzeug/2.0.2 Python/3.10.1

[
    {
        "created_date": "2022-01-20T13:23:50.826961",
        "email": "[email protected]",
        "id": 1,
        "username": "michael"
    },
    {
        "created_date": "2022-01-20T13:23:50.826961",
        "email": "[email protected]",
        "id": 2,
        "username": "michaelherman"
    }
]

GET single user:

$ http GET http://localhost:5004/users/1

HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
Content-Length: 128
Content-Type: application/json
Date: Thu, 20 Jan 2022 13:24:30 GMT
Server: Werkzeug/2.0.2 Python/3.10.1

{
    "created_date": "2022-01-20T13:23:50.826961",
    "email": "[email protected]",
    "id": 1,
    "username": "michael"
}

POST:

$ http --json POST http://localhost:5004/users username=someone email=[email protected] password=foobarfoo

HTTP/1.0 201 CREATED
Access-Control-Allow-Origin: *
Content-Length: 54
Content-Type: application/json
Date: Thu, 20 Jan 2022 13:24:54 GMT
Server: Werkzeug/2.0.2 Python/3.10.1

{
    "message": "[email protected] was added!"
}

PUT:

$ http --json PUT http://localhost:5004/users/3 username=foo email=[email protected]

HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
Content-Length: 36
Content-Type: application/json
Date: Thu, 20 Jan 2022 13:25:30 GMT
Server: Werkzeug/2.0.2 Python/3.10.1

{
    "message": "3 was updated!"
}

DELETE:

$ http DELETE http://localhost:5004/users/3

HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
Content-Length: 46
Content-Type: application/json
Date: Thu, 20 Jan 2022 13:25:53 GMT
Server: Werkzeug/2.0.2 Python/3.10.1

{
    "message": "[email protected] was removed!"
}

Register a new user:

$ http --json POST http://localhost:5004/auth/register username=mike email=[email protected] password=notasecurepassword

HTTP/1.0 201 CREATED
Access-Control-Allow-Origin: *
Content-Length: 62
Content-Type: application/json
Date: Thu, 20 Jan 2022 13:26:19 GMT
Server: Werkzeug/2.0.2 Python/3.10.1

{
    "email": "[email protected]",
    "username": "mike"
}

Log a user in:

$ http --json POST http://localhost:5004/auth/login email=[email protected] password=notasecurepassword

HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
Content-Length: 330
Content-Type: application/json
Date: Thu, 20 Jan 2022 13:26:47 GMT
Server: Werkzeug/2.0.2 Python/3.10.1

{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDI2ODYxMDcsImlhdCI6MTY0MjY4NTIwNywic3ViIjo0fQ.6fubtSXeGKBk7iSrYHDC8L_xmZ-zhUz2SUZ3-5kaeqY",
    "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDUyNzcyMDcsImlhdCI6MTY0MjY4NTIwNywic3ViIjo0fQ.IftKYFQXlkBZhAZErHhC1iVj9zqo7JCY1zoQqt9fzU4"
}

Check user status:

$ http --json GET http://localhost:5004/auth/status "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDI2ODYxMDcsImlhdCI6MTY0MjY4NTIwNywic3ViIjo0fQ.6fubtSXeGKBk7iSrYHDC8L_xmZ-zhUz2SUZ3-5kaeqY"

HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
Content-Length: 62
Content-Type: application/json
Date: Thu, 20 Jan 2022 13:27:42 GMT
Server: Werkzeug/2.0.2 Python/3.10.1

{
    "email": "[email protected]",
    "username": "mike"
}

Obtain a new access token:

$ http --json POST http://localhost:5004/auth/refresh refresh_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDUyNzcyMDcsImlhdCI6MTY0MjY4NTIwNywic3ViIjo0fQ.IftKYFQXlkBZhAZErHhC1iVj9zqo7JCY1zoQqt9fzU4

HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
Content-Length: 330
Content-Type: application/json
Date: Thu, 20 Jan 2022 13:28:26 GMT
Server: Werkzeug/2.0.2 Python/3.10.1

{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDI2ODYyMDYsImlhdCI6MTY0MjY4NTMwNiwic3ViIjo0fQ.WiYV43CBmDPMuIMK_alkOphKwiWxgrB2Tg-vIdtequ4",
    "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDUyNzczMDYsImlhdCI6MTY0MjY4NTMwNiwic3ViIjo0fQ.ED-G_K2v2mPeh79dgs3yYItHXWMw5gAb7X6tHJITqR4"
}

GitHub

Finally, create a new project on GitHub and update the Git remote. Push your project up to GitHub once done.

Why GitHub?

For those that have gone through the Test-Driven Development with Python, Flask, and Docker and Authentication with Flask, React, and Docker courses, the move from GitLab to GitHub is so that we can incorporate AWS CodeBuild into the flow. CodeBuild is a powerful continuous integration and build tool that works really well the Docker orchestration tools on AWS. Unfortunately, as of writing, there's no easy way to integrate GitLab with CodeBuild.




Mark as Completed