Skip to content

31 fix documentation page routing #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 59 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,76 @@
# 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:

- Token-based authentication
- 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

Expand Down Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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.
6 changes: 3 additions & 3 deletions _quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 34 additions & 16 deletions docs/architecture.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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')
Expand All @@ -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.
54 changes: 26 additions & 28 deletions docs/authentication.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "Authentication"
---

## Authentication Flow
## Security features

This template implements a comprehensive authentication system with security best practices:

Expand Down Expand Up @@ -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
Expand All @@ -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')
Expand All @@ -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
Expand All @@ -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')
Expand All @@ -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)
7 changes: 6 additions & 1 deletion docs/contributing.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading