FastKit Core: architecture, layers, and infrastructure
In our previous post, we argued that FastAPI's greatest strength — its total freedom — is also its biggest challenge when building business applications. FastKit solves this by introducing a standardized structure on top of FastAPI, without replacing anything underneath it.
But what does that structure actually look like? How does FastKit think about separating concerns, and why does that matter for a real project? This post answers those questions by walking through the architectural principles behind FastKit Core and every module it provides.
The Core Idea: Solve Infrastructure Once
Every web application — regardless of size or architecture style — solves the same set of foundational problems: talking to a database, validating input, handling errors, caching expensive operations, and supporting multiple languages. These are not business problems. They are infrastructure problems, and they are largely identical across every project.
FastKit Core's philosophy is simple: solve these problems once, in a standardized, testable, and replaceable way, so your team never has to solve them again. The moment you start a new FastKit project, that foundational 20% is already done. You start at feature code, not at boilerplate.
FastKit Core is not a framework on top of FastAPI. It is a structured foundation underneath your application logic — one that stays out of your way once it is in place.
The Four-Layer Architecture
FastKit Core organizes every application into four distinct layers. Each layer has a single, well-defined responsibility, and each communicates only with its immediate neighbor. This is the architectural decision that keeps FastKit projects maintainable as they grow.
Layer 1 — HTTP Layer (Router)
This is the entry point of every request. Its only responsibility is to receive HTTP input, validate the shape of that input against a Pydantic schema, and hand it off to the service layer. It knows nothing about business logic, databases, or infrastructure. A FastKit router is thin by design — if you find yourself writing complex logic in a router, that logic belongs in the service layer.
Layer 2 — Service Layer
This is where your application logic lives. Services orchestrate operations: they call repositories to fetch data, apply business rules, fire events, and return results. A service knows about repositories, but it does not write SQL. It knows about events, but it does not send emails directly. It is the coordinator, not the executor.
Layer 3 — Repository Layer
Repositories are the only part of the application that talks to the database. They abstract all data access behind a clean, query-oriented interface. If you ever switch from PostgreSQL to MySQL — or from SQLAlchemy to another ORM — you only change the repository. The service layer above it does not notice.
Layer 4 — Database Layer
At the base sit the SQLAlchemy models with FastKit's built-in mixins. Timestamps, soft deletes, slug generation, and multi-language content are declared at this level and automatically available to everything above it.
Orthogonality: Why Layers Don't Talk Across the Stack
The term orthogonality in software architecture means something practical: a change in one layer should not cause ripple effects in unrelated layers. FastKit enforces this through Dependency Injection — each layer receives its dependencies from the outside, rather than creating them internally.
In practice, a UserService does not instantiate a UserRepository directly. Instead, it declares that it needs one, and FastAPI's dependency injection system provides it. This small distinction has a large consequence: you can swap, mock, or extend any layer in isolation. Testing a service is just a matter of injecting a mock repository — no database required.
When layers are orthogonal, bugs are local. A problem in the repository does not corrupt service logic. A refactored service does not break the HTTP contract. Each layer fails — and improves — independently.
This is the principle that makes FastKit projects easy to onboard into. A new developer can read a service class and fully understand the business logic without tracing through database queries or HTTP parsing code. Everything is in its place, and its place is clearly defined.
Infrastructure Modules: The Solved Problems
Beyond the four-layer structure, FastKit Core ships with a set of infrastructure modules that address the most common cross-cutting concerns in production applications. Each module is independent — use all of them, or only the ones your project needs.
Database & Repository Pattern → docs
The database module provides base SQLAlchemy models with composable mixins for timestamps, soft deletes, and slug generation. On top of that, the repository pattern delivers a clean, Django-style query interface — filtering, ordering, pagination, and transactions — without writing raw SQL. Full async support is included with complete feature parity.
The standout feature here is TranslatableMixin — a unique solution for multi-language model content that is genuinely difficult to implement cleanly from scratch. Declare a field as translatable, and the repository handles locale-aware queries automatically.
Service Layer → docs
BaseCrudService and AsyncBaseCrudService provide complete CRUD operations out of the box, with a lifecycle hook system that lets you inject logic at any point: before_create, after_create, before_update, after_delete, and more. You extend the base service, override the hooks you need, and ignore the rest. The SlugServiceMixin handles automatic slug generation for content-driven applications.
Validation → docs
Built on top of Pydantic, the validation module provides BaseSchema with reusable validator mixins for common patterns — email format, password strength, non-empty strings, and more. All validation errors are automatically formatted into a consistent, translated structure that frontend and mobile teams can depend on without writing defensive parsing code on their end.
HTTP Utilities → docs
Standardized response formatters ensure that every endpoint — whether it returns a single item, a paginated list, or an error — follows the same envelope format. Global exception handlers translate Python exceptions into clean, predictable HTTP responses. The module also includes RequestIDMiddleware for distributed tracing and LocaleMiddleware for automatic locale detection from request headers.
Translations → docs
The translation system is built around simple JSON translation files — one per language, organized by domain. TranslationManager handles locale detection, parameter substitution, and fallback to a default language when a translation is missing. This same system powers the translated validation error messages, so i18n is consistent across your entire application from day one.
Caching → docs
The caching layer ships with two backends out of the box: InMemoryBackend for development and testing, and RedisBackend for production. The @cached decorator lets you cache the result of any function with a single line of code, with configurable TTL. Pattern-based cache invalidation means you can clear related cache keys by prefix — essential for keeping cached data consistent when records are updated or deleted.
Events → docs
The event system is a signal-based pub/sub mechanism for decoupling side effects from business logic. Instead of a service directly calling a mailer, a logger, or a notification system, it fires a signal — user.created, invoice.paid — and any number of receivers handle it independently. Receivers are isolated from each other: an exception in one does not prevent the others from running. Both sync and async receivers are supported, and the connected_to context manager makes event-driven code trivial to test.
Configuration → docs
Environment-aware configuration built on Pydantic Settings. Define your settings once as typed classes, load them from .env files or environment variables, and access them throughout the application with full IDE autocompletion. Multiple environment profiles — development, staging, production — are supported out of the box.
FastKit Core is not an all-or-nothing proposition. If your project only needs the Repository Pattern and HTTP utilities today, use only those. The modules are independent by design — adding more later does not require restructuring anything you have already written.
Async/Sync Parity: No Second-Class Citizens
One architectural decision worth highlighting explicitly: FastKit Core treats synchronous and asynchronous code as equals. Every pattern — repositories, services, events, cache — has a full async implementation that provides the same features and follows the same conventions as its synchronous counterpart.
This matters in practice because real applications are rarely all-async or all-sync. You might have legacy synchronous database drivers alongside modern async I/O. FastKit does not force you to choose one model for your entire codebase. You use what fits each situation, and the infrastructure layer supports both without compromise.
What FastKit Core Is Not
It is worth being explicit about the boundaries. FastKit Core does not replace FastAPI — it sits alongside it. Every router is still a standard FastAPI router. Every dependency is still resolved by FastAPI's DI system. Every response is still served by Uvicorn or any ASGI server you choose.
FastKit Core does not dictate your project structure beyond the layer conventions. It does not choose your authentication library, your task queue, or your deployment strategy. It does not introduce hidden magic or global state. What you see is what runs.
The goal is to give your project a solid, consistent foundation — and then get out of the way.
What's Next
This post has given you the conceptual map. The next posts in this series go one layer at a time — with real code from a CRM Invoicing application built entirely with FastKit. You will see how each module behaves under real requirements, not toy examples.
Ready to explore FastKit Core in your next project? ⭐ Star us on GitHub and give it a try.