# 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 corresponding 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)](https://searx.dc09.ru/search?q=mariadb+install+ubuntu)", for example: [an article on DigitalOcean for Ubuntu 22.04](https://url.dc09.ru/mdbu22do) 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. Open `.env` and `.env_db`, generate secret keys and the database password as explained in the comment above `SESSION_KEY=` 10. Edit the `version_code` file if needed, 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` or `make docker-build`|Build a docker image from `Dockerfile`| |`make docker-push`|Upload the built image to Docker Hub| |`make docker-run`|Starts the containers with Docker Compose| |`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 - `version` contains only one variable, your application version code; change it whatever you want or leave `1.0.0` #### 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/__init__.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 deleting/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](https://github.com/muicss/starlette-wtf/blob/master/README.md) 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__.py`s - `main.py` - `common.py` - `respond.py` - `paths/errors.py` ### Database [Related docs page](https://fastapi.tiangolo.com/tutorial/sql-databases/) - 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`) omits `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 constructor. **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 constructor. **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 constructor. **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/__init__.py` Import: `from . import sql` #### `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](https://docs.docker.com/get-started/04_sharing_app/) 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`