{"id":36970559,"url":"https://github.com/allisson/fastpubsub","last_synced_at":"2026-01-13T21:49:05.072Z","repository":{"id":331011346,"uuid":"1123251724","full_name":"allisson/fastpubsub","owner":"allisson","description":"Simple pubsub system based on FastAPI and PostgreSQL","archived":false,"fork":false,"pushed_at":"2026-01-04T00:58:50.000Z","size":148,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-09T10:55:54.861Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/allisson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-26T13:39:43.000Z","updated_at":"2026-01-04T00:58:53.000Z","dependencies_parsed_at":"2026-01-02T08:10:50.343Z","dependency_job_id":null,"html_url":"https://github.com/allisson/fastpubsub","commit_stats":null,"previous_names":["allisson/fastpubsub"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/allisson/fastpubsub","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Ffastpubsub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Ffastpubsub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Ffastpubsub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Ffastpubsub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/allisson","download_url":"https://codeload.github.com/allisson/fastpubsub/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Ffastpubsub/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28401089,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T14:36:09.778Z","status":"ssl_error","status_checked_at":"2026-01-13T14:35:19.697Z","response_time":56,"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":[],"created_at":"2026-01-13T21:49:04.347Z","updated_at":"2026-01-13T21:49:05.051Z","avatar_url":"https://github.com/allisson.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🚀 fastpubsub\n\n\u003e Simple, reliable, and scalable pub/sub system based on FastAPI and PostgreSQL\n\n[![Docker](https://img.shields.io/badge/docker-hub-blue)](https://hub.docker.com/r/allisson/fastpubsub)\n\n## 📖 Overview\n\n**fastpubsub** is a lightweight publish-subscribe messaging system built with FastAPI and PostgreSQL. It provides a simple HTTP API for message publishing and subscription management with powerful features like message filtering, delivery guarantees, dead-letter queues, and automatic retries with exponential backoff. The system is built with asyncio for efficient concurrent operations and uses SQLAlchemy's async engine with psycopg's native async support.\n\n### 🎯 What is fastpubsub?\n\nfastpubsub is **not** intended to replace dedicated high-throughput messaging systems like Google Cloud Pub/Sub, NATS, or Apache Kafka. Instead, it brings key pub/sub features to **simple architectures** where you already have PostgreSQL available. If you're running a small to medium-sized application with PostgreSQL as your primary database, fastpubsub lets you add reliable messaging capabilities without introducing additional infrastructure complexity.\n\n**Use fastpubsub when you:**\n- Already use PostgreSQL and want to avoid managing separate message brokers\n- Want to keep your stack simple with fewer moving parts\n- Need pub/sub functionality for small to medium workloads\n- Prefer simplicity over maximum throughput\n- Want a single database for both application data and messaging\n- Need reliable message delivery with retries and dead-letter queues\n\n**Consider dedicated message brokers when you:**\n- Need to handle millions of messages per second\n- Require horizontal scalability across multiple datacenters\n- Need advanced features like message streaming or complex routing\n- Want to decouple your messaging infrastructure from your database\n\n### ✨ Key Features\n\n- 🎯 **Topic-based messaging** - Organize messages by topics\n- 🔒 **Secure** - Built-in authentication with JWT and scope-based permissions\n- 🔍 **Message filtering** - Subscribe to specific messages using JSON-based filters\n- 🔄 **Automatic retries** - Configurable retry logic with exponential backoff\n- 💀 **Dead Letter Queue (DLQ)** - Handle failed messages gracefully\n- 📊 **Metrics \u0026 Monitoring** - Built-in subscription metrics and Prometheus support\n- 🐳 **Docker-ready** - Easy deployment with Docker\n- 🛡️ **Reliable delivery** - Acknowledgment and negative-acknowledgment support\n- 🧹 **Automatic cleanup** - Background jobs for message maintenance\n\n## 🏗️ Architecture\n\nfastpubsub uses PostgreSQL as its backend, leveraging stored procedures and JSONB capabilities for high-performance message routing and filtering. The system is built with asyncio for efficient concurrent operations, using SQLAlchemy's async engine with psycopg's native async support. The architecture consists of:\n\n- **API Server**: Asynchronous RESTful HTTP API for all operations\n- **Database**: PostgreSQL with custom functions for message management, accessed via async SQLAlchemy\n- **Cleanup Workers**: Background jobs for message maintenance\n\n## 🔄 Message Flow\n\n1. **Publish**: Messages are published to a topic\n2. **Route**: Messages are routed to all subscriptions for that topic\n3. **Filter**: Subscriptions with filters only receive matching messages\n4. **Consume**: Consumers fetch messages in batches\n5. **Process**: Consumer processes the message\n6. **ACK/NACK**: Consumer acknowledges success or requests retry\n7. **Retry**: Failed messages are retried with exponential backoff\n8. **DLQ**: Messages exceeding max attempts move to the dead letter queue\n\n## 🐳 Quick Start with Docker\n\nAll commands use the official Docker image from [Docker Hub](https://hub.docker.com/r/allisson/fastpubsub).\n\n### 1️⃣ Prerequisites\n\n- Docker installed\n- PostgreSQL database (can also run in Docker)\n\n### 2️⃣ Database Setup\n\nFirst, you need to run database migrations:\n\n```bash\ndocker run --rm \\\n  -e FASTPUBSUB_DATABASE_URL='postgresql+psycopg://YOUR_USER:YOUR_PASSWORD@YOUR_HOST:5432/YOUR_DATABASE' \\\n  allisson/fastpubsub db-migrate\n```\n\n### 3️⃣ Start the Server\n\nRun the API server:\n\n```bash\ndocker run -p 8000:8000 \\\n  -e FASTPUBSUB_DATABASE_URL='postgresql+psycopg://YOUR_USER:YOUR_PASSWORD@YOUR_HOST:5432/YOUR_DATABASE' \\\n  allisson/fastpubsub server\n```\n\nThe API will be available at `http://localhost:8000`. You can access the interactive API documentation at `http://localhost:8000/docs`.\n\n## 🎮 Docker Commands\n\n### 🗄️ Database Migration\n\nApply database migrations to set up or upgrade the schema:\n\n```bash\ndocker run --rm \\\n  -e FASTPUBSUB_DATABASE_URL='postgresql+psycopg://YOUR_USER:YOUR_PASSWORD@YOUR_HOST:5432/YOUR_DATABASE' \\\n  allisson/fastpubsub db-migrate\n```\n\nThis command creates all necessary tables, indexes, and stored procedures.\n\n### 🌐 API Server\n\nStart the HTTP API server:\n\n```bash\ndocker run -p 8000:8000 \\\n  -e FASTPUBSUB_DATABASE_URL='postgresql+psycopg://YOUR_USER:YOUR_PASSWORD@YOUR_HOST:5432/YOUR_DATABASE' \\\n  allisson/fastpubsub server\n```\n\nThe server runs with Gunicorn and Uvicorn workers for production-grade performance.\n\n### 🧹 Cleanup Acked Messages\n\nRemove acknowledged messages older than a specified threshold:\n\n```bash\ndocker run --rm \\\n  -e FASTPUBSUB_DATABASE_URL='postgresql+psycopg://YOUR_USER:YOUR_PASSWORD@YOUR_HOST:5432/YOUR_DATABASE' \\\n  allisson/fastpubsub cleanup_acked_messages\n```\n\nThis removes acked messages to prevent database bloat. By default, messages older than 1 hour (3600 seconds) are deleted.\n\n### 🔓 Cleanup Stuck Messages\n\nRelease messages that are stuck in \"delivered\" state (locked but not acked/nacked):\n\n```bash\ndocker run --rm \\\n  -e FASTPUBSUB_DATABASE_URL='postgresql+psycopg://YOUR_USER:YOUR_PASSWORD@YOUR_HOST:5432/YOUR_DATABASE' \\\n  allisson/fastpubsub cleanup_stuck_messages\n```\n\nThis handles cases where a consumer crashed without acknowledging messages. By default, messages locked for more than 60 seconds are released.\n\n### 💡 Running as Cron Jobs\n\nIt's recommended to run cleanup commands periodically using cron or a scheduler like Kubernetes CronJob:\n\n```bash\n# Example: Run cleanup_acked_messages every hour\n0 * * * * docker run --rm -e FASTPUBSUB_DATABASE_URL='postgresql+psycopg://YOUR_USER:YOUR_PASSWORD@YOUR_HOST:5432/YOUR_DATABASE' allisson/fastpubsub cleanup_acked_messages\n\n# Example: Run cleanup_stuck_messages every 5 minutes\n*/5 * * * * docker run --rm -e FASTPUBSUB_DATABASE_URL='postgresql+psycopg://YOUR_USER:YOUR_PASSWORD@YOUR_HOST:5432/YOUR_DATABASE' allisson/fastpubsub cleanup_stuck_messages\n```\n\n### 🔐 Authentication Commands\n\n#### Generate Secret Key\n\nGenerate a secure random secret key for authentication:\n\n```bash\ndocker run --rm allisson/fastpubsub generate_secret_key\n```\n\n**Output:**\n```\nnew_secret=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6\n```\n\nUse this secret key to set the `FASTPUBSUB_AUTH_SECRET_KEY` environment variable.\n\n#### Create Client\n\nCreate a new client with API credentials:\n\n```bash\ndocker run --rm \\\n  -e FASTPUBSUB_DATABASE_URL='postgresql+psycopg://YOUR_USER:YOUR_PASSWORD@YOUR_HOST:5432/YOUR_DATABASE' \\\n  -e FASTPUBSUB_AUTH_ENABLED='true' \\\n  -e FASTPUBSUB_AUTH_SECRET_KEY='your-secret-key' \\\n  allisson/fastpubsub create_client \"My Application\" \"*\" true\n```\n\n**Arguments:**\n1. Client name (e.g., \"My Application\")\n2. Scopes (e.g., \"*\" for admin, or \"topics:create topics:read\")\n3. Is active flag (true or false)\n\n**Output:**\n```\nclient_id=550e8400-e29b-41d4-a716-446655440000\nclient_secret=a1b2c3d4e5f6g7h8\n```\n\nSave the `client_id` and `client_secret` securely - the secret cannot be retrieved later.\n\n## ⚙️ Configuration\n\nConfigure fastpubsub using environment variables. All variables are prefixed with `FASTPUBSUB_`.\n\n### 🗄️ Database Configuration\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `FASTPUBSUB_DATABASE_URL` | PostgreSQL connection URL (required) | - |\n| `FASTPUBSUB_DATABASE_ECHO` | Enable SQLAlchemy query logging | `false` |\n| `FASTPUBSUB_DATABASE_POOL_SIZE` | Connection pool size | `5` |\n| `FASTPUBSUB_DATABASE_MAX_OVERFLOW` | Max overflow connections | `10` |\n| `FASTPUBSUB_DATABASE_POOL_PRE_PING` | Test connections before use | `true` |\n\n### 📝 Logging Configuration\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `FASTPUBSUB_LOG_LEVEL` | Log level (debug, info, warning, error) | `info` |\n| `FASTPUBSUB_LOG_FORMATTER` | Log format string | See below |\n\nDefault log format:\n```\nasctime=%(asctime)s level=%(levelname)s pathname=%(pathname)s line=%(lineno)s message=%(message)s\n```\n\n### 🔔 Subscription Defaults\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `FASTPUBSUB_SUBSCRIPTION_MAX_ATTEMPTS` | Maximum delivery attempts before DLQ | `5` |\n| `FASTPUBSUB_SUBSCRIPTION_BACKOFF_MIN_SECONDS` | Minimum retry delay | `5` |\n| `FASTPUBSUB_SUBSCRIPTION_BACKOFF_MAX_SECONDS` | Maximum retry delay | `300` |\n\n### 🌐 API Server Configuration\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `FASTPUBSUB_API_DEBUG` | Enable debug mode | `false` |\n| `FASTPUBSUB_API_HOST` | Server bind host | `0.0.0.0` |\n| `FASTPUBSUB_API_PORT` | Server port | `8000` |\n| `FASTPUBSUB_API_NUM_WORKERS` | Number of Gunicorn workers | `1` |\n\n### 🔐 Authentication Configuration\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `FASTPUBSUB_AUTH_ENABLED` | Enable authentication | `false` |\n| `FASTPUBSUB_AUTH_SECRET_KEY` | Secret key for JWT signing (required if auth enabled) | `None` |\n| `FASTPUBSUB_AUTH_ALGORITHM` | JWT signing algorithm | `HS256` |\n| `FASTPUBSUB_AUTH_ACCESS_TOKEN_EXPIRE_MINUTES` | Access token expiration time in minutes | `30` |\n\n### 🧹 Cleanup Workers Configuration\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `FASTPUBSUB_CLEANUP_ACKED_MESSAGES_OLDER_THAN_SECONDS` | Delete acked messages older than (seconds) | `3600` |\n| `FASTPUBSUB_CLEANUP_STUCK_MESSAGES_LOCK_TIMEOUT_SECONDS` | Release messages locked longer than (seconds) | `60` |\n\n### 📋 Example Docker Run with Configuration\n\n```bash\ndocker run -p 8000:8000 \\\n  -e FASTPUBSUB_DATABASE_URL='postgresql+psycopg://YOUR_USER:YOUR_PASSWORD@YOUR_HOST:5432/YOUR_DATABASE' \\\n  -e FASTPUBSUB_LOG_LEVEL='info' \\\n  -e FASTPUBSUB_API_NUM_WORKERS='4' \\\n  -e FASTPUBSUB_SUBSCRIPTION_MAX_ATTEMPTS='10' \\\n  allisson/fastpubsub server\n```\n\n## 📡 API Reference\n\n### 🔐 Authentication\n\nfastpubsub supports optional JWT-based authentication to secure API access. When authentication is disabled (default), all API endpoints are accessible without credentials. When enabled, clients must authenticate using OAuth2 client credentials flow.\n\n#### Scopes\n\nAuthentication uses a scope-based permission system. Scopes can be global or object-specific:\n\n**Global Scopes:**\n- `*` - Admin mode, full access to all resources and operations\n- `topics:create` - Can create new topics\n- `topics:read` - Can list or get topics\n- `topics:delete` - Can delete topics\n- `topics:publish` - Can publish messages to topics\n- `subscriptions:create` - Can create new subscriptions\n- `subscriptions:read` - Can list or get subscriptions\n- `subscriptions:delete` - Can delete subscriptions\n- `subscriptions:consume` - Can consume messages from subscriptions\n- `clients:create` - Can create new clients\n- `clients:update` - Can update clients\n- `clients:read` - Can list or get clients\n- `clients:delete` - Can delete clients\n\n**Object-Specific Scopes:**\n\nYou can restrict access to specific resources by appending the resource ID to the scope:\n- `topics:publish:my-topic-id` - Can only publish to the topic with ID \"my-topic-id\"\n- `subscriptions:consume:my-subscription` - Can only consume from the subscription with ID \"my-subscription\"\n\nMultiple scopes can be combined, separated by spaces: `topics:create topics:read subscriptions:read`\n\n#### Obtaining an Access Token\n\n**Request:**\n```http\nPOST /oauth/token\nContent-Type: application/json\n\n{\n  \"client_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"client_secret\": \"a1b2c3d4e5f6g7h8\"\n}\n```\n\n**Response:** `201 Created`\n```json\n{\n  \"access_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n  \"token_type\": \"Bearer\",\n  \"expires_in\": 1800,\n  \"scope\": \"topics:create topics:read\"\n}\n```\n\n#### Using the Access Token\n\nInclude the access token in the `Authorization` header for authenticated requests:\n\n```bash\ncurl -H \"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\" \\\n  http://localhost:8000/topics\n```\n\n### 👥 Clients\n\nClients represent applications or services that access the API. Each client has credentials (client_id and client_secret) and a set of scopes that define their permissions.\n\n#### Create a Client\n\n```http\nPOST /clients\nAuthorization: Bearer \u003ctoken\u003e\n```\n\n**Request Body:**\n```json\n{\n  \"name\": \"My Application\",\n  \"scopes\": \"topics:create topics:read subscriptions:consume\",\n  \"is_active\": true\n}\n```\n\n**Response:** `201 Created`\n```json\n{\n  \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"secret\": \"a1b2c3d4e5f6g7h8\"\n}\n```\n\n**Note:** The client secret is only returned once during creation. Store it securely.\n\n#### Get a Client\n\n```http\nGET /clients/{id}\nAuthorization: Bearer \u003ctoken\u003e\n```\n\n**Response:** `200 OK`\n```json\n{\n  \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"name\": \"My Application\",\n  \"scopes\": \"topics:create topics:read\",\n  \"is_active\": true,\n  \"token_version\": 1,\n  \"created_at\": \"2025-12-29T15:30:00Z\",\n  \"updated_at\": \"2025-12-29T15:30:00Z\"\n}\n```\n\n#### List Clients\n\n```http\nGET /clients?offset=0\u0026limit=10\nAuthorization: Bearer \u003ctoken\u003e\n```\n\n**Response:** `200 OK`\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n      \"name\": \"My Application\",\n      \"scopes\": \"topics:create topics:read\",\n      \"is_active\": true,\n      \"token_version\": 1,\n      \"created_at\": \"2025-12-29T15:30:00Z\",\n      \"updated_at\": \"2025-12-29T15:30:00Z\"\n    }\n  ]\n}\n```\n\n#### Update a Client\n\n```http\nPUT /clients/{id}\nAuthorization: Bearer \u003ctoken\u003e\n```\n\n**Request Body:**\n```json\n{\n  \"name\": \"Updated Application Name\",\n  \"scopes\": \"topics:read subscriptions:read\",\n  \"is_active\": true\n}\n```\n\n**Response:** `200 OK`\n\n**Note:** Updating a client increments its `token_version`, which invalidates all existing access tokens for that client.\n\n#### Delete a Client\n\n```http\nDELETE /clients/{id}\nAuthorization: Bearer \u003ctoken\u003e\n```\n\n**Response:** `204 No Content`\n\n### 🎯 Topics\n\nTopics are channels where messages are published.\n\n#### Create a Topic\n\n```http\nPOST /topics\n```\n\n**Request Body:**\n```json\n{\n  \"id\": \"user-events\"\n}\n```\n\n**Response:** `201 Created`\n```json\n{\n  \"id\": \"user-events\",\n  \"created_at\": \"2025-12-29T15:30:00Z\"\n}\n```\n\n#### Get a Topic\n\n```http\nGET /topics/{id}\n```\n\n**Response:** `200 OK`\n\n#### List Topics\n\n```http\nGET /topics?offset=0\u0026limit=10\n```\n\n**Response:** `200 OK`\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"user-events\",\n      \"created_at\": \"2025-12-29T15:30:00Z\"\n    }\n  ]\n}\n```\n\n#### Delete a Topic\n\n```http\nDELETE /topics/{id}\n```\n\n**Response:** `204 No Content`\n\n#### Publish Messages\n\n```http\nPOST /topics/{id}/messages\n```\n\n**Request Body:**\n```json\n[\n  {\n    \"event\": \"user.created\",\n    \"user_id\": \"123\",\n    \"country\": \"BR\"\n  },\n  {\n    \"event\": \"user.updated\",\n    \"user_id\": \"456\",\n    \"country\": \"US\"\n  }\n]\n```\n\n**Response:** `204 No Content`\n\n### 📬 Subscriptions\n\nSubscriptions receive messages from topics, optionally filtered.\n\n#### Create a Subscription\n\n```http\nPOST /subscriptions\n```\n\n**Request Body:**\n```json\n{\n  \"id\": \"user-processor\",\n  \"topic_id\": \"user-events\",\n  \"filter\": {\"country\": [\"BR\", \"US\"]},\n  \"max_delivery_attempts\": 5,\n  \"backoff_min_seconds\": 5,\n  \"backoff_max_seconds\": 300\n}\n```\n\n**Response:** `201 Created`\n\n**Filter Examples:**\n- `{\"country\": [\"BR\", \"US\"]}` - Messages where country is BR or US\n- `{\"event\": [\"user.created\"]}` - Only user.created events\n- `{\"premium\": [true]}` - Only premium users\n- `{}` or `null` - No filtering, receive all messages\n\n#### Get a Subscription\n\n```http\nGET /subscriptions/{id}\n```\n\n**Response:** `200 OK`\n\n#### List Subscriptions\n\n```http\nGET /subscriptions?offset=0\u0026limit=10\n```\n\n**Response:** `200 OK`\n\n#### Delete a Subscription\n\n```http\nDELETE /subscriptions/{id}\n```\n\n**Response:** `204 No Content`\n\n### 📨 Consuming Messages\n\n#### Consume Messages\n\nRetrieve messages from a subscription:\n\n```http\nGET /subscriptions/{id}/messages?consumer_id=worker-1\u0026batch_size=10\n```\n\n**Response:** `200 OK`\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n      \"subscription_id\": \"user-processor\",\n      \"payload\": {\n        \"event\": \"user.created\",\n        \"user_id\": \"123\",\n        \"country\": \"BR\"\n      },\n      \"delivery_attempts\": 1,\n      \"created_at\": \"2025-12-29T15:30:00Z\"\n    }\n  ]\n}\n```\n\n**Parameters:**\n- `consumer_id`: Unique identifier for the consumer (required)\n- `batch_size`: Number of messages to retrieve (default: 10, max: 100)\n\n### ✅ Acknowledging Messages\n\n#### Acknowledge (ACK) Messages\n\nMark messages as successfully processed:\n\n```http\nPOST /subscriptions/{id}/acks\n```\n\n**Request Body:**\n```json\n[\n  \"550e8400-e29b-41d4-a716-446655440000\",\n  \"660e8400-e29b-41d4-a716-446655440001\"\n]\n```\n\n**Response:** `204 No Content`\n\n#### Negative Acknowledge (NACK) Messages\n\nMark messages for retry:\n\n```http\nPOST /subscriptions/{id}/nacks\n```\n\n**Request Body:**\n```json\n[\n  \"550e8400-e29b-41d4-a716-446655440000\"\n]\n```\n\n**Response:** `204 No Content`\n\nNACKed messages will be retried with exponential backoff until `max_delivery_attempts` is reached.\n\n### 💀 Dead Letter Queue (DLQ)\n\nMessages that exceed `max_delivery_attempts` are moved to the DLQ.\n\n#### List DLQ Messages\n\n```http\nGET /subscriptions/{id}/dlq?offset=0\u0026limit=10\n```\n\n**Response:** `200 OK`\n\n#### Reprocess DLQ Messages\n\nMove messages back to the subscription queue for reprocessing:\n\n```http\nPOST /subscriptions/{id}/dlq/reprocess\n```\n\n**Request Body:**\n```json\n[\n  \"550e8400-e29b-41d4-a716-446655440000\"\n]\n```\n\n**Response:** `204 No Content`\n\n### 📊 Metrics\n\n#### Get Subscription Metrics\n\n```http\nGET /subscriptions/{id}/metrics\n```\n\n**Response:** `200 OK`\n```json\n{\n  \"subscription_id\": \"user-processor\",\n  \"available\": 150,\n  \"delivered\": 25,\n  \"acked\": 1000,\n  \"dlq\": 5\n}\n```\n\n**Metrics:**\n- `available`: Messages ready to be consumed\n- `delivered`: Messages currently locked by consumers\n- `acked`: Total acknowledged messages\n- `dlq`: Messages in the dead letter queue\n\n### 🏥 Health Checks\n\nHealth check endpoints are useful for monitoring and orchestration systems like Kubernetes.\n\n#### Liveness Probe\n\nCheck if the application is alive:\n\n```http\nGET /liveness\n```\n\n**Response:** `200 OK`\n```json\n{\n  \"status\": \"alive\"\n}\n```\n\nThe liveness endpoint always returns a successful response if the application is running. Use this endpoint to determine if the application needs to be restarted.\n\n#### Readiness Probe\n\nCheck if the application is ready to handle requests:\n\n```http\nGET /readiness\n```\n\n**Response:** `200 OK`\n```json\n{\n  \"status\": \"ready\"\n}\n```\n\n**Error Response:** `503 Service Unavailable`\n```json\n{\n  \"detail\": \"database is down\"\n}\n```\n\nThe readiness endpoint checks if the database connection is healthy. Use this endpoint to determine if the application should receive traffic.\n\n### 📊 Prometheus Metrics\n\nGet Prometheus-compatible metrics for monitoring:\n\n```http\nGET /metrics\n```\n\n**Response:** `200 OK` (Prometheus text format)\n```\n# HELP http_requests_total Total number of requests by method, path and status\n# TYPE http_requests_total counter\nhttp_requests_total{method=\"GET\",path=\"/topics\",status=\"200\"} 42.0\n...\n```\n\nThe metrics endpoint exposes application metrics in Prometheus format, including:\n- HTTP request counts and latencies\n- Request duration histograms\n- Active requests gauge\n- And other standard FastAPI metrics\n\nYou can configure Prometheus to scrape this endpoint for monitoring and alerting.\n\n## 💡 Usage Examples\n\n### Example 1: Setting Up Authentication\n\n```bash\n# 1. Generate a secret key\ndocker run --rm allisson/fastpubsub generate_secret_key\n# Output: new_secret=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6\n\n# 2. Start the server with authentication enabled\ndocker run -p 8000:8000 \\\n  -e FASTPUBSUB_DATABASE_URL='postgresql+psycopg://YOUR_USER:YOUR_PASSWORD@YOUR_HOST:5432/YOUR_DATABASE' \\\n  -e FASTPUBSUB_AUTH_ENABLED='true' \\\n  -e FASTPUBSUB_AUTH_SECRET_KEY='a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6' \\\n  allisson/fastpubsub server\n\n# 3. Create an admin client (requires initial client creation via CLI or direct DB access)\ndocker run --rm \\\n  -e FASTPUBSUB_DATABASE_URL='postgresql+psycopg://YOUR_USER:YOUR_PASSWORD@YOUR_HOST:5432/YOUR_DATABASE' \\\n  -e FASTPUBSUB_AUTH_ENABLED='true' \\\n  -e FASTPUBSUB_AUTH_SECRET_KEY='a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6' \\\n  allisson/fastpubsub create_client \"Admin Client\" \"*\" true\n# Output:\n# client_id=550e8400-e29b-41d4-a716-446655440000\n# client_secret=a1b2c3d4e5f6g7h8\n\n# 4. Get an access token\ncurl -X POST http://localhost:8000/oauth/token \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"client_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"client_secret\": \"a1b2c3d4e5f6g7h8\"\n  }'\n# Output: {\"access_token\": \"eyJhbGc...\", \"token_type\": \"Bearer\", \"expires_in\": 1800, \"scope\": \"*\"}\n\n# 5. Use the token to access protected endpoints\nTOKEN=\"eyJhbGc...\"\ncurl -H \"Authorization: Bearer $TOKEN\" http://localhost:8000/topics\n```\n\n### Example 2: Simple Pub/Sub\n\n```bash\n# 1. Create a topic\ncurl -X POST http://localhost:8000/topics \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"id\": \"notifications\"}'\n\n# 2. Create a subscription\ncurl -X POST http://localhost:8000/subscriptions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"id\": \"email-sender\",\n    \"topic_id\": \"notifications\"\n  }'\n\n# 3. Publish messages\ncurl -X POST http://localhost:8000/topics/notifications/messages \\\n  -H \"Content-Type: application/json\" \\\n  -d '[\n    {\"type\": \"email\", \"to\": \"user@example.com\", \"subject\": \"Welcome!\"}\n  ]'\n\n# 4. Consume messages\ncurl \"http://localhost:8000/subscriptions/email-sender/messages?consumer_id=worker-1\u0026batch_size=10\"\n\n# 5. Acknowledge messages\ncurl -X POST http://localhost:8000/subscriptions/email-sender/acks \\\n  -H \"Content-Type: application/json\" \\\n  -d '[\"550e8400-e29b-41d4-a716-446655440000\"]'\n```\n\n### Example 3: Creating Clients with Different Scopes\n\n```bash\n# Assuming you have an admin token\nADMIN_TOKEN=\"eyJhbGc...\"\n\n# 1. Create a client that can only publish to a specific topic\ncurl -X POST http://localhost:8000/clients \\\n  -H \"Authorization: Bearer $ADMIN_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Publisher Service\",\n    \"scopes\": \"topics:publish:notifications\",\n    \"is_active\": true\n  }'\n\n# 2. Create a client that can only consume from a specific subscription\ncurl -X POST http://localhost:8000/clients \\\n  -H \"Authorization: Bearer $ADMIN_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Consumer Service\",\n    \"scopes\": \"subscriptions:consume:email-sender\",\n    \"is_active\": true\n  }'\n\n# 3. Create a client with multiple permissions\ncurl -X POST http://localhost:8000/clients \\\n  -H \"Authorization: Bearer $ADMIN_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Multi-Purpose Service\",\n    \"scopes\": \"topics:create topics:read topics:publish subscriptions:create subscriptions:read\",\n    \"is_active\": true\n  }'\n```\n\n### Example 4: Filtered Subscription\n\n```bash\n# Create a subscription that only receives messages from BR and US\ncurl -X POST http://localhost:8000/subscriptions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"id\": \"regional-processor\",\n    \"topic_id\": \"user-events\",\n    \"filter\": {\"country\": [\"BR\", \"US\"]}\n  }'\n\n# Publish messages - only BR/US messages will be routed to this subscription\ncurl -X POST http://localhost:8000/topics/user-events/messages \\\n  -H \"Content-Type: application/json\" \\\n  -d '[\n    {\"event\": \"user.login\", \"user_id\": \"1\", \"country\": \"BR\"},\n    {\"event\": \"user.login\", \"user_id\": \"2\", \"country\": \"JP\"},\n    {\"event\": \"user.login\", \"user_id\": \"3\", \"country\": \"US\"}\n  ]'\n```\n\nThe subscription will only receive messages with `country` set to \"BR\" or \"US\" (messages for user 1 and 3, not user 2).\n\n### Example 5: Complex Filter\n\nThe filter feature uses a simple JSON style where keys are field names and values are arrays of acceptable values:\n\n```json\n{\n  \"filter\": {\n    \"event_type\": [\"order.created\", \"order.updated\"],\n    \"priority\": [\"high\", \"critical\"],\n    \"region\": [\"us-east\", \"us-west\"]\n  }\n}\n```\n\nThis filter matches messages that have:\n- `event_type` equal to \"order.created\" OR \"order.updated\"\n- AND `priority` equal to \"high\" OR \"critical\"\n- AND `region` equal to \"us-east\" OR \"us-west\"\n\n### Example 6: Handling Failed Messages\n\n```bash\n# Check metrics to see if there are DLQ messages\ncurl \"http://localhost:8000/subscriptions/email-sender/metrics\"\n\n# List DLQ messages\ncurl \"http://localhost:8000/subscriptions/email-sender/dlq\"\n\n# Reprocess DLQ messages after fixing the issue\ncurl -X POST http://localhost:8000/subscriptions/email-sender/dlq/reprocess \\\n  -H \"Content-Type: application/json\" \\\n  -d '[\"550e8400-e29b-41d4-a716-446655440000\"]'\n```\n\n### Example 7: Health Check Monitoring\n\n```bash\n# Check if the application is alive (for restart decisions)\ncurl \"http://localhost:8000/liveness\"\n\n# Check if the application is ready to serve traffic\ncurl \"http://localhost:8000/readiness\"\n```\n\n**Kubernetes example configuration:**\n\n```yaml\nlivenessProbe:\n  httpGet:\n    path: /liveness\n    port: 8000\n  initialDelaySeconds: 30\n  periodSeconds: 10\n\nreadinessProbe:\n  httpGet:\n    path: /readiness\n    port: 8000\n  initialDelaySeconds: 10\n  periodSeconds: 5\n```\n\n### Example 8: Monitoring with Prometheus\n\n```bash\n# Access Prometheus metrics\ncurl \"http://localhost:8000/metrics\"\n```\n\n**Prometheus scrape configuration:**\n\n```yaml\nscrape_configs:\n  - job_name: 'fastpubsub'\n    static_configs:\n      - targets: ['localhost:8000']\n    metrics_path: '/metrics'\n    scrape_interval: 15s\n```\n\n### Example 9: Multiple Consumers Processing the Same Subscription\n\nRunning multiple consumers in parallel for the same subscription helps process messages faster:\n\n```bash\n# Terminal 1: Start consumer worker 1\nwhile true; do\n  MESSAGES=$(curl -s \"http://localhost:8000/subscriptions/email-sender/messages?consumer_id=worker-1\u0026batch_size=10\")\n  echo \"$MESSAGES\" | jq -r '.data[].id' | while read -r msg_id; do\n    # Process message here\n    echo \"Worker 1 processing: $msg_id\"\n    # Acknowledge after processing\n    curl -X POST http://localhost:8000/subscriptions/email-sender/acks \\\n      -H \"Content-Type: application/json\" \\\n      -d \"[\\\"$msg_id\\\"]\"\n  done\n  sleep 1\ndone\n\n# Terminal 2: Start consumer worker 2\nwhile true; do\n  MESSAGES=$(curl -s \"http://localhost:8000/subscriptions/email-sender/messages?consumer_id=worker-2\u0026batch_size=10\")\n  echo \"$MESSAGES\" | jq -r '.data[].id' | while read -r msg_id; do\n    echo \"Worker 2 processing: $msg_id\"\n    curl -X POST http://localhost:8000/subscriptions/email-sender/acks \\\n      -H \"Content-Type: application/json\" \\\n      -d \"[\\\"$msg_id\\\"]\"\n  done\n  sleep 1\ndone\n```\n\nEach consumer uses a unique `consumer_id` to identify itself. Messages are locked to prevent duplicate processing across consumers.\n\n### Example 10: Error Handling and Retry Pattern\n\nProper error handling ensures reliable message processing:\n\n```python\nimport asyncio\nimport httpx\nimport logging\nfrom typing import Dict, Any\n\nlogger = logging.getLogger(__name__)\n\n\n# Define custom exceptions (implement based on your business logic)\nclass RetriableError(Exception):\n    \"\"\"Temporary error that should be retried\"\"\"\n    pass\n\n\nclass PermanentError(Exception):\n    \"\"\"Permanent error that should not be retried\"\"\"\n    pass\n\n\nasync def process_single_message(payload: Dict[str, Any]):\n    \"\"\"\n    Process a single message - implement your business logic here.\n\n    Raise RetriableError for temporary failures (network issues, service unavailable).\n    Raise PermanentError for permanent failures (invalid data, business rule violation).\n    \"\"\"\n    # Example implementation\n    if not payload.get(\"email\"):\n        raise PermanentError(\"Missing required field: email\")\n\n    try:\n        # Your actual processing logic here\n        # For example: send email, update database, call external API, etc.\n        logger.info(f\"Processing message: {payload}\")\n    except ConnectionError:\n        # Temporary network issue - retry later\n        raise RetriableError(\"Network connection failed\")\n\n\nasync def process_messages(subscription_id: str, consumer_id: str):\n    \"\"\"Consumer implementation with proper error handling\"\"\"\n    base_url = \"http://localhost:8000\"\n\n    async with httpx.AsyncClient() as client:\n        while True:\n            try:\n                # Fetch messages\n                response = await client.get(\n                    f\"{base_url}/subscriptions/{subscription_id}/messages\",\n                    params={\"consumer_id\": consumer_id, \"batch_size\": 10},\n                    timeout=30.0\n                )\n                response.raise_for_status()\n                messages = response.json()[\"data\"]\n\n                if not messages:\n                    await asyncio.sleep(1)\n                    continue\n\n                # Process each message\n                ack_ids = []\n                nack_ids = []\n\n                for message in messages:\n                    msg_id = message[\"id\"]\n                    try:\n                        await process_single_message(message[\"payload\"])\n                        ack_ids.append(msg_id)\n                        logger.info(f\"Successfully processed message {msg_id}\")\n                    except RetriableError as e:\n                        # Temporary error - retry later\n                        nack_ids.append(msg_id)\n                        logger.warning(f\"Retriable error for {msg_id}: {e}\")\n                    except PermanentError as e:\n                        # Permanent error - ack to prevent retries\n                        ack_ids.append(msg_id)\n                        logger.error(f\"Permanent error for {msg_id}: {e}\")\n\n                # Acknowledge successful/permanent-error messages\n                if ack_ids:\n                    await client.post(\n                        f\"{base_url}/subscriptions/{subscription_id}/acks\",\n                        json=ack_ids,\n                        timeout=10.0\n                    )\n\n                # NACK retriable errors for retry with backoff\n                if nack_ids:\n                    await client.post(\n                        f\"{base_url}/subscriptions/{subscription_id}/nacks\",\n                        json=nack_ids,\n                        timeout=10.0\n                    )\n\n            except httpx.HTTPError as e:\n                logger.error(f\"HTTP error: {e}\")\n                await asyncio.sleep(5)\n            except Exception as e:\n                logger.error(f\"Unexpected error: {e}\")\n                await asyncio.sleep(5)\n\n\nif __name__ == \"__main__\":\n    # Run the consumer\n    asyncio.run(process_messages(\"email-sender\", \"worker-1\"))\n```\n\n**Best practices shown:**\n- Distinguish between retriable and permanent errors\n- ACK permanent errors to prevent infinite retries\n- NACK retriable errors to trigger exponential backoff\n- Use timeouts to prevent hanging\n- Log processing status for debugging\n\n### Example 11: Monitoring and Alerting Setup\n\nSet up monitoring to track system health:\n\n```bash\n# Create a monitoring script (monitor.sh)\n#!/bin/bash\nSUBSCRIPTION_ID=\"email-sender\"\nAPI_URL=\"http://localhost:8000\"\n\n# Get metrics\nMETRICS=$(curl -s \"$API_URL/subscriptions/$SUBSCRIPTION_ID/metrics\")\n\nAVAILABLE=$(echo \"$METRICS\" | jq -r '.available')\nDELIVERED=$(echo \"$METRICS\" | jq -r '.delivered')\nDLQ=$(echo \"$METRICS\" | jq -r '.dlq')\n\necho \"Subscription: $SUBSCRIPTION_ID\"\necho \"Available messages: $AVAILABLE\"\necho \"Delivered messages: $DELIVERED\"\necho \"DLQ messages: $DLQ\"\n\n# Alert if DLQ has messages\nif [ \"$DLQ\" -gt 0 ]; then\n  echo \"⚠️  WARNING: $DLQ messages in dead letter queue!\"\n  # Send alert (e.g., to Slack, PagerDuty, etc.)\n  # curl -X POST YOUR_WEBHOOK_URL -d \"DLQ has $DLQ messages\"\nfi\n\n# Alert if messages are piling up\nif [ \"$AVAILABLE\" -gt 1000 ]; then\n  echo \"⚠️  WARNING: $AVAILABLE messages waiting (consumer may be slow)\"\nfi\n\n# Alert if too many messages are stuck in delivered state\nif [ \"$DELIVERED\" -gt 100 ]; then\n  echo \"⚠️  WARNING: $DELIVERED messages in delivered state (possible consumer crash)\"\nfi\n```\n\n### Example 12: Topic Fan-out Pattern\n\nOne message published to multiple subscriptions for different purposes:\n\n```bash\n# 1. Create a topic for order events\ncurl -X POST http://localhost:8000/topics \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"id\": \"orders\"}'\n\n# 2. Create multiple subscriptions for different purposes\n# Subscription for sending emails\ncurl -X POST http://localhost:8000/subscriptions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"id\": \"order-emails\",\n    \"topic_id\": \"orders\"\n  }'\n\n# Subscription for updating inventory\ncurl -X POST http://localhost:8000/subscriptions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"id\": \"order-inventory\",\n    \"topic_id\": \"orders\"\n  }'\n\n# Subscription for analytics (only completed orders)\ncurl -X POST http://localhost:8000/subscriptions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"id\": \"order-analytics\",\n    \"topic_id\": \"orders\",\n    \"filter\": {\"status\": [\"completed\"]}\n  }'\n\n# 3. Publish an order event\ncurl -X POST http://localhost:8000/topics/orders/messages \\\n  -H \"Content-Type: application/json\" \\\n  -d '[\n    {\n      \"order_id\": \"12345\",\n      \"customer_email\": \"customer@example.com\",\n      \"status\": \"completed\",\n      \"total\": 99.99\n    }\n  ]'\n\n# Result: All three subscriptions receive the message\n# - order-emails: Sends confirmation email\n# - order-inventory: Updates stock levels\n# - order-analytics: Records completed order for analytics\n```\n\nThis pattern allows you to decouple different parts of your system while maintaining a single source of truth for events.\n\n## 🎯 Best Practices\n\n### 🔧 Consumer Implementation\n\n- **Always acknowledge messages**: Use ACK for success, NACK for retriable failures\n- **Use unique consumer IDs**: Helps with debugging and metrics\n- **Handle idempotency**: Messages may be delivered more than once\n- **Implement timeouts**: Don't let message processing hang indefinitely\n- **Monitor DLQ**: Regularly check and handle dead-letter messages\n\n### 🏃 Performance Tips\n\n- **Batch consumption**: Use appropriate `batch_size` for your workload\n- **Multiple workers**: Run multiple consumers with different `consumer_id`\n- **Optimize filters**: More specific filters reduce unnecessary message delivery\n- **Regular cleanup**: Schedule cleanup jobs to maintain database performance\n- **Connection pooling**: Configure appropriate pool sizes for your load\n\n### 🔒 Reliability\n\n- **Run cleanup workers**: Essential for production deployments\n- **Monitor metrics**: Track available, delivered, acked, and DLQ counts\n- **Set appropriate timeouts**: Configure backoff settings based on your use case\n- **Database backups**: Regular PostgreSQL backups are crucial\n\n### 🔐 Security\n\n- **Enable authentication**: Set `FASTPUBSUB_AUTH_ENABLED=true` for production deployments\n- **Secure secret keys**: Generate strong secret keys using the `generate_secret_key` command\n- **Principle of least privilege**: Grant clients only the scopes they need\n- **Rotate credentials**: Regularly update client secrets by recreating clients\n- **Token management**: Access tokens expire after 30 minutes by default (configurable)\n- **Revoke access**: Update a client to increment its `token_version` and invalidate all existing tokens\n\n## 📚 API Documentation\n\nOnce the server is running, you can access the interactive API documentation:\n\n- **Swagger UI**: `http://localhost:8000/docs`\n- **ReDoc**: `http://localhost:8000/redoc`\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n\n## 🤝 Contributing\n\nContributions are welcome! Please feel free to submit issues and pull requests.\n\n## 🐛 Troubleshooting\n\n### Connection Issues\n\nIf you're having trouble connecting to the database from Docker:\n- Use `host.docker.internal` instead of `localhost` when running on Docker Desktop\n- Ensure your PostgreSQL allows connections from Docker networks\n- Check firewall rules if using a remote database\n\n### Messages Not Being Consumed\n\n- Verify the subscription exists and is properly configured\n- Check if filters are too restrictive\n- Look at metrics to see message counts\n- Ensure cleanup_stuck_messages is running if consumers crashed\n\n### High Message Latency\n\n- Increase the number of API workers (`FASTPUBSUB_API_NUM_WORKERS`)\n- Run multiple consumer instances\n- Check database connection pool settings\n- Review and optimize your subscription filters\n\n### Authentication Issues\n\n**401 Unauthorized / Invalid Token:**\n- Verify that `FASTPUBSUB_AUTH_ENABLED=true` is set on the server\n- Ensure you're using a valid access token obtained from `/oauth/token`\n- Check that the token hasn't expired (default: 30 minutes)\n- Verify the client is still active and hasn't been deleted or disabled\n- If the client was updated, old tokens are invalidated - request a new token\n\n**403 Forbidden / Insufficient Scope:**\n- Check that the client has the required scope for the operation\n- For object-specific operations, ensure the scope includes the resource ID\n- Use `*` scope for admin/testing purposes (not recommended for production)\n- Example: To publish to topic \"events\", client needs `topics:publish` or `topics:publish:events` scope\n\n**Missing FASTPUBSUB_AUTH_SECRET_KEY:**\n- Generate a secret key using `docker run --rm allisson/fastpubsub generate_secret_key`\n- Set it as an environment variable: `FASTPUBSUB_AUTH_SECRET_KEY=your-generated-key`\n- The same secret key must be used across all server instances\n\n## 🛠️ Development Setup\n\nThis section is for developers who want to contribute to fastpubsub or run it locally without Docker.\n\n### 📋 Prerequisites\n\n- **Python 3.14+**: The project requires Python 3.14 or later\n- **uv**: Fast Python package installer and resolver ([installation guide](https://github.com/astral-sh/uv))\n- **PostgreSQL 14+**: Local PostgreSQL instance for development\n- **make**: For running Makefile commands (usually pre-installed on Unix-like systems)\n\n### 🚀 Initial Setup\n\n1. **Clone the repository:**\n\n```bash\ngit clone https://github.com/allisson/fastpubsub.git\ncd fastpubsub\n```\n\n2. **Start a local PostgreSQL instance (optional):**\n\nIf you don't have PostgreSQL running, you can use the provided Makefile command:\n\n```bash\nmake start-postgresql\n```\n\nThis starts a PostgreSQL container with default credentials:\n- User: `fastpubsub`\n- Password: `fastpubsub`\n- Database: `fastpubsub`\n- Port: `5432`\n\nTo stop and remove the PostgreSQL container later:\n\n```bash\nmake remove-postgresql\n```\n\n3. **Set up environment variables:**\n\nCopy the sample environment file and adjust as needed:\n\n```bash\ncp env.sample .env\n```\n\nEdit `.env` to configure your local database connection and other settings.\n\n4. **Install dependencies:**\n\n```bash\n# Install uv if you haven't already\npip install uv\n\n# Install project dependencies (including development dependencies)\nuv sync\n```\n\nThis creates a virtual environment at `.venv` and installs all required packages.\n\n5. **Run database migrations:**\n\n```bash\nmake run-db-migrate\n```\n\nOr manually:\n\n```bash\nPYTHONPATH=./ uv run python fastpubsub/main.py db-migrate\n```\n\n### 🧪 Running Tests\n\nRun the full test suite:\n\n```bash\nmake test\n```\n\nOr manually with pytest:\n\n```bash\nuv run pytest -v\n```\n\nFor coverage reporting:\n\n```bash\nuv run pytest -v --cov=fastpubsub --cov-report=term-missing\n```\n\n### 🎨 Linting and Code Quality\n\nThe project uses [ruff](https://docs.astral.sh/ruff/) for linting and formatting, along with pre-commit hooks.\n\n**Run linting:**\n\n```bash\nmake lint\n```\n\nThis runs all pre-commit hooks including:\n- Ruff linting and formatting\n- Various file checks (trailing whitespace, YAML/JSON validation, etc.)\n- MyPy type checking\n\n**Install pre-commit hooks (recommended):**\n\n```bash\nuv run pre-commit install\n```\n\nAfter installation, the hooks will run automatically on every commit.\n\n**Manual formatting:**\n\n```bash\n# Format code with ruff\nuv run ruff format .\n\n# Run ruff checks with auto-fix\nuv run ruff check --fix .\n```\n\n### 🏃 Running the Server Locally\n\nStart the development server:\n\n```bash\nmake run-server\n```\n\nOr manually:\n\n```bash\nPYTHONPATH=./ uv run python fastpubsub/main.py server\n```\n\nThe API will be available at:\n- Server: `http://localhost:8000`\n- Swagger UI: `http://localhost:8000/docs`\n- ReDoc: `http://localhost:8000/redoc`\n\n### 🗄️ Database Migrations\n\n**Create a new migration:**\n\n```bash\nmake create-migration\n```\n\nThis generates a new migration file in `migrations/versions/`. Edit the file to define your schema changes.\n\n**Apply migrations:**\n\n```bash\nmake run-db-migrate\n```\n\n### 🐳 Building Docker Image Locally\n\nBuild the Docker image:\n\n```bash\nmake docker-build\n```\n\nOr manually:\n\n```bash\ndocker build --rm -t fastpubsub .\n```\n\n### 🔧 Development Workflow\n\n1. **Create a feature branch:**\n\n```bash\ngit checkout -b feature/your-feature-name\n```\n\n2. **Make your changes and test locally:**\n\n```bash\n# Run linting\nmake lint\n\n# Run tests\nmake test\n\n# Start the server to manually test\nmake run-server\n```\n\n3. **Commit your changes:**\n\nThe pre-commit hooks will automatically run linting and checks. Ensure all checks pass.\n\n```bash\ngit add .\ngit commit -m \"Your commit message\"\n```\n\n4. **Push and create a pull request:**\n\n```bash\ngit push origin feature/your-feature-name\n```\n\n### 📦 Project Structure\n\n```\nfastpubsub/\n├── fastpubsub/           # Main application package\n│   ├── api/              # FastAPI routes and API logic\n│   ├── services/         # Business logic and services\n│   ├── config.py         # Configuration management\n│   ├── database.py       # Database connection and migrations\n│   ├── models.py         # Pydantic models\n│   ├── main.py           # CLI entry point\n│   └── ...\n├── migrations/           # Alembic database migrations\n│   └── versions/         # Migration files\n├── tests/                # Test suite\n│   ├── api/              # API tests\n│   ├── services/         # Service tests\n│   └── ...\n├── Dockerfile            # Production Docker image\n├── Makefile              # Development commands\n├── pyproject.toml        # Project metadata and dependencies\n├── ruff.toml             # Ruff linter configuration\n├── .pre-commit-config.yaml  # Pre-commit hooks configuration\n└── README.md             # This file\n```\n\n### 💻 Available Makefile Commands\n\n| Command | Description |\n|---------|-------------|\n| `make test` | Run the test suite with pytest |\n| `make lint` | Run pre-commit hooks (linting, formatting, checks) |\n| `make start-postgresql` | Start a local PostgreSQL Docker container |\n| `make remove-postgresql` | Stop and remove the PostgreSQL container |\n| `make create-migration` | Create a new Alembic migration file |\n| `make run-db-migrate` | Apply database migrations |\n| `make run-server` | Start the development server |\n| `make docker-build` | Build the Docker image locally |\n\n### 🔍 Additional Tips\n\n- **Virtual Environment**: The project uses `uv` which automatically manages a virtual environment in `.venv/`\n- **Python Version**: Ensure you're using Python 3.14+ as specified in `pyproject.toml`\n- **Environment Variables**: All configuration is done via environment variables prefixed with `FASTPUBSUB_`\n- **IDE Setup**: Consider configuring your IDE to use the `.venv/bin/python` interpreter\n- **Database**: The test suite uses the same database configured in your `.env` file\n\n---\n\nMade with ❤️ using FastAPI and PostgreSQL\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fallisson%2Ffastpubsub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fallisson%2Ffastpubsub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fallisson%2Ffastpubsub/lists"}