Skip to content

Commit 2fabd3d

Browse files
committed
Merge branch 'feat/react-email' of https://github.com/tiangolo/full-stack-fastapi-template into feat/react-email
2 parents 3188fb6 + 0afa21f commit 2fabd3d

16 files changed

Lines changed: 156 additions & 216 deletions

.copier/update_dotenv.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,20 @@
99
answers = json.loads(answers_path.read_text())
1010
env_path = root_path / ".env"
1111
env_content = env_path.read_text()
12+
# Keep deployment provider settings from replacing local development defaults.
13+
env_answer_keys = {
14+
"emails_from_email",
15+
"first_superuser",
16+
"first_superuser_password",
17+
"postgres_password",
18+
"project_name",
19+
"secret_key",
20+
}
1221
lines = []
1322
for line in env_content.splitlines():
1423
for key, value in answers.items():
24+
if key not in env_answer_keys:
25+
continue
1526
upper_key = key.upper()
1627
if line.startswith(f"{upper_key}="):
1728
if " " in value:

.env

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,20 @@
1-
# Domain
2-
# This would be set to the production domain with an env var on deployment
3-
# used by Traefik to transmit traffic and acquire TLS certificates
4-
DOMAIN=localhost
5-
# To test the local Traefik config
6-
# DOMAIN=localhost.tiangolo.com
7-
8-
# Public frontend URL, used by the backend to generate links in emails.
9-
# Deployments derive this from DOMAIN in compose.deploy.yml.
10-
FRONTEND_HOST=http://localhost:5173
11-
12-
# Environment: local, staging, production
1+
# Enable local-only development behavior
132
ENVIRONMENT=local
143

154
PROJECT_NAME="Full Stack FastAPI Project"
165

17-
# Backend
18-
# Additional allowed CORS origins, separated by commas
19-
BACKEND_CORS_ORIGINS=
206
SECRET_KEY=changethis
217
FIRST_SUPERUSER=admin@example.com
228
FIRST_SUPERUSER_PASSWORD=changethis
239

2410
# Emails
25-
SMTP_HOST=
26-
SMTP_USER=
27-
SMTP_PASSWORD=
11+
SMTP_HOST=localhost
2812
EMAILS_FROM_EMAIL=info@example.com
29-
SMTP_TLS=True
30-
SMTP_SSL=False
31-
SMTP_PORT=587
13+
SMTP_TLS=False
14+
SMTP_PORT=1025
3215

3316
# Postgres
3417
POSTGRES_SERVER=localhost
35-
POSTGRES_PORT=5432
3618
POSTGRES_DB=app
3719
POSTGRES_USER=postgres
3820
POSTGRES_PASSWORD=changethis
39-
40-
SENTRY_DSN=

.github/workflows/deploy-production.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ jobs:
1717
- self-hosted
1818
- production
1919
env:
20-
ENVIRONMENT: production
2120
DOMAIN: ${{ secrets.DOMAIN }}
2221
SECRET_KEY: ${{ secrets.SECRET_KEY }}
2322
FIRST_SUPERUSER: ${{ secrets.FIRST_SUPERUSER }}

.github/workflows/deploy-staging.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ jobs:
1717
- self-hosted
1818
- staging
1919
env:
20-
ENVIRONMENT: staging
2120
DOMAIN: ${{ secrets.DOMAIN }}
2221
SECRET_KEY: ${{ secrets.SECRET_KEY }}
2322
FIRST_SUPERUSER: ${{ secrets.FIRST_SUPERUSER }}

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ repos:
6464
entry: bash ./scripts/generate-client.sh
6565
pass_filenames: false
6666
language: unsupported
67-
files: ^backend/.*$|^scripts/generate-client\.sh$
67+
files: ^backend/app/.*\.py$|^backend/pyproject\.toml$|^uv\.lock$|^frontend/openapi-ts\.config\.ts$|^scripts/generate-client\.sh$
6868

