A generic API built using Flask, Redis, MySQL, Gunicorn, and NGINX.
To, as a learning experience, create an API that I can easily spin up with the minimal requirements I feel I will frequently need, and to design the controllers (API endpoint code) in such a way that I only need to add/remove/modify the database models code to add/remove/modify API endpoints.
Steps to start running this API...
- Clone the repo
git clone https://github.com/wafer-bw/generic-flask-api.git
- Enter the repo directory
cd generic-flask-api
- Setup a virtual environment
python3 -m virtualenv env
- Activate the virtual environment
. env/bin/activate
- Install requirements/dependencies
pip install -r api/requirements.txt
Run API in a dev environment with Flask's debug mode features
- Configure database name, username, and password by updating the below environment variables in /api/environments/dev.env.
DB_NAME
DB_USER
MYSQL_ROOT_PASSWORD
- Get the MySQL and Redis docker containers going.
docker-compose -f docker-compose-dev.yml up --build -d
- Enter API app directory.
cd api
- Run API app.
python run_app_dev.py
- View usage help.
python run_app_dev.py -h
Run API in a production environment setting using NGINX
and Gunicorn
.
- Configure database name, username, and password by updating the below environment variables in /api/environments/prod.env.
DB_NAME
DB_USER
MYSQL_ROOT_PASSWORD
- Run API - Flask APP (Gunicorn), Redis, MySQL, and NGINX.
docker-compose up --build -d
Run API in a dev environment with Flask's debug mode features on an ARM device like a Raspberry Pi.
- Configure database name, username, and password by updating the below environment variables in /api/environments/dev.env.
DB_NAME
DB_USER
MYSQL_ROOT_PASSWORD
- Get the MySQL and Redis docker containers going.
docker-compose -f docker-compose-arm-dev.yml up --build -d
- Enter API app directory.
cd api
- Run API app.
python run_app_dev.py
- View usage help.
python run_app_dev.py -h
Run API in a production environment setting using nginx
and gunicorn
on an ARM device like a Raspberry Pi.
- Configure database name, username, and password by updating the below environment variables in /api/environments/prod.env.
DB_NAME
DB_USER
MYSQL_ROOT_PASSWORD
- Run API - Flask APP (Gunicorn), Redis, MySQL, and NGINX.
docker-compose -f docker-compose-arm-prod.yml up --build -d
Once the app is up and running, try going through these examples. The examples are given assuming you are running the API on port 8000 (Development Environment Defaults).
- Check that things are working at http://localhost:8000. You should see something like this:
{"http_code":200,"msg":"Hello World!","pagination":{},"results":[],"success":true}
- Create a couple fruits. Run this a second time to see what happens when you try to create something that already exists.
curl --request POST -H "Content-Type: application/json" -d '{"name":"Bananas"}' "http://localhost:8000/fruits" curl --request POST -H "Content-Type: application/json" -d '{"name":"Blueberry"}' "http://localhost:8000/fruits"
- Query the fruits by ID. These pages are cached as per the
CACHE_DEFAULT_TIMEOUT
environment variable defined in either /api/environments/dev.env or /api/environments/prod.env.- http://localhost:8000/fruits/1
- http://localhost:8000/fruits/2
- http://localhost:8000/fruits/3 (Unless you created more than two, this will respond with a 404 message stating the fruit doesn't exist)
- View all existing fruits (paginated and cached per query params):
- Update a fruit by id:
- Check the fruit by id here http://localhost:8000/fruits/1, this result will now be cached.
- Update the fruit.
curl --request PUT -H "Content-Type: application/json" -d '{"name":"Banana"}' http://localhost:8000/fruits/1
- See that it's still cached here http://localhost:8000/fruits/1
- After the default cache expiry time (
CACHE_DEFAULT_TIMEOUT
) has passed, check back at http://localhost:8080/fruits/1 - it will be updated!
- Delete a fruit by id:
curl --request DELETE http://localhost:8000/fruits/1
- This API also has a users model as an example. You can create a user with:
curl --request POST -H "Content-Type: application/json" -d '{"email":"someemail@gmail.com", "password": "somepassword"}' "http://localhost:8000/users"
An example of how the models in this API are generic using the Development Environment...
- Copy the fruits model, creating a veggies model:
cp api/app/models/fruits.py api/app/models/veggies.py
- Edit api/app/models/veggies.py
- Modify the line
class Fruits(Model):
toclass Veggies(Model):
- Modify the line
- Edit api/app/models/__init__.py
- Add
from app.models.veggies import Veggies
to the imports. - Add
"veggies": Veggies
to themodels
dictionary.
- Add
- Create a veggie:
curl --request POST -H "Content-Type: application/json" -d '{"name":"Potato"}' "http://localhost:8000/veggies"
Steps to get a backup mysqldump .sql
file and apply it to a running mysql database, thanks to spalladino
's suggestion here.
- You can obtain the
mysql_container_id
mentioned in the following steps usingdocker ps
- Get a mysqldump backup
.sql
file by using:docker exec {mysql_container_id} /usr/bin/mysqldump -u{user} -p{password} {database_name} > backup.sql
- You may need to remove a line at the top of your
backup.sql
file warning you about CLI password usage, it will look like this:mysqldump: [Warning] Using a password on the command line interface can be insecure.
- To restore / apply the dump to a database, use:
cat backup.sql | docker exec -i {mysql_container_id} /usr/bin/mysql -u{user} -p{password} {database_name}
Steps to use Flask-Migrate to apply schema changes.
- Make sure you're in the repo directory then...
cd api
- Set the FLASK_APP environment variable for Flask-Migrate to use like:
export FLASK_APP='app.factory:create_app("Dev")'
- Initialize in case it isn't initialized already with:
flask db init
- Generate migrations with
flask db migrate
- If you get an error like:
Error: Can't locate revision identified by
then you'll need to run this while selecting your DB in the mysql CLIdelete from alembic_version;
- If you get an error like:
- As per the Flask-Migrate documentation:
The migration script needs to be reviewed and edited, as Alembic currently does not detect every change you make to your models. In particular, Alembic is currently unable to detect table name changes, column name changes, or anonymously named constraints.
- Apply the migration using:
flask db upgrade