{"id":38656295,"url":"https://github.com/ggouzi/fastapi-sample-app-features","last_synced_at":"2026-01-17T09:28:13.348Z","repository":{"id":202901340,"uuid":"706315505","full_name":"ggouzi/fastapi-sample-app-features","owner":"ggouzi","description":"A FASTAPI example app, using a MySQL DB and providing features like: Authentication, versioning, monitoring, cron, permissions... The API and the DB are containerized, monitoring through Prometheus/Grafana and the whole project is accessible through a single gateway endpoint based on nginx reverse proxy feature","archived":false,"fork":false,"pushed_at":"2023-11-16T07:40:45.000Z","size":800,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-01-28T23:10:28.334Z","etag":null,"topics":["authentication","authentication-backend","docker","docker-compose","fatsapi","grafana","mysql","nginx-proxy","oauth2","prometheus","prometheus-metrics"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ggouzi.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-10-17T17:50:54.000Z","updated_at":"2023-10-23T12:30:32.000Z","dependencies_parsed_at":null,"dependency_job_id":"ccd7d05e-cc83-423e-9954-9b7a86947892","html_url":"https://github.com/ggouzi/fastapi-sample-app-features","commit_stats":{"total_commits":7,"total_committers":1,"mean_commits":7.0,"dds":0.0,"last_synced_commit":"bbc93594584d73ac82c797065441e2ac9bbcf143"},"previous_names":["ggouzi/fastapi-sample-app-features"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ggouzi/fastapi-sample-app-features","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ggouzi%2Ffastapi-sample-app-features","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ggouzi%2Ffastapi-sample-app-features/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ggouzi%2Ffastapi-sample-app-features/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ggouzi%2Ffastapi-sample-app-features/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ggouzi","download_url":"https://codeload.github.com/ggouzi/fastapi-sample-app-features/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ggouzi%2Ffastapi-sample-app-features/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28505559,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T06:57:29.758Z","status":"ssl_error","status_checked_at":"2026-01-17T06:56:03.931Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","authentication-backend","docker","docker-compose","fatsapi","grafana","mysql","nginx-proxy","oauth2","prometheus","prometheus-metrics"],"created_at":"2026-01-17T09:28:13.208Z","updated_at":"2026-01-17T09:28:13.277Z","avatar_url":"https://github.com/ggouzi.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# fastapi-example-app\n![Logo](app/static/files/logo.png)\n\n## Purpose\n\nThis FastAPI application serves as a reference architecture, showcasing best practices and a clean structure for building web APIs. It is designed to assist in creating a new API from scratch and understanding the key aspects of a backend architecture. The backend API provides two primary resources: Users and Items, with data storage in a MySQL database.\n\nThe application adheres to recommended guidelines and incorporates essential features for production use, such as authentication, versioning, permissions, crons, serving static files, logging, documentation, and monitoring (see [Features](#Features])).\n\nYou can launch this project in the following ways:\n- [Locally](#local-setup-standalone)\n- [Through docker containers](#local-setup-docker-compose)\n- [Through Kubernetes](#production-deployment)\n\n\n## Summary\n- [Key Concepts](#key-concepts)\n- [Architecture](#architecture)\n- [Database](#database-schema)\n- [Features](#features)\n  - [1. Versioning](#Versioning)\n  - [2. Authentication](#Authentication)\n  - [3. Permissions](#Permissions)\n  - [4. Crons](#Crons)\n  - [5. Serve static files ](##serve-static-files)\n  - [6. Logging](#Logging)\n  - [7. Documentation](#Documentation)\n  - [8. Monitoring](#Monitoring)\n- [Postman collection](#postman-collection)\n- [Installation](#installation)\n  - [Standalone app](#local-setup-standalone)\n  - [Containerized (Docker)](#local-setup-docker-compose)\n  - [Production Deployment (Kubernetes)](#production-deployment)\n\n\n## Key Concepts\n\n#### Topics\n- Backend development\n- DevOps\n- Monitoring\n\n### Technologies\n- [FastAPI (Python)](https://fastapi.tiangolo.com/)\n- [MySQL](https://www.mysql.com)\n- [Prometheus](https://prometheus.io/)\n- [Grafana](https://grafana.com/)\n- [Nginx](https://www.nginx.com/)\n- [Docker](https://www.docker.com/)\n- [Docker-Compose](https://docs.docker.com/compose/)\n- [Kubernetes](https://kubernetes.io)\n\n## Architecture\n\n```\n├── app\n    ├── api       =\u003e Controllers for defining HTTP routes for resources and handling errors\n    ├── cron      =\u003e Background tasks definition\n    ├── crud      =\u003e CRUD operations for each resource (no error handling)\n    ├── db        =\u003e Database connection management\n    ├── exceptions =\u003e Custom exceptions\n    ├── models    =\u003e Serialization of resources from the database schema into classes\n    ├── repository =\u003e Middleware for communication between api/controllers and other CRUD operations\n    ├── schemas   =\u003e Mapping of input/output HTTP payloads\n    ├── static    =\u003e Serving static files\n    ├── test      =\u003e For testing\n    ├── utils     =\u003e Utilities and common functions/constants used across other folders\n    ├── .env      =\u003e Environment variables for local usage\n    ├── .env.docker-compose  =\u003e Environment variables for Docker container usage\n    ├── main.py   =\u003e Entry point, route import, cron job definition, auto-documentation generation, and app initialization\n    ├── settings.py =\u003e Mapping of environment variables from .env file to class attributes\n    ├── requirements.txt =\u003e List of dependencies\n```\n\n### Database schema\nThe database schema in this example is straightforward, with two primary resources: Users and Items. Users can create, update, retrieve, and delete items. Other tables are less relevant from a project perspective:\n\n- Tokens table stores tokens for user authentication (see [Authentication](#Authentication)\n- Roles table stores a static list of roles, each mapped to an ID (see [Permissions](#Permissions)\n- Versions table stores each version as a string and includes a boolean flag to specify if the version is supported (see [Versioning](#Versioning)\n\n![Database schema](documentation/fastapi-app-db.jpg)\n\nFor example, you can perform actions like:\n- Create a new user\n- Create an item\n- Update a user\n- Delete an item\n- And more...\n\n## Features\nThis application showcases various features that are crucial for production-ready APIs.\n- [1. Versioning](#Versioning)\n- [2. Authentication](#Authentication)\n- [3. Permissions](#Permissions)\n- [4. Crons](#Crons)\n- [5. Serve static files ](##serve-static-files)\n- [6. Logging](#Logging)\n- [7. Documentation](#Documentation)\n- [8. Monitoring](#Monitoring)\n\n### 1. Versioning\nVersioning is implemented using an X-Version HTTP header and is handled through a decorator @custom_declarators.version_check. This function checks for the presence of the X-Version HTTP header and compares it with the content of the versions table. Each version can be either supported or not. The absence of the header is considered equivalent to a supported version.\n\n- If `version.supported=True` or if version is not sent in HTTP header =\u003e Allow to continue executing the given route\n- If `version.supported=False` =\u003e Raise a HTTP 426 error\n\n\u003cdetails\u003e\u003csummary\u003eExample of supported/non-supported versions\u003c/summary\u003e\n\n\n```bash\n# A non-supported version\n➭ VERSION=0.9; curl -sSw \"\\nstatus_code: \"%{http_code} http://localhost:8080/roles -H \"X-Version: ${VERSION}\"\n{\"detail\":\"Version not supported anymore\"}\nstatus_code: 426\n```\n\n```bash\n# A supported version        \n➭ VERSION=1.0; curl -sSw \"\\nstatus_code: \"%{http_code} http://localhost:8080/roles -H \"X-Version: ${VERSION}\"\n[{\"name\":\"admin\",\"id\":1},{\"name\":\"user\",\"id\":2}]\nstatus_code: 200\n```\n\n\u003c/details\u003e\n\n### 2. Authentication\n\n\n#### Tokens\nThis application incorporates an OAuth2 authentication mechanism using two types of tokens:\n\n- **access_token**: This token is temporary and has a limited lifespan, typically a few hours. It is required for every route that demands a user to be authenticated.\n- **refresh_token**:  In contrast, the refresh token is long-lasting, persisting for several months. It serves the purpose of obtaining a new access_token.\n\nThe goal of this mechanism is to minimize the frequency with which a user needs to enter their password while still allowing them to remain logged in.\n\nHere's how it functions:\n1. **User Login**: To log in, a user provides their password via a `POST /auth/token` request. In response, the API returns both an `access_token` and a `refresh_token`\n2. **Token Storage**: The user's frontend application securely stores these credentials in its local cache.\n3. **Authenticated Requests*: As the user navigates the application and makes requests to the API, they include the `access_token` in the HTTP Authorization header using the Bearer token mechanism.\n4. **Token Validation**: The API examines this HTTP header for every route to ensure the user is authenticated and has the necessary access permissions.\n5. **Token Expiry Check**: The API also checks the validity of the `access_token` provided in the header. If the `access_token` has expired, the API responds with a HTTP 401 (Unauthorized) status code.\n6. **Token Refresh**: If the user's `access_token` has expired, they can send a `POST /auth/refresh` request, providing their `refresh_token` in the request payload\n7. **Token Regeneration**: The API parses the `refresh_token` to verify its association with a known user in the database's tokens table. If the token is valid, the API generates a new set of `access_token` and `refresh_token` for the user.\n\n\nUtilizing tokens in this manner allows the application to avoid the need to transmit the user's password in HTTP requests, which reduces the risk of sensitive information interception, such as man-in-the-middle attacks, Wi-Fi spoofing, and exposure over non-secure connections.\n\nThe two types of tokens, access_token and refresh_token, are randomly generated strings composed of characters and digits. By default, they have a length of 128 characters. This length can be adjusted in the `utils/consts.py` file.\n\n\n#### Multi-device logging\n\nYou may have noticed we use a `tokens` table in database instead of new dimensions in the `Users` table.\n\nAdding all the `tokens` fields in the `Users` table would have worked fine but it prevents the app to store several tokens for the same user.\n\nIf the app is designed to be released on several devices (iOS/Android/Web...), this limitation can harm users preventing them to be logged in through several devices at the same time.\n\nUsing a `tokens` table, we keep this **one-to-many** relationship between Users and Tokens.\n\n\n#### Password Security\n\nTo ensure user password security, the application employs a robust security strategy. Here's how it's achieved:\n\n- User passwords are never stored in plain text in the database. This practice avoids exposing users' sensitive information in the event of a data breach.\n- Passwords are hashed on the backend, and plain text passwords are never transmitted between the backend and the database.\n- The hashed password is stored in the `hashed_password` column of the Users table.\n\nHowever, there's a potential security issue that arises when multiple users employ the same password. In such cases, they would have the same hashed password in the database. This situation could be exploited by attackers in the event of a database breach.\n\nTo mitigate this risk, the application introduces an additional layer of security: a `salt`. This unique random string, known as a salt, is used as a seed during user creation. It ensures that the hashed password cannot be decrypted without the corresponding salt. Each user's salt is unique, preventing different users with the same password from having the same hashed password.\n\nAt the backend level, password operations proceed as follows:\n\n- When a user logs in via a `POST /auth/token` request and sends their password in the request payload, the API fetches the `hashed_password` and the `salt` from the database. It then decrypts the `hashed_password` (using the `salt` as well) and compares it with the plain text password sent in the HTTP request.\n- When creating or updating a user, the password is encrypted, and the resulting `hashed_password` is stored in the database.\n\nAn additional layer of security is introduced through the `SECRET_KEY` in the `utils/consts.py` file. This secret key is used in combination with the `hashed_password` and `salt` to encrypt and decrypt passwords. Even in the event of a database breach, this secret key remains concealed from attackers, making it nearly impossible for them to reverse-engineer the decryption method used for the hashed passwords.\n\n![Auth](documentation/auth.jpg)\n\n\nAll auth parameters/variables are stored in `utils/consts.py` and can be adjusted to your need.\n\n### 3. Permissions\nUsers are associated with a role, which defines their permissions. There are currently two roles in database:\n\n\u003cdetails\u003e\u003csummary\u003eRoles\u003c/summary\u003e\n\n```bash\ncurl -sS http://localhost:8080/roles | jq .\n[\n  {\n    \"name\": \"admin\",\n    \"id\": 1\n  },\n  {\n    \"name\": \"user\",\n    \"id\": 2\n  }\n]\n```\n\n\u003c/details\u003e\n\nRoles are the key element which defines permissions. Here are the following 4 different permissions currently handled, and their signification.\n- `PERMISSION_ADMIN`: Admin account\n- `PERMISSION_ADMIN_OR_USER_OWNER` =\u003e `role_id=1 or role_id=2 and user_id=current_user.id`: For routes with a user_id parameter in the path, allowing access to admin or the user matching the `user_id`.\n- `PERMISSION_ADMIN_OR_ITEM_OWNER` For routes with an item_id parameter in the path, allowing access to admin or the user who owns the item matching the `item_id`.\n- `PERMISSION_USER` : Any authenticated user.\n\nPermission are defined through a custom decorator for each route:\n\n```python\n@custom_declarators.permission(permission_string=consts.Consts.PERMISSION_ADMIN)\ndef create_user_as_admin(user: user_schema.UserCreate, request: Request, db: Session = Depends(get_db)):\n  ...\n```\n\n\u003cdetails\u003e\u003csummary\u003eThe declarator itself is defined here\u003c/summary\u003e\n\n```python\ndef permission(permission_string):\n    def decorator_auth(func):\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            request = kwargs['request']\n            db = kwargs['db']\n            token = rights.retrieve_token_from_header(request)\n            if permission_string == consts.Consts.PERMISSION_ADMIN:\n                rights.is_admin(db, token)\n            elif permission_string == consts.Consts.PERMISSION_ADMIN_OR_USER_OWNER:\n                user_id = kwargs['user_id']\n                rights.is_admin_or_user_owner(db=db, token=token, user_id=user_id)\n            elif permission_string == consts.Consts.PERMISSION_ADMIN_OR_ITEM_OWNER:\n                item_id = kwargs['item_id']\n                rights.is_admin_or_item_owner(db=db, token=token, item_id=item_id)\n            elif permission_string == consts.Consts.PERMISSION_USER:\n                rights.is_authenticated(db=db, token=token)\n            return func(*args, **kwargs)\n        return wrapper\n    return decorator_auth\n```\n\n\u003c/details\u003e\n\nFunctions defined in `rights.py` will trigger a HTTP 403 error if the current user doesn't have the given permission.\nTherefore, you can protect any route with any level of permission through this decorator\n\n### 4. Crons\nCron can be configured using the [BackgroundScheduler module](https://fastapi.tiangolo.com/tutorial/background-tasks/).\n\nThey are defined easily using a decorator\n\n\u003cdetails\u003e\u003csummary\u003eSample code for cron configuration\u003c/summary\u003e\n\n```python\n# This cron will run at an interval of 1 day (everyday) and will:\n# - Open a connection to the database\n# - Fetch expired access_token (where access_token_expiration\u003cnow) and delete them\n# - Close the connection to the database. Closing the connection in the end is important to not leave open conections waiting forever and wasting the pool of connections defined in the database configuration\n@sched.scheduled_job('interval', days=1)\ndef delete_expired_tokens():\n    db = SessionLocal()\n    db_tokens = token_crud.delete_expired_tokens(db=db)\n    logger.info(f\"Tokens: Expired tokens deleted: {db_tokens}\")\n    db.close()\n```\n\nYou then call `sched.start()` to start the cron\n\nYou should observe such logs when you start the app. This means the cron job is currently running\n```\n2023-10-17 19:40:27,831 [INFO] Added job \"delete_expired_tokens\" to job store \"default\"\n2023-10-17 19:40:27,831 [INFO] Scheduler started\n```\n\n\u003c/details\u003e\n\n\n### 5. Serve static files\nWe can configure the FastAPI app to serve static files using the following command\n\n```python\napp.mount(\"/\", StaticFiles(directory=\"static/files/\"))\n```\n\nThis maps files under `static/files/` folder at the root level of the web server (`/`). `static/files/logo.png` could be fetched from `http://localhost:8080/logo.png`\n\n\n### 6. Logging\nLogging is implemented using the standard Python logging library. The logging configuration is defined in `main.py`. Logs are used to record informational, warning, and error messages.\n\n\u003cdetails\u003e\u003csummary\u003eSample code for logging\u003c/summary\u003e\n\n  ```python\n  import logging\n  logging.basicConfig(\n      level=logging.INFO,\n      format=\"%(asctime)s [%(levelname)s] %(message)s\"\n  )\n\n  logger = logging.getLogger()\n  # Print something\n  logging.info(\"A simple message\")\n  logging.warning(\"A warning message\")\n  logging.error(\"An error message\")\n  ```\n\n  ```\n  2023-10-20 11:00:37,526 [INFO] A simple message\n  2023-10-20 11:00:37,526 [WARNING] A warning message\n  2023-10-20 11:00:37,526 [ERROR] An error message\n  ```\n\u003c/details\u003e\n\n### 7. Documentation\nOne of the great features of FastAPI is the [automatic generation of API documentation](https://fastapi.tiangolo.com/tutorial/metadata/). It supports two documentation interfaces: Swagger and Redoc. In this example, we use Redoc.\n\nFirst of all we need to define the openapi.json endpoint which will then be used to generate the Redoc documentation. We generate the endpoint /openai.json, which will return the OpenAPI schema in a JSON format. This function uses HTTPBasicCredentials and therefore documentation route can be protected with username/password authentication.\n\n\u003cdetails\u003e\u003csummary\u003eSample code for documentation\u003c/summary\u003e\n\n```python\n# Docs\n@app.get(\"/openapi.json\", include_in_schema=False)\nasync def get_open_api_endpoint(credentials: HTTPBasicCredentials = Depends(security), db: Session = Depends(get_db)):\n    openapi_schema = get_openapi(\n        title=\"Sample API Documentation\",\n        version=\"1.0.0\",\n        routes=app.routes,\n        description=\"Postman Collection: https://api.postman.com/collections/1999344-93e21dc5-aa22-4fbf-a196-fcb5e5f1926c?access_key=PMAT-01HCWNW2JZVWXF79N5ESXY61TT\",\n        openapi_version=\"3.0.3\",\n    )\n\n    openapi_schema[\"info\"][\"x-logo\"] = {\n        \"url\": \"/logo.png\"\n    }\n    app.openapi_schema = openapi_schema\n    return JSONResponse(openapi_schema)\n```\n\n\u003c/details\u003e\n\nIn the following block of code, from main.py, we generate the Redoc documentation from the OpenAPI JSON documentation endpoint we previously configured, /openapi.json. We serve it on /docs route.\n```python\n@app.get(\"/docs\", include_in_schema=False)\nasync def get_documentation(credentials: HTTPBasicCredentials = Depends(security), db: Session = Depends(get_db)):\n  ...\n  return get_redoc_html(openapi_url=\"/openapi.json\", title=\"docs\")\n```\n\nThe documentation can be accessed on [localhost:8080/docs](localhost:8080/docs) and is generated from the signature of the APIRouter fonctions (under /api folder). There is by default a single user in the database sample: username: *admin* / password: *admin*\n![Doc](documentation/doc.png)\n```python\n@router.post(\"/items\", response_model=item_schema.ItemResponse, status_code=201, responses=get_responses([201, 401, 403, 409, 422, 426, 500]), tags=[\"Items\"], description=\"Create an Item object. Permission=User\")\n```\n\n### 8. Monitoring\n\nThis app example contains also a prometheus exporter (see https://github.com/trallnag/prometheus-fastapi-instrumentator) to expose default metrics (no custom metric here).\n\nThe endpoint is available at [http://localhost:8000/metrics](http://localhost:8000/metrics) and exposes an endpoint that can be fetched regularly from a [Prometheus](https://prometheus.io/) instance to monitor the app.\n\nVisualization can be performed through [Grafana](https://grafana.com/)\n\n![Grafana](documentation/grafana.png)\nDashboard coming from https://github.com/Kludex/fastapi-prometheus-grafana\n\nGrafana default credentials are: *admin*/*admin*\n\nSee [Containerized (Docker)](#local-setup-docker-compose) below to insall Prometheus/Grafana through docker-compose\n\n## Postman collection\n\nPostman collection available here: [here](https://api.postman.com/collections/1999344-93e21dc5-aa22-4fbf-a196-fcb5e5f1926c?access_key=PMAT-01HCWNW2JZVWXF79N5ESXY61TT)\n\n## Installation\n\n- [Standalone app](#local-setup-standalone)\n- [Containerized (Docker)](#local-setup-docker-compose)\n- [Production Deployment (Kubernetes)](#production-deployment)\n\n### Local setup standalone\n\nEnsure you have a mysql server installed and initialise the database by executing those 3 SQL scripts.\nDefault configuration: user: *test*, database name: *test*, password: *test*\n\n```\nmysql -u root \u003c database/base/01_init_db.sql\nmysql -u root \u003c database/base/02_schema.sql\nmysql -u root \u003c database/base/03_data.sql\n```\n\n```bash\npip3 install -r requirements.txt\n```\n\nIf you are using different MySQL credentials, please edit the `.env` file accordingly. This file is used to pass sensitive database connection information to the API.\n\n```bash\npython3 main.py\n```\n\n### Local setup docker-compose\n\nMySQL and FastAPI app are containerized and provided in the `docker-compose.yml` file.\nFastAPI app docker image is built from `app/Dockerfile` (specified in the `docker-compose.yml` file)\n\nThe .env file used in this case is `.env.docker-compose`. It differs by the fast the DB_HOST is not `localhost` anymore as MySQL server and FastAPI run in two distinct containers now.\n\nPort 3306 of MySQL and port 8080 (API) and 8000 (monitoring) are not exposed to outside each container. To reach the API we use a third component: An Nginx reverse proxy. Requests will be sent towards the nginx gateway.\n\nThere are also two other containers for monitoring: Prometheus and Grafana.\n\n![docker-containers](documentation/docker-containers.png)\n\nThe idea is to expose only port 8080 of Nginx and redirect routes based on their path to the appopriate location.\n\n\n- `^/auth|docs|openapi.json|items|users|roles|versions` ➜ routed to API container port 8080\n- `^/metrics` ➜ routed to API container port 8000\n- `^/prometheus` ➜ routed to Prometheus container port 9090\n- `^/grafana` ➜ routed to Grafana container port 3306\n\nSee nginx configuration below:\n\n\u003cdetails\u003e\u003csummary\u003eNginx configuration\u003c/summary\u003e\n\n```\nhttp {\n    server {\n        listen 8080 default_server;\n\n        # set DNS resolver as Docker internal DNS\n        resolver 127.0.0.11 valid=10s;\n        resolver_timeout 5s;\n\n        location ~ ^/(auth|docs|openapi.json|items|users|roles|versions) {\n            proxy_pass http://fastapi-example:8080;\n        }\n\n        location /metrics {\n            proxy_pass http://fastapi-example:8000;\n        }\n\n        location /prometheus/ {\n            proxy_pass http://prometheus:9090;\n        }\n\n        location /grafana/ {\n            proxy_set_header Host grafana;\n            proxy_set_header Origin https://grafana:3000;\n            proxy_pass http://grafana:3000;\n        }\n    }\n}\n\n```\n\u003c/details\u003e\n\nTo launch everything (MySQL, FastAPI, Nginx, Prometheus and Grafana), just run the following command:\n\n```bash\ndocker-compose up\n```\n\nAnd fetch any resource from port 8080: [localhost:8080/grafana](http://localhost:8080/grafana), [localhost:8080/users](http://localhost:8080/users), [localhost:8080/metrics](http://localhost:8080/metrics), [localhost:8080/prometheus](http://localhost:8080/prometheus), ...\n\n\n### Production deployment\nTODO\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fggouzi%2Ffastapi-sample-app-features","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fggouzi%2Ffastapi-sample-app-features","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fggouzi%2Ffastapi-sample-app-features/lists"}