Simple and handy FastAPI template for combined frontend and backend as in Flask, includes Jinja, WTForms, MySQL ORM and Docker
Find a file Use this template
2023-02-27 20:11:55 +04:00
.gitea Basic app, mainly copied from tmpl-flask 2023-02-18 20:01:13 +04:00
app sql.db -> sql(.init) 2023-02-27 20:11:55 +04:00
static WTForms, SQLAlchemy 2023-02-20 12:09:43 +04:00
templates WTForms, SQLAlchemy 2023-02-20 12:09:43 +04:00
.env MySQL database 2023-02-19 17:49:44 +04:00
.env_db MySQL database 2023-02-19 17:49:44 +04:00
.env_debug Loading .env_debug in Makefile; make clean command bugfix 2023-02-27 18:49:30 +04:00
.gitignore Basic app, mainly copied from tmpl-flask 2023-02-18 20:01:13 +04:00
docker-compose.yml Basic app, mainly copied from tmpl-flask 2023-02-18 20:01:13 +04:00
Dockerfile Basic app, mainly copied from tmpl-flask 2023-02-18 20:01:13 +04:00
LICENSE Initial commit 2023-02-18 18:32:41 +03:00
main.py Basic app, mainly copied from tmpl-flask 2023-02-18 20:01:13 +04:00
Makefile Loading .env_debug in Makefile; make clean command bugfix 2023-02-27 18:49:30 +04:00
mypy.ini Checked with MyPy, Pylint; updated Pylint config 2023-02-27 19:48:34 +04:00
pylintrc Checked with MyPy, Pylint; updated Pylint config 2023-02-27 19:48:34 +04:00
README.md Readme: updated info about debug env 2023-02-27 18:51:08 +04:00
requirements.txt WTForms, SQLAlchemy 2023-02-20 12:09:43 +04:00

FastAPI template

It is a simple and handy FastAPI template for combined frontend and backend as in Flask.
Includes Jinja, WTForms, MySQL ORM and Docker.

Features

  • Basic FastAPI app
  • @app.routes are defined in separate files
  • Lots of helper functions
  • Jinja2 templates, WTForms
  • MySQL/MariaDB database, SQLAlchemy 2
  • AutoPEP8 formatter, MyPy and Pylint
  • Dockerfile, Docker-Compose

Usage

  1. Create a repository from this template
  2. For debugging, change the database connection parameters in .env_debug file correspoding to the configuration of MariaDB/MySQL server on your PC
  3. Edit app paths, custom error pages and forms as explained in the "Structure" section
  4. Create your own Jinja templates in the templates/ directory, editing base.html and inheriting other templates from it is recommended (see index,table,admin.html for examples)
  5. Edit or remove CSS and JS files in static/
  6. Edit sql/models,schemas,crud.py corresponding to your database structure as explained below (Structure > Database)
  7. Check if Makefile, Dockerfile, docker-compose.yml are correct
  8. Run the formatter and linters (make format, then make check)
  9. Build a docker image and publish it

Makefile

Make commands:

Command Description
make format Format the code using AutoPEP8
make check Check the code with linters (MyPy, Pylint)
make dev Run the app in development mode
make prod Run a production server
make docker Build a docker image from Dockerfile
make clean Clean all cache

Structure

Configuration

.envs

  • .env_debug contains the DB configuration and tokens for the development environment, must not be used in production
  • .env is a config for the application itself, loaded only in docker-compose by default
  • .env_db is a config for MySQL/MariaDB server, also loaded only in docker-compose for the mariadb container

The main config loaded by app/common.py:

  • templates_dir, static_dir contain the paths to templates and static files directories correspondingly
  • settings (pydantic.BaseSettings):
    • debug means the application is running in a development environment
    • session_key, csrf_key are secret keys for WTForms CSRF protection;
      generated with secrets.token_urlsafe if are not set in the environment variables
  • templates is a Jinja2Templates FastAPI object, shouldn't be used manually, see Helper Functions -> respond.with_tmpl below

