diff --git a/README.md b/README.md index c204737..5ae4f46 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,31 @@ # FastAPI, Jinja2, PostgreSQL Webapp Template + ![Screenshot of homepage](docs/static/Screenshot.png) -## Documentation +## Quickstart -This README provides a high-level overview. See the **[full documentation website](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/)** for more detailed information on installation, features, architecture, conventions and code style, customization, and deployment to cloud platforms. +This quickstart guide provides a high-level overview. See the full +documentation for comprehensive information on +[features](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/index.html), +[installation](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/installation.html), +[architecture](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/architecture.html), +[conventions, code style, and +customization](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/customization.html), +[deployment to cloud +platforms](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/deployment.html), +and +[contributing](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/contributing.html). ## Features -*FastAPI, Jinja2, PostgreSQL Webapp Template* combines three of the most lightweight and performant open-source web development frameworks in existence into a customizable webapp template with: +This template combines three of the most lightweight and performant +open-source web development frameworks into a customizable webapp +template with: - Pure Python backend -- Low-Javascript frontend -- Powerful, easy-to-manage database layer +- Minimal-Javascript frontend +- Powerful, easy-to-manage database The template also includes full-featured secure auth with: @@ -20,29 +33,44 @@ The template also includes full-featured secure auth with: - Password recovery flow - Role-based access control system -The design philosophy of the template is to prefer low-level, best-in-class open-source frameworks that offer flexibility, scalability, and performance without vendor-lock-in. You'll find the template amazingly easy not only to understand and customize, but also to deploy to any major cloud hosting platform. +## Design Philosophy + +The design philosophy of the template is to prefer low-level, +best-in-class open-source frameworks that offer flexibility, +scalability, and performance without vendor-lock-in. You’ll find the +template amazingly easy not only to understand and customize, but also +to deploy to any major cloud hosting platform. -## Tech stack +## Tech Stack **Core frameworks:** -- [FastAPI](https://fastapi.tiangolo.com/): scalable, high-performance, type-annotated Python web backend framework -- [PostgreSQL](https://www.postgresql.org/): the world's most advanced open-source database engine -- [Jinja2](https://jinja.palletsprojects.com/en/3.1.x/): frontend HTML templating engine + +- [FastAPI](https://fastapi.tiangolo.com/): scalable, high-performance, + type-annotated Python web backend framework +- [PostgreSQL](https://www.postgresql.org/): the world’s most advanced + open-source database engine +- [Jinja2](https://jinja.palletsprojects.com/en/3.1.x/): frontend HTML + templating engine - [SQLModel](https://sqlmodel.tiangolo.com/): easy-to-use Python ORM **Additional technologies:** + - [Poetry](https://python-poetry.org/): Python dependency manager - [Pytest](https://docs.pytest.org/en/7.4.x/): testing framework - [Docker](https://www.docker.com/): development containerization - [Github Actions](https://docs.github.com/en/actions): CI/CD pipeline -- [Quarto](https://quarto.org/docs/): simple documentation website renderer -- [MyPy](https://mypy.readthedocs.io/en/stable/): static type checker for Python +- [Quarto](https://quarto.org/docs/): simple documentation website + renderer +- [MyPy](https://mypy.readthedocs.io/en/stable/): static type checker + for Python - [Bootstrap](https://getbootstrap.com/): HTML/CSS styler -- [Resend](https://resend.com/): zero- or low-cost email service used for password recovery +- [Resend](https://resend.com/): zero- or low-cost email service used + for password recovery -## Quickstart +## Installation -For comprehensive installation instructions, see the [documentation website](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/). +For comprehensive installation instructions, see the [installation +page](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/installation.html). ### Python and Docker @@ -87,17 +115,22 @@ poetry install poetry shell ``` -(Note: You will need to activate the shell every time you open a new terminal session. Alternatively, you can use the `poetry run` prefix before other commands to run them without activating the shell.) +(Note: You will need to activate the shell every time you open a new +terminal session. Alternatively, you can use the `poetry run` prefix +before other commands to run them without activating the shell.) ### Set environment variables Copy .env.example to .env with `cp .env.example .env`. -Generate a 256 bit secret key with `openssl rand -base64 32` and paste it into the .env file. +Generate a 256 bit secret key with `openssl rand -base64 32` and paste +it into the .env file. Set your desired database name, username, and password in the .env file. -To use password recovery, register a [Resend](https://resend.com/) account, verify a domain, get an API key, and paste the API key into the .env file. +To use password recovery, register a [Resend](https://resend.com/) +account, verify a domain, get an API key, and paste the API key into the +.env file. ### Start development database @@ -107,7 +140,8 @@ docker compose up -d ### Run the development server -Make sure the development database is running and tables and default permissions/roles are created first. +Make sure the development database is running and tables and default +permissions/roles are created first. ``` bash uvicorn main:app --host 0.0.0.0 --port 8000 --reload @@ -123,8 +157,12 @@ mypy . ### Contributing -Your contributions are welcome! See the [issues page](https://github.com/promptly-technologies-llc/fastapi-jinja2-postgres-webapp/issues) for ideas. Fork the repository, create a new branch, make your changes, and submit a pull request. +Your contributions are welcome! See the [issues +page](https://github.com/promptly-technologies-llc/fastapi-jinja2-postgres-webapp/issues) +for ideas. Fork the repository, create a new branch, make your changes, +and submit a pull request. ### License -This project is licensed under the MIT License. See the LICENSE file for more details. +This project is licensed under the MIT License. See the LICENSE file for +more details. diff --git a/_quarto.yml b/_quarto.yml index b9083ff..875ef8a 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -6,14 +6,14 @@ website: title: "FastAPI Webapp Template" navbar: left: - - href: docs/index.qmd + - href: index.qmd text: Home - href: docs/architecture.qmd text: Architecture - - href: docs/installation.qmd - text: Installation - href: docs/authentication.qmd text: Authentication + - href: docs/installation.qmd + text: Installation - href: docs/customization.qmd text: Customization - href: docs/deployment.qmd diff --git a/docs/architecture.qmd b/docs/architecture.qmd index 3849176..cf44957 100644 --- a/docs/architecture.qmd +++ b/docs/architecture.qmd @@ -2,7 +2,7 @@ title: "Architecture" --- -## Architecture +## Data flow This application uses a Post-Redirect-Get (PRG) pattern. The user submits a form, which sends a POST request to a FastAPI endpoint on the server. The database is updated, and the user is redirected to a GET endpoint, which fetches the updated data and re-renders the Jinja2 page template with the new data. @@ -12,18 +12,34 @@ This application uses a Post-Redirect-Get (PRG) pattern. The user submits a form from graphviz import Digraph dot = Digraph() - -dot.node('A', 'User submits form') -dot.node('B', 'HTML/JS form validation') -dot.node('C', 'Convert to Pydantic model') -dot.node('D', 'Optional custom validation') -dot.node('E', 'Update database') -dot.node('F', 'Middleware error handler') -dot.node('G', 'Render error template') -dot.node('H', 'Redirect to GET endpoint') -dot.node('I', 'Fetch updated data') -dot.node('J', 'Re-render Jinja2 page template') - +dot.attr(rankdir='TB') +dot.attr('node', shape='box', style='rounded') + +# Create client subgraph at top +with dot.subgraph(name='cluster_client') as client: + client.attr(label='Client') + client.attr(rank='topmost') + client.node('A', 'User submits form', fillcolor='lightblue', style='rounded,filled') + client.node('B', 'HTML/JS form validation', fillcolor='lightblue', style='rounded,filled') + +# Create server subgraph below +with dot.subgraph(name='cluster_server') as server: + server.attr(label='Server') + server.node('C', 'Convert to Pydantic model', fillcolor='lightgreen', style='rounded,filled') + server.node('D', 'Optional custom validation', fillcolor='lightgreen', style='rounded,filled') + server.node('E', 'Update database', fillcolor='lightgreen', style='rounded,filled') + server.node('F', 'Middleware error handler', fillcolor='lightgreen', style='rounded,filled') + server.node('G', 'Render error template', fillcolor='lightgreen', style='rounded,filled') + server.node('H', 'Redirect to GET endpoint', fillcolor='lightgreen', style='rounded,filled') + server.node('I', 'Fetch updated data', fillcolor='lightgreen', style='rounded,filled') + server.node('K', 'Re-render Jinja2 page template', fillcolor='lightgreen', style='rounded,filled') + +with dot.subgraph(name='cluster_client_post') as client_post: + client_post.attr(label='Client') + client_post.attr(rank='bottommost') + client_post.node('J', 'Display rendered page', fillcolor='lightblue', style='rounded,filled') + +# Add visible edges dot.edge('A', 'B') dot.edge('B', 'A') dot.edge('B', 'C', label='POST Request to FastAPI endpoint') @@ -33,12 +49,14 @@ dot.edge('D', 'E', label='Valid data') dot.edge('D', 'F', label='Custom Validation Error') dot.edge('E', 'H', label='Data updated') dot.edge('H', 'I') -dot.edge('I', 'J') +dot.edge('I', 'K') +dot.edge('K', 'J', label='Return HTML') dot.edge('F', 'G') +dot.edge('G', 'J', label='Return HTML') -dot.render('static/webapp_flow', format='png', cleanup=True) +dot.render('static/data_flow', format='png', cleanup=True) ``` -![Webapp Flow](static/webapp_flow.png) +![Data flow diagram](static/data_flow.png) The advantage of the PRG pattern is that it is very straightforward to implement and keeps most of the rendering logic on the server side. The disadvantage is that it requires an extra round trip to the database to fetch the updated data, and re-rendering the entire page template may be less efficient than a partial page update on the client side. \ No newline at end of file diff --git a/docs/authentication.qmd b/docs/authentication.qmd index 6468023..c292746 100644 --- a/docs/authentication.qmd +++ b/docs/authentication.qmd @@ -2,7 +2,7 @@ title: "Authentication" --- -## Authentication Flow +## Security features This template implements a comprehensive authentication system with security best practices: @@ -30,9 +30,9 @@ This template implements a comprehensive authentication system with security bes - Security-related errors don't leak information - Comprehensive error logging -The diagrams below show the main authentication flows and security measures. +The diagrams below show the main authentication flows. -### Registration and Login Flow +### Registration and login flow ``` {python} #| echo: false @@ -47,25 +47,25 @@ auth.attr('node', shape='box', style='rounded') # Client-side nodes with auth.subgraph(name='cluster_client') as client: client.attr(label='Client') - client.node('register_form', 'Submit registration') - client.node('login_form', 'Submit login') - client.node('store_cookies', 'Store secure cookies') + client.node('register_form', 'Submit registration', fillcolor='lightblue', style='rounded,filled') + client.node('login_form', 'Submit login', fillcolor='lightblue', style='rounded,filled') + client.node('store_cookies', 'Store secure cookies', fillcolor='lightblue', style='rounded,filled') # Server-side nodes with auth.subgraph(name='cluster_server') as server: server.attr(label='Server') # Registration path - server.node('validate_register', 'Validate registration data') - server.node('hash_new', 'Hash new password') - server.node('store_user', 'Store user in database') + server.node('validate_register', 'Validate registration data', fillcolor='lightgreen', style='rounded,filled') + server.node('hash_new', 'Hash new password', fillcolor='lightgreen', style='rounded,filled') + server.node('store_user', 'Store user in database', fillcolor='lightgreen', style='rounded,filled') # Login path - server.node('validate_login', 'Validate login data') - server.node('verify_password', 'Verify password hash') - server.node('fetch_user', 'Fetch user from database') + server.node('validate_login', 'Validate login data', fillcolor='lightgreen', style='rounded,filled') + server.node('verify_password', 'Verify password hash', fillcolor='lightgreen', style='rounded,filled') + server.node('fetch_user', 'Fetch user from database', fillcolor='lightgreen', style='rounded,filled') # Common path - server.node('generate_tokens', 'Generate JWT tokens') + server.node('generate_tokens', 'Generate JWT tokens', fillcolor='lightgreen', style='rounded,filled') # Registration path auth.edge('register_form', 'validate_register', 'POST /register') @@ -85,9 +85,9 @@ auth.edge('generate_tokens', 'store_cookies', 'Set-Cookie') auth.render('static/auth_flow', format='png', cleanup=True) ``` -![Registration and Login Flow](static/auth_flow.png) +![Registration and login flow](static/auth_flow.png) -### Password Reset Flow +### Password reset flow ``` {python} #| echo: false @@ -99,19 +99,17 @@ reset = Digraph(name='reset_flow') reset.attr(rankdir='TB') reset.attr('node', shape='box', style='rounded') -# Client-side nodes -reset.attr(label='Client') -reset.node('forgot', 'User submits forgot password form') -reset.node('reset', 'User submits reset password form') -reset.node('email_client', 'User clicks reset link') +# Client-side nodes - using light blue fill +reset.node('forgot', 'User submits forgot password form', fillcolor='lightblue', style='rounded,filled') +reset.node('reset', 'User submits reset password form', fillcolor='lightblue', style='rounded,filled') +reset.node('email_client', 'User clicks reset link', fillcolor='lightblue', style='rounded,filled') -# Server-side nodes -reset.attr(label='Server') -reset.node('validate', 'Validation') -reset.node('token_gen', 'Generate reset token') -reset.node('hash', 'Hash password') -reset.node('email_server', 'Send email with Resend') -reset.node('db', 'Database', shape='cylinder') +# Server-side nodes - using light green fill +reset.node('validate', 'Validation', fillcolor='lightgreen', style='rounded,filled') +reset.node('token_gen', 'Generate reset token', fillcolor='lightgreen', style='rounded,filled') +reset.node('hash', 'Hash password', fillcolor='lightgreen', style='rounded,filled') +reset.node('email_server', 'Send email with Resend', fillcolor='lightgreen', style='rounded,filled') +reset.node('db', 'Database', shape='cylinder', fillcolor='lightgreen', style='filled') # Add edges with labels reset.edge('forgot', 'token_gen', 'POST') @@ -126,4 +124,4 @@ reset.edge('hash', 'db', 'Update') reset.render('static/reset_flow', format='png', cleanup=True) ``` -![Password Reset Flow](static/reset_flow.png) +![Password reset flow](static/reset_flow.png) diff --git a/docs/contributing.qmd b/docs/contributing.qmd index b0e41ae..034c882 100644 --- a/docs/contributing.qmd +++ b/docs/contributing.qmd @@ -8,12 +8,17 @@ Fork the repository, create a new branch, make your changes, and submit a pull r ### Render the documentation -The documentation is rendered with [Quarto](https://quarto.org/docs/). Make changes to the `.qmd` files in the `docs` folder. Then run the following command to render: +The README and documentation website are rendered with [Quarto](https://quarto.org/docs/). Make changes to the `.qmd` files in the root folder and the `docs` folder. Then run the following commands to render: ``` bash +# To render the documentation website quarto render +# To render the README +quarto render index.qmd --output-dir . --output README.md --to gfm ``` +Due to a quirk of Quarto, an unnecessary `index.html` file is created in the root folder when the README is rendered. This file can be safely deleted. + ## Maintainers ### Increment the version diff --git a/docs/index.qmd b/docs/index.qmd deleted file mode 100644 index 2974b31..0000000 --- a/docs/index.qmd +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: "FastAPI, Jinja2, PostgreSQL Webapp Template" ---- - -![Screenshot of homepage](static/Screenshot.png) - -## Features - -This template combines three of the most lightweight and performant open-source web development frameworks into a customizable webapp template with: - -- Pure Python backend -- Low-Javascript frontend -- Powerful, easy-to-manage database layer - -The template also includes full-featured secure auth with: - -- Token-based authentication -- Password recovery flow -- Role-based access control system - -## Design Philosophy - -The design philosophy of the template is to prefer low-level, best-in-class open-source frameworks that offer flexibility, scalability, and performance without vendor-lock-in. You'll find the template amazingly easy not only to understand and customize, but also to deploy to any major cloud hosting platform. - -## Tech Stack - -**Core frameworks:** -- [FastAPI](https://fastapi.tiangolo.com/): scalable, high-performance, type-annotated Python web backend framework -- [PostgreSQL](https://www.postgresql.org/): the world's most advanced open-source database engine -- [Jinja2](https://jinja.palletsprojects.com/en/3.1.x/): frontend HTML templating engine -- [SQLModel](https://sqlmodel.tiangolo.com/): easy-to-use Python ORM - -**Additional technologies:** -- [Poetry](https://python-poetry.org/): Python dependency manager -- [Pytest](https://docs.pytest.org/en/7.4.x/): testing framework -- [Docker](https://www.docker.com/): development containerization -- [Github Actions](https://docs.github.com/en/actions): CI/CD pipeline -- [Quarto](https://quarto.org/docs/): simple documentation website renderer -- [MyPy](https://mypy.readthedocs.io/en/stable/): static type checker for Python -- [Bootstrap](https://getbootstrap.com/): HTML/CSS styler -- [Resend](https://resend.com/): zero- or low-cost email service used for password recovery - -## License - -This project is developed and maintained by Promptly Technologies, LLC and licensed under the open-source MIT License. See the LICENSE file for more details. diff --git a/docs/static/auth_flow.png b/docs/static/auth_flow.png index 0eb77dc..2f331be 100644 Binary files a/docs/static/auth_flow.png and b/docs/static/auth_flow.png differ diff --git a/docs/static/data_flow.png b/docs/static/data_flow.png new file mode 100644 index 0000000..0297c6c Binary files /dev/null and b/docs/static/data_flow.png differ diff --git a/docs/static/reset_flow.png b/docs/static/reset_flow.png index a3cf6b3..bdaccae 100644 Binary files a/docs/static/reset_flow.png and b/docs/static/reset_flow.png differ diff --git a/docs/static/webapp_flow.png b/docs/static/webapp_flow.png deleted file mode 100644 index c6b8358..0000000 Binary files a/docs/static/webapp_flow.png and /dev/null differ diff --git a/index.qmd b/index.qmd new file mode 100644 index 0000000..93151fe --- /dev/null +++ b/index.qmd @@ -0,0 +1,136 @@ +--- +title: "FastAPI, Jinja2, PostgreSQL Webapp Template" +--- + +![Screenshot of homepage](docs/static/Screenshot.png) + +## Quickstart + +This quickstart guide provides a high-level overview. See the full documentation for comprehensive information on [features](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/index.html), [installation](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/installation.html), [architecture](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/architecture.html), [conventions, code style, and customization](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/customization.html), [deployment to cloud platforms](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/deployment.html), and [contributing](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/contributing.html). + +## Features + +This template combines three of the most lightweight and performant open-source web development frameworks into a customizable webapp template with: + +- Pure Python backend +- Minimal-Javascript frontend +- Powerful, easy-to-manage database + +The template also includes full-featured secure auth with: + +- Token-based authentication +- Password recovery flow +- Role-based access control system + +## Design Philosophy + +The design philosophy of the template is to prefer low-level, best-in-class open-source frameworks that offer flexibility, scalability, and performance without vendor-lock-in. You'll find the template amazingly easy not only to understand and customize, but also to deploy to any major cloud hosting platform. + +## Tech Stack + +**Core frameworks:** + +- [FastAPI](https://fastapi.tiangolo.com/): scalable, high-performance, type-annotated Python web backend framework +- [PostgreSQL](https://www.postgresql.org/): the world's most advanced open-source database engine +- [Jinja2](https://jinja.palletsprojects.com/en/3.1.x/): frontend HTML templating engine +- [SQLModel](https://sqlmodel.tiangolo.com/): easy-to-use Python ORM + +**Additional technologies:** + +- [Poetry](https://python-poetry.org/): Python dependency manager +- [Pytest](https://docs.pytest.org/en/7.4.x/): testing framework +- [Docker](https://www.docker.com/): development containerization +- [Github Actions](https://docs.github.com/en/actions): CI/CD pipeline +- [Quarto](https://quarto.org/docs/): simple documentation website renderer +- [MyPy](https://mypy.readthedocs.io/en/stable/): static type checker for Python +- [Bootstrap](https://getbootstrap.com/): HTML/CSS styler +- [Resend](https://resend.com/): zero- or low-cost email service used for password recovery + +## Installation + +For comprehensive installation instructions, see the [installation page](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/installation.html). + +### Python and Docker + +- [Python 3.12 or higher](https://www.python.org/downloads/) +- [Docker and Docker Compose](https://docs.docker.com/get-docker/) + +### PostgreSQL headers + +For Ubuntu/Debian: + +``` bash +sudo apt update && sudo apt install -y python3-dev libpq-dev +``` + +For macOS: + +``` bash +brew install postgresql +``` + +For Windows: + +- No installation required + +### Python dependencies + +1. Install Poetry + +``` bash +pipx install poetry +``` + +2. Install project dependencies + +``` bash +poetry install +``` + +3. Activate shell + +``` bash +poetry shell +``` + +(Note: You will need to activate the shell every time you open a new terminal session. Alternatively, you can use the `poetry run` prefix before other commands to run them without activating the shell.) + +### Set environment variables + +Copy .env.example to .env with `cp .env.example .env`. + +Generate a 256 bit secret key with `openssl rand -base64 32` and paste it into the .env file. + +Set your desired database name, username, and password in the .env file. + +To use password recovery, register a [Resend](https://resend.com/) account, verify a domain, get an API key, and paste the API key into the .env file. + +### Start development database + +``` bash +docker compose up -d +``` + +### Run the development server + +Make sure the development database is running and tables and default permissions/roles are created first. + +``` bash +uvicorn main:app --host 0.0.0.0 --port 8000 --reload +``` + +Navigate to http://localhost:8000/ + +### Lint types with mypy + +``` bash +mypy . +``` + +### Contributing + +Your contributions are welcome! See the [issues page](https://github.com/promptly-technologies-llc/fastapi-jinja2-postgres-webapp/issues) for ideas. Fork the repository, create a new branch, make your changes, and submit a pull request. + +### License + +This project is licensed under the MIT License. See the LICENSE file for more details. diff --git a/static/Screenshot.png b/static/Screenshot.png new file mode 100644 index 0000000..d224337 Binary files /dev/null and b/static/Screenshot.png differ