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-03-01 19:46:52 +04:00
.gitea .gitea/template: remove files which should not be processed 2023-03-01 15:56:44 +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 Readme: mariadb note, wtforms and database sections, app publishing; Makefile: docker-push 2023-03-01 19:39:12 +04:00
mypy.ini Checked with MyPy, Pylint; updated Pylint config 2023-02-27 19:48:34 +04:00
pylintrc Pylint: disabled docstrings check 2023-03-01 19:46:52 +04:00
README.md Readme: line wrap 2023-03-01 19:43:27 +04:00
requirements.txt WTForms, SQLAlchemy 2023-02-20 12:09:43 +04:00
version_code Docker image version code 2023-03-01 16:05:20 +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
    1. If you have no MariaDB/MySQL test server installed, search for "mariadb install (your linux distro)",
      for example: an article on DigitalOcean for Ubuntu 22.04
  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. Edit the version_code file if needed, 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

Related docs page

Each file in the app/forms directory contains a WTForms class (or multiple classes).
You can simply copy users.py content to create a form and use it in your project.

Also, there is one helper function in __init__.py named get_form for instantiating WTForms class.
It should be preferred to the direct constructor call.

Sample website

Included templates and CSS/JS files, paths, forms and SQL database are related to the sample website (for showing how the template works) and can be deleted or edited, except:

  • __init__.pys
  • main.py
  • common.py
  • respond.py
  • paths/errors.py

Database

Related docs page

  • SQLAlchemy models are stored in app/sql/models.py
  • Pydantic models (schemas) used for more handy SQLAlchemy models processing are stored in app/sql/schemas.py
  • Functions executing SQL CRUD requests are stored in app/sql/crud.py

Note

As the official FastAPI documentation recommends (the link is above), there are two schemas — User and UserCreate — for one User SQLAlchemy model. The first one is needed for SELECT requests, and the second one is for INSERT requests (creating users). The first one contains all information about user, but the second one (UserCreate) ommits id field, because we don't want to specify an ID when creating a new user, it will be generated automatically by the MariaDB server.

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, check the version_code file and correct it if needed,
Then build a Docker image using make docker command.

Follow this documentation page to create an account in the Docker Hub.

To publish the image, you can use make docker-push command,
or manually enter the image name and its tag (version): docker push yourname/yourapp:tag