Kickoff your app like a kangaroo through the stars: clean, fast, and unapologetically structured.
Cosmic FastAPI is a template for creating a FastAPI project following the Cosmic Python guidelines.
- Cosmic FastAPI
This template includes FastAPI using the fastest Pydantic V2 model validation. Designed for Event-Driven
Architecture (EDA) and Domain-Driven Design (DDD). Providing a clean and simple structure to start a new project. With
the addition of pre-commit hooks to ensure code quality. And ready to be used in a CI/CD GitHub Workflows pipeline.
The Architecture Decision Records define the modern Cosmic Python standard used by this template. They are the first reference for people and coding agents extending the project. The records preserve the book's domain-first philosophy while adopting FastAPI, Pydantic 2, SQLAlchemy 2, Alembic, uv, Ruff, Pyrefly, and pytest. Repository-level agent instructions turn those decisions into an implementation workflow. The Cosmic Python coverage matrix distinguishes included patterns from conditional extensions such as optimistic locking, broker adapters, and the transactional outbox.
Variables prefixed with FASTAPI_ are used to configure the API UI.
| Name | Description | Default Value |
|---|---|---|
| FASTAPI_DEBUG | Debug Mode | False |
| FASTAPI_PROJECT_NAME | Swagger Title | API GATEWAY |
| FASTAPI_PROJECT_DESCRIPTION | Swagger Description | ... |
| FASTAPI_PROJECT_LICENSE | License info | ... |
| FASTAPI_PROJECT_CONTACT | Contact details | ... |
| FASTAPI_VERSION | Application Version | template.version |
| FASTAPI_DOCS_URL | Swagger Endpoint | /docs |
Variables prefixed with UVICORN_ are used to configure the server.
| Name | Description | Default Value |
|---|---|---|
| UVICORN_HOST | Server Host | '127.0.0.1' |
| UVICORN_PORT | Server Port | 8000 |
| UVICORN_LOG_LEVEL | Log Level | 'info' |
| UVICORN_RELOAD | Enable/Disable Reload | False |
Variables prefixed with DATABASE_ configure relational persistence.
| Name | Description | Default Value |
|---|---|---|
| DATABASE_URL | SQLAlchemy database URL | sqlite+pysqlite:///./cosmic-fastapi.db |
| DATABASE_AUTO_CREATE_SCHEMA | Create tables at startup for local demos | False |
Use Alembic migrations for normal schema management. DATABASE_AUTO_CREATE_SCHEMA
exists for isolated tests and local demonstrations only.
As the application grows, keep the dependency direction visible in the directory structure. The domain remains plain Python. Framework validation belongs at the entrypoint boundary, and persistence belongs in adapters.
.
|-- migrations
|-- src/template
| |-- adapters # (3)
| | |-- models
| | |-- queries.py
| | |-- repository.py
| | `-- unit_of_work.py
| |-- domain # (1)
| | |-- commands
| | |-- events
| | `-- models
| |-- entrypoint # (4)
| | |-- monitor.py
| | |-- schemas.py
| | `-- users.py
| |-- service_layer # (2)
| | |-- handlers.py
| | |-- messagebus.py
| | |-- queries.py
| | |-- read_models.py
| | |-- repository.py
| | `-- unit_of_work.py
| |-- settings
| `-- bootstrap.py
`-- tests
|-- unit
|-- integration
`-- e2e
- (1). Domain, from Domain Driven Architecture.
- Commands, from Command Query Responsibility Segregation (CQRS). Commands are the messages that change the state of the system.
- Events describe facts that happened in the domain. Domain events do not imply Event Sourcing.
- Models, represent the domain entities, business objects of interest.
- (2). The service layer coordinates use cases with handlers, a message bus, repository ports, and a unit of work.
What is the difference between a domain service and a service layer?
- Application service (our service layer) its job is to handle requests from the outside world and to orchestrate an operation.
- Domain Service. This is the name for a piece of logic that belongs in the domain model but doesn't sit naturally inside a stateful entity or value object. For example, if you were building a shopping cart application, you might choose to build taxation rules as a domain service.
- (3). Adapters, it comes from "ports and adapters" terminology. This will fill up with any other abstractions around external I/O. Strictly speaking, you would call these secondary adapters or driven adapters, or sometimes inward-facing adapters.
- (4). Entrypoints are the places we drive our application from. FastAPI routes and Pydantic request or response schemas live here. In ports and adapters terminology, these are primary or driving adapters.
The root bootstrap.py module is the composition root. It wires concrete adapters to application ports without leaking
framework concerns into the domain.
Commands, Events, and Models are the building blocks of our Domain. Aggregates and behavior-rich domain objects
remain plain Python. Commands and events may use frozen Pydantic models as immutable schema contracts. See
ADR 0011. Python fields use snake_case;
JSON message payloads use camelCase as documented in
ADR 0012.
First, we define our domain models. These are the objects that represent the business concepts we’re working with. They should be as simple as possible and contain only the attributes that are essential to the business, using the business jargon. The idea is that, if you were to show these models to a non-technical person, but someone who understands the business process, they would be able to understand what the application does. Models encapsulate the behaviour, state, and business rules that govern the application. Models can encompass entities, aggregates, and sometimes even Value Objects.
Entities are objects that have distinct identities that run throughout their lifecycle. In other words, an entity is defined not just by its attributes but also by a unique identifier that differentiates it from other entities of the same type. Entities are mutable and can have their attributes modified while maintaining the same identity. They are often used to represent real-world objects or concepts that have an ongoing existence.
For example, in an e-commerce system, a "Product" can be an entity. Each product has a unique identifier, and its attributes (such as name, description, price) can change without changing its identity.
A Value Object is a concept from Domain-Driven Design (DDD). It's an object that represents a descriptive aspect of the
domain with no conceptual identity. In other words, a Value Object is defined solely by its attributes, and two Value
Objects with the same attributes are considered equal. They are immutable and can be thought of as "flyweight" objects
that are shared whenever their values are the same.
Value Objects can be part of a Model. In fact, they often enhance the expressiveness and maintainability of Models.
Value Objects help to define attributes with semantic meaning and encapsulate their validation and behaviour. In some
cases, a Model might consist of one or more entities and Value Objects that work together to represent and manage the
business logic and data.
Aggregates are clusters of related entities and value objects that are treated as a single unit. The aggregate is
the boundary within which changes are managed and consistency is maintained. One entity within the aggregate is
designated as the "aggregate root."
All interactions with the aggregate are done through this root entity. This helps ensure that the integrity and
consistency of the data is maintained within the aggregate.
For example, in the case of an e-commerce system, a "Shopping Cart" could be an aggregate. The shopping cart would be composed of multiple line items (entities representing products in the cart) and possibly other related information. All changes to the items in the cart would be managed through the shopping cart aggregate root.
Entities can be part of an aggregate, and an aggregate often includes one or more entities and possibly value objects. Aggregates define the transactional boundaries and consistency rules within the domain. They encapsulate business rules and enforce invariants to ensure that the data remains in a valid and consistent state.
Schemas define validated data contracts. Boundary schemas describe the input and output data of an API or external adapter. Immutable command and event schemas describe application messages.
In this project we use Pydantic for entrypoint schemas and immutable message schemas. Boundary objects are translated
when their external contract differs from the application message. See the
Pydantic documentation for runtime validation.
Commands and Events are the building blocks of our Event Driven Architecture. They are immutable Pydantic
models because they carry data contracts and have no behaviour.
Both commands and events are often used in software architectures to promote separation of concerns, modularity, and extensibility. By encapsulating actions or occurrences into discrete objects, it becomes easier to reason about the system and make changes without impacting other parts of the codebase.
In an API, boundary schemas validate client requests and are translated into commands. A handler executes the command inside a unit of work. Events raised by aggregates are dispatched to interested handlers after the command completes. HTTP responses remain boundary schemas rather than domain events.
Commands may enter through HTTP or message-queue adapters. Domain events may remain in-process or be translated into versioned integration events for Kafka, RabbitMQ, SQS, or another broker. Integration events and HTTP responses can describe the same business fact without sharing one lifecycle or envelope.
Write-side commands load aggregate roots through repositories and a unit of work. Read-only queries use purpose-built read models and reader adapters, as described in ADR 0014.
Commands represent actions or requests to be performed by a system. They typically encapsulate a specific intent or operation that needs to be executed. In software development, commands are often used in conjunction with a command pattern or a similar architectural pattern to decouple the sender of the command from its execution.
For example, in a web application, a command might be used to represent a user's request to update their profile information. The command object would contain the necessary data to carry out the update operation, and it would be executed by an appropriate handler or service.
Events represent notifications or signals that something has happened within a system. They convey information about a specific occurrence and are often used for communication between different components or modules of an application. Events are typically used in event-driven architectures or publish-subscribe patterns.
When an event occurs, it can be published to an event bus or a similar mechanism. Other components that have subscribed to that event can receive and react to it accordingly. This allows for loose coupling and enables different parts of the system to respond to events without direct dependencies on each other.
For example, in an e-commerce application, an event might be triggered when a new order is placed. Subscribed components, such as inventory management or shipping modules, can then react to this event by updating their respective states or initiating further actions.
The main idea behind Clean Architecture is to create a separation of concerns in software systems, allowing for easier maintenance, testing, and scalability. The core principle of Clean Architecture is the dependency rule, which states that dependencies should always point inward towards the core of the application and not outward towards external frameworks or tools. This helps keep the core of the application independent and flexible.
Clean Architecture typically consists of several layers, each with a specific responsibility:
- Entities: These are the core business objects and rules.
- Use Cases (Interacts): These encapsulate the business logic and orchestrate interactions between entities.
- Interfaces (Gateways): These define the interfaces that allow the use cases to interact with external data sources or systems.
- Frameworks and Drivers: These are the outermost layers that deal with the infrastructure, such as databases, web frameworks, UI, etc.
In our project, we can easily observe the different layers according to our directory structure. It's an architecture that "screams": by its naming conventions, we can easily understand what is the responsibility of each module.
This project uses make as an adaptation layer.
Run make help to see all available commands.
This package uses uv for dependency management.
Install uv in the system site_packages. DO NOT INSTALL IT in a virtual environment itself.
To install uv, run:
pip install uv-
Clone the repository
git clone "git@github.com/tomasanchez/cosmic-fastapi.git" -
Install dependencies
cd cosmic-fastapi && uv sync --dev
-
Activate pre-commit hooks (Optional)
Using pre-commit to run some checks before committing is highly recommended.
To activate the pre-commit hooks, run:
pre-commit install
To run the checks manually, run:
uv run pre-commit run --all-files
-
Apply database migrations:
make migrate
-
Run:
uv run python -m template.main
-
Go to http://localhost:8000/docs to see the API documentation.
You can run the tests with:
uv run pytestor with the make command:
make testTo generate a coverage report add --cov src.
uv run pytest --cov srcOr with the make command:
make cover- FastAPI official Documentation
- Pydantic official Documentation
- UV official Documentation
- Cosmic Python
This project is licensed under the terms of the MIT licence unless otherwise specified. See LICENSE for
more details or visit https://mit-license.org/.
This project was designed and developed by Tomás Sánchez <info@tomsanchez.com.ar>.
Deeply inspired by FastAPI-MVC following Cosmic Python guidelines for project structure.
If you find this project useful, please consider supporting its development by sponsoring it.
