# 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.route`s 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](#paths), [custom error pages](#custom-error-pages) and [forms](#wtforms) 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](#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](#publishing-app) 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 #### `.env`s - `.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](#custom-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: ```python 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](https://docs.docker.com/get-started/04_sharing_app/) to upload your image to Docker Hub.