6969
- id: add-release-date
7070
language: unsupported

README.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
- 🤖 An automatically generated frontend client.
1818
- 🧪 [Playwright](https://playwright.dev) for End-to-End testing.
1919
- 🦇 Dark mode support.
20-
- 🐋 [Docker Compose](https://www.docker.com) for development and production.
20+
- 🐋 [Docker Compose](https://www.docker.com) for local services and deployment.
2121
- 🔒 Secure password hashing by default.
2222
- 🔑 JWT (JSON Web Token) authentication.
2323
- 📫 Email based password recovery.
@@ -199,12 +199,8 @@ The input variables, with their default values (some auto generated) are:
199199
- `secret_key`: (default: `"changethis"`) The secret key for the project, used for security, stored in .env, you can generate one with the method above.
200200
- `first_superuser`: (default: `"admin@example.com"`) The email of the first superuser (in .env).
201201
- `first_superuser_password`: (default: `"changethis"`) The password of the first superuser (in .env).
202-
- `smtp_host`: (default: "") The SMTP server host to send emails, you can set it later in .env.
203-
- `smtp_user`: (default: "") The SMTP server user to send emails, you can set it later in .env.
204-
- `smtp_password`: (default: "") The SMTP server password to send emails, you can set it later in .env.
205202
- `emails_from_email`: (default: `"info@example.com"`) The email account to send emails from, you can set it later in .env.
206203
- `postgres_password`: (default: `"changethis"`) The password for the PostgreSQL database, stored in .env, you can generate one with the method above.
207-
- `sentry_dsn`: (default: "") The DSN for Sentry, if you are using it, you can set it later in .env.
208204

209205
## Backend Development
210206

@@ -222,7 +218,7 @@ Deployment docs: [deployment.md](./deployment.md).
222218

223219
General development docs: [development.md](./development.md).
224220

225-
This includes using Docker Compose, custom local domains, `.env` configurations, etc.
221+
This includes the local FastAPI and Vite workflow, Docker Compose services, custom local domains, `.env` configuration, and more.
226222

227223
## Release Notes
228224

backend/README.md

Lines changed: 30 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,25 @@
55
* [Docker](https://www.docker.com/).
66
* [uv](https://docs.astral.sh/uv/) for Python package and environment management.
77

8-
## Docker Compose
8+
## Local Development
99

10-
Start the local development environment with Docker Compose following the guide in [../development.md](../development.md).
10+
Run the backend locally and connect it to PostgreSQL in Docker Compose.
11+
12+
From the project root, start PostgreSQL and Mailcatcher:
13+
14+
```console
15+
$ docker compose up -d db mailcatcher
16+
```
17+
18+
Then, from `./backend/`, install the dependencies, prepare the database, and start the development server:
19+
20+
```console
21+
$ uv sync
22+
$ uv run bash scripts/prestart.sh
23+
$ uv run fastapi dev
24+
```
25+
26+
The API is available at `http://localhost:8000`, with automatic interactive docs at `http://localhost:8000/docs`.
1127

1228
## General Workflow
1329

@@ -35,66 +51,32 @@ There are already configurations in place to run the backend through the VS Code
3551

3652
The setup is also already configured so you can run the tests through the VS Code Python tests tab.
3753

38-
## Docker Compose Override
54+
## Full Stack with Docker Compose
3955

40-
During development, you can change Docker Compose settings that will only affect the local development environment in the file `compose.override.yml`.
41-
42-
The changes to that file only affect the local development environment, not the production environment. So, you can add "temporary" changes that help the development workflow.
43-
44-
For example, the directory with the backend code is synchronized in the Docker container, copying the code you change live to the directory inside the container. That allows you to test your changes right away, without having to build the Docker image again. It should only be done during development, for production, you should build the Docker image with a recent version of the backend code. But during development, it allows you to iterate very fast.
45-
46-
There is also a command override that runs `fastapi run --reload` instead of the default `fastapi run`. It starts a single server process (instead of multiple, as would be for production) and reloads the process whenever the code changes. Have in mind that if you have a syntax error and save the Python file, it will break and exit, and the container will stop. After that, you can restart the container by fixing the error and running again:
56+
To run the backend and built frontend in Docker Compose:
4757

4858
```console
4959
$ docker compose watch
5060
```
5161

52-
There is also a commented out `command` override, you can uncomment it and comment the default one. It makes the backend container run a process that does "nothing", but keeps the container alive. That allows you to get inside your running container and execute commands inside, for example a Python interpreter to test installed dependencies, or start the development server that reloads when it detects changes.
62+
The application is available at `http://localhost:8000`.
5363

54-
To get inside the container with a `bash` session you can start the stack with:
64+
### Docker Compose Override
5565

56-
```console
57-
$ docker compose watch
58-
```
66+
The `compose.override.yml` file contains local settings for published ports, source synchronization, automatic image rebuilds, and backend reloads. Docker Compose applies it automatically when you run `docker compose` without an explicit file list.
5967

60-
and then in another terminal, `exec` inside the running container:
68+
To open a shell in the backend container:
6169

6270
```console
6371
$ docker compose exec backend bash
6472
```
6573

66-
You should see an output like:
67-
68-
```console
69-
root@7f2607af31c3:/app#
70-
```
71-
72-
that means that you are in a `bash` session inside your container, as a `root` user, under the `/app` directory, this directory has another directory called "app" inside, that's where your code lives inside the container: `/app/app`.
73-
74-
There you can use the `fastapi run --reload` command to run the debug live reloading server.
75-
76-
```console
77-
$ fastapi run --reload app/main.py
78-
```
79-
80-
...it will look like:
81-
82-
```console
83-
root@7f2607af31c3:/app# fastapi run --reload app/main.py
84-
```
85-
86-
and then hit enter. That runs the live reloading server that auto reloads when it detects code changes.
87-
88-
Nevertheless, if it doesn't detect a change but a syntax error, it will just stop with an error. But as the container is still alive and you are in a Bash session, you can quickly restart it after fixing the error, running the same command ("up arrow" and "Enter").
89-
90-
...this previous detail is what makes it useful to have the container alive doing nothing and then, in a Bash session, make it run the live reload server.
91-
9274
## Backend tests
9375

94-
To test the backend run:
76+
To test the backend from the `backend` directory, run:
9577

9678
```console
97-
$ bash ./scripts/test.sh
79+
$ uv run bash ./scripts/test.sh
9880
```
9981

10082
The tests run with Pytest, modify and add tests to `./backend/tests/`.
@@ -123,30 +105,22 @@ When the tests are run, a file `htmlcov/index.html` is generated, you can open i
123105

124106
## Migrations
125107

126-
As during local development your app directory is mounted as a volume inside the container, you can also run the migrations with `alembic` commands inside the container and the migration code will be in your app directory (instead of being only inside the container). So you can add it to your git repository.
127-
128-
Make sure you create a "revision" of your models and that you "upgrade" your database with that revision every time you change them. As this is what will update the tables in your database. Otherwise, your application will have errors.
129-
130-
* Start an interactive session in the backend container:
131-
132-
```console
133-
$ docker compose exec backend bash
134-
```
108+
Make sure you create a revision of your models and upgrade the database with that revision every time you change them. From the `backend` directory, use `uv` to run Alembic against the PostgreSQL container:
135109

136110
* Alembic is already configured to import your SQLModel models from `./backend/app/models.py`.
137111

138-
* After changing a model (for example, adding a column), inside the container, create a revision, e.g.:
112+
* After changing a model (for example, adding a column), create a revision:
139113

140114
```console
141-
$ alembic revision --autogenerate -m "Add column last_name to User model"
115+
$ uv run alembic revision --autogenerate -m "Add column last_name to User model"
142116
```
143117

144118
* Commit to the git repository the files generated in the alembic directory.
145119

146120
* After creating the revision, run the migration in the database (this is what will actually change the database):
147121

148122
```console
149-
$ alembic upgrade head
123+
$ uv run alembic upgrade head
150124
```
151125

152126
If you don't want to use migrations at all, uncomment the lines in the file at `./backend/app/core/db.py` that end in:

backend/app/core/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class Settings(BaseSettings):
3434
# 60 minutes * 24 hours * 8 days = 8 days
3535
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
3636
FRONTEND_HOST: str = "http://localhost:5173"
37-
ENVIRONMENT: Literal["local", "staging", "production"] = "local"
37+
ENVIRONMENT: Literal["local"] | None = None
3838

3939
BACKEND_CORS_ORIGINS: Annotated[
4040
list[AnyUrl] | str, BeforeValidator(parse_cors)

compose.deploy.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,23 @@ services:
3838
restart: always
3939
labels:
4040
# Route HTTPS traffic for the Adminer subdomain
41-
- traefik.http.routers.adminer-https.rule=Host(`adminer.${DOMAIN?Variable not set}`)
41+
- traefik.http.routers.adminer-https.rule=Host(`adminer.${DOMAIN:?Variable not set}`)
4242
- traefik.http.routers.adminer-https.entrypoints=https
4343
- traefik.http.routers.adminer-https.tls=true
4444
# Use the Let's Encrypt resolver
4545
- traefik.http.routers.adminer-https.tls.certresolver=le
4646

4747
prestart:
4848
environment:
49-
FRONTEND_HOST: https://${DOMAIN?Variable not set}
49+
FRONTEND_HOST: https://${DOMAIN:?Variable not set}
5050

5151
backend:
5252
restart: always
5353
environment:
54-
FRONTEND_HOST: https://${DOMAIN?Variable not set}
54+
FRONTEND_HOST: https://${DOMAIN:?Variable not set}
5555
labels:
5656
# Route HTTPS traffic for this domain
57-
- traefik.http.routers.backend-https.rule=Host(`${DOMAIN?Variable not set}`)
57+
- traefik.http.routers.backend-https.rule=Host(`${DOMAIN:?Variable not set}`)
5858
- traefik.http.routers.backend-https.entrypoints=https
5959
- traefik.http.routers.backend-https.tls=true
6060
# Use the Let's Encrypt resolver

compose.override.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,14 @@ services:
6868
volumes:
6969
- ./backend/htmlcov:/app/backend/htmlcov
7070
environment:
71+
ENVIRONMENT: "local"
7172
SMTP_HOST: "mailcatcher"
7273
SMTP_PORT: "1025"
7374
SMTP_TLS: "false"
74-
EMAILS_FROM_EMAIL: "noreply@example.com"
75+
76+
prestart:
77+
environment:
78+
ENVIRONMENT: "local"
7579

7680
mailcatcher:
7781
image: schickling/mailcatcher
@@ -89,15 +93,15 @@ services:
8993
depends_on:
9094
- backend
9195
- mailcatcher
92-
env_file:
93-
- .env
9496
environment:
97+
- FIRST_SUPERUSER=${FIRST_SUPERUSER:?Variable not set}
98+
- FIRST_SUPERUSER_PASSWORD=${FIRST_SUPERUSER_PASSWORD:?Variable not set}
9599
- PLAYWRIGHT_BASE_URL=http://backend:8000
96100
- VITE_API_URL=http://backend:8000
97101
- MAILCATCHER_HOST=http://mailcatcher:1080
98102
# For the reports when run locally
99103
- PLAYWRIGHT_HTML_HOST=0.0.0.0
100-
- CI=${CI}
104+
- CI=${CI:-}
101105
volumes:
102106
- ./frontend/blob-report:/app/frontend/blob-report
103107
- ./frontend/test-results:/app/frontend/test-results

0 commit comments

Comments
 (0)