The database config loaded by sql/db.py:

  • sql_settings (pydantic.BaseSettings):
    • db_host
    • db_port
    • db_user
    • db_password
    • db_database
  • db_url is the MySQL connection URL generated from the sql_settings configuration;
    just edit the line declaring db_url in db.py if you are going to use other DBMS, e.g. PostgreSQL

Paths

app/paths directory contains all FastAPI paths separated into multiple files.
Each file have a class inside with the add_paths method.
This method is called on application startup.

You can

  • add your own paths to the existing files
  • rename or delete a file in app/paths/ (it's not recommended to do this with errors.py, see about error pages below)
  • create a new file in this directory copying the contents of pages.py

Note

In the paths files, FastAPI's decorators are called with @self.app., not just @app.

In case of deleteing/renaming/creating any paths files, app/main.py also must be modified:

  1. Find the comment # Add your own paths...
  2. Add or remove import statements below
  3. Add or remove elements in paths list below

Custom Error Pages

app/paths/errors.py automatically adds error handlers when the application launches.
By default, 404 and 500 HTTP codes are configured for the custom pages (templates/404.html and 500.html), but you can add your own:

  1. Open errors.py
  2. Find the comment # Add other...
  3. Add (or remove) elements to the list below

WTForms

TODO

Database

TODO

Helper functions

respond.py

Import: from . import respond

with_redirect(url=/, code=200, ...)

Return a redirect to the page specified in url.
By default, code is 302 so method is changed to GET. To leave the same HTTP method, use 307 status code or call with_redirect_307 function.
args and kwargs are passed directly to the Response contructor.

Args: url (str, optional): Target URL, root by default code (int, optional): HTTP response code

Returns: FastAPI's RedirectResponse object

with_redirect_302(url=/, ...)

with_redirect_307(url=/, ...)

As said before,

  • HTTP 302 Found redirects to a page without saving the same method as in the current request
  • HTTP 307 Temporary Redirect doesn't change the method (POST request = POST method after redirect)

with_text(content, code=200, ...)

Return a plain text to the user.
args and kwargs are passed directly to the Response contructor.

Args: content (str): Plain text content code (int, optional): HTTP response code

Returns: FastAPI's PlainTextResponse object

with_template(name, request, code=200, ..., **context)

with_tmpl(name, request, code=200, ..., **context)

Render a Jinja2 template and return Response object.
response_class parameter is not needed.
A small explanation about the request function argument:

from fastapi import Request
from . import respond

@app.get('/')
async def main_page(req: Request):  # <--
    return respond.with_tmpl(
        'index.html',
        request=req,  # <--
        ...
    )

FastAPI will automatically pass the Request object to your function if you specify the correct type hint (: Request)

Args: name (str): Template filename request (Request): FastAPI request object code (int, optional): HTTP response code headers (Optional[Mapping[str, str]], optional): Additional headers, passed to the Response constructor background (Optional[BackgroundTask], optional): Background task, passed to the Response constructor

Returns: FastAPI's TemplateResponse object

with_file(path, mime=None, code=200, ...)

Send the file specified in path automatically guessing its mimetype if mime is None.
args and kwargs are passed directly to the Response contructor.

Args: path (os.PathLike): File path mime (Optional[str], optional): File mimetype code (int, optional): HTTP response code

Returns: FileResponse: FastAPI's FileResponse object

forms/__init__.py

Import: from . import forms

async get_form(form, req)

Almost the same as form.from_formdata, and must be used instead of instantiating form object directly as in Flask.
See respond.with_tmpl for explanation about the request argument.

Args: form (Type[StarletteForm]): StarletteForm class req (Request): Request object

Returns: StarletteForm instance

sql/db.py

async get_db()

FastAPI dependency returning database Session object.
Code is copied from the official docs.

Yields: SQLAlchemy Session object

Publishing app

First of all, build an image: make docker
Follow this documentation page to upload your image to Docker Hub.