fastapi-sqlalchemy-asyncpg: a pinned FastAPI and Postgres stack
Integration of FastAPI framework supported by Pydantic with SQLAlchemy ORM and PostgreSQL on asyncpg driver
At a glance
- What is it?
- This is a demonstration application rather than a library, and its value is that every choice is visible and pinned. FastAPI with Pydantic 2 for validation, SQLAlchemy 2 against PostgreSQL 17 through asyncpg, Alembic for migrations, JWT sessions in Redis, Polars for spreadsheet import, structured logging through Rotoger, and a Makefile and Compose file that bring the whole thing up in four commands.
- Who is it for?
- fastapi-sqlalchemy-asyncpg suits a developer who wants to see one modern async Python stack assembled end to end, with the deployment pieces included rather than implied, and who plans to unpin and own it afterwards. It does not suit a team that wants a starting point to copy and keep current, because every dependency is pinned to an exact version and the Python floor is 3.14.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 82 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A demonstration application, pinned to the patch
This project describes itself as a demonstration application that demonstrates the integration of FastAPI with Pydantic 2 for data validation, the SQLAlchemy ORM, an asynchronous connection to a PostgreSQL 17 database and the asyncpg client library.
The interesting word in that sentence is demonstration. This is not a library and not a framework, it is a working application that puts a full stack on the table so you can read how the pieces fit.
The version policy is what makes it a snapshot rather than a template. Every runtime dependency is pinned to an exact version, not a range: fastapi at 0.139.2, pydantic with the email extra at 2.13.4, pydantic-settings at 2.14.2, sqlalchemy at 2.0.51, uvicorn with the standard extra at 0.51.0, asyncpg at 0.31.0, alembic at 1.18.5, httpx, uvloop, httptools, rich, pyjwt, redis at 8.0.1, bcrypt at 5.0.0, polars at 1.42.1, python-multipart, fastexcel at 0.20.2, granian at 2.7.9, apscheduler with the redis and sqlalchemy extras, rotoger at 0.3.0, pyinstrument, websockets and jinja2.
And the Python requirement is narrow: requires-python is greater than or equal to 3.14 and less than 3.15. That is a recent interpreter, and it means the application runs on exactly one Python minor version.
The licence is MIT, the package name is fastapi-sqlalchemy-asyncpg at version 3.0.0, and there are no GitHub releases. The last push to the repository was on 2026-08-02, and the topics list the stack plainly: asyncio, asyncpg, docker, fastapi, granian, postgresql, pydantic, sqlalchemy and uvicorn.
Four make commands, and a Compose file that shows the topology
The getting started section is four commands, and the Makefile is the entry point for everything: building, running, testing and managing the project, with make help listing the available commands.
1. make docker-build
2. make docker-up > alternatively > make docker-up-granian
3. make docker-apply-db-migrations
4. make docker-feed-databaseThe order matters, and the alternative in the second line is the first sign of the design. There are separate targets for uvicorn and for granian, so the same application can be run under either server.
The Compose file is the clearest description of the topology. The API service builds from the repository Dockerfile, loads both .env and .secrets as environment files, and runs uvicorn against the application entry point:
uvicorn app.main:app
--host 0.0.0.0 --port 8080
--lifespan=on --use-colors --loop uvloop --http httptools --reloadThe uvloop and httptools options are the event loop and the HTTP parser, both chosen for speed in an async application, and the reload flag is there because this is a development configuration.
The volumes are worth noting: the application, tests, templates and alembic directories are mounted into the container, so a change on the host is a change in the running service without rebuilding the image. And the service depends on postgres being healthy and on redis having started, which is the ordering that stops the application starting before its database is ready.
The Postgres service is itself built from a Dockerfile in the db/ directory, with a create.sql file mounted into the container's entrypoint initialisation directory, and a health check that runs pg_isready against the configured database and user every five seconds. Redis is a separate service with its own port, and the application talks to it for token storage.
The data: Shakespeare, generated models and spreadsheet import
The feed-database step needs a dataset, and the choice is a good one for a demonstration.
Finding a good and engaging dataset can be challenging, and the works of William Shakespeare provide an excellent foundation for demonstrating read-only declarative models and executing a wide variety of complex queries. The dataset comes from the opensourceshakespeare repository, and the data models were subsequently generated using sqlacodegen.
So the models in the project are generated from a real schema rather than hand written for the demo, which means the declarative models are the kind you would get from a legacy database, with the awkward column names and the missing relationships that a real schema has. For a demonstration, that is more instructive than a tidy invented schema.
The spreadsheet import is the other half. The project uses the polars library to read Excel data into a DataFrame by passing the bytes to the read_excel function, and the calamine engine can be used for reading all major types of Excel workbook, the xlsx, xlsb and xls formats, and is dramatically faster than the other options, using the fastexcel module to bind calamine.
For local development without Docker, the README gives the uv route:
uv sync
source .venv/bin/activateThe project also carries a uv.lock, so the local environment resolves to the same versions as the containers. That matters in a repository where the dependencies are pinned exactly, because a lockfile is what stops the pins from drifting the moment somebody installs without the lock.
Logging, authentication and the scheduler
Three subsystems are worth reading because each one is a decision rather than a default.
Logging goes through Rotoger, a small library the project describes as a production-ready logging setup for modern asynchronous applications. It is built on structlog and brings four things. The logs are structured, so entries are JSON and are machine readable and easy to query in a log management system. The logging is asynchronous and non-blocking, designed for frameworks like FastAPI, so that I/O-bound logging does not hold up the event loop. The JSON serialisation uses orjson, one of the fastest JSON libraries for Python. And the log rotation is built in, so log file sizes and retention policies are managed inside the application without relying on external tools like logrotate.
Authentication is JWT with Redis as the token storage, plus password hashing with bcrypt. Storing the tokens in Redis rather than in the database or in memory is what makes them survive a restart and be shared between workers.
Scheduling uses APScheduler, described as a flexible in-process task scheduler with cron-like capabilities, which lets you schedule jobs to run at specific times or intervals, and supports multiple job stores. The dependency line shows the extras enabled: apscheduler with the redis and sqlalchemy extras.
Those two extras are the interesting part. An in-process scheduler runs inside the worker process, which means with more than one worker the same job can be scheduled several times, or not at all if the process that held it died. Pointing APScheduler at Redis or the database as the job store makes the schedule shared between workers and durable across restarts, and that is what the extras are for.
The wider repository, and the profiling and test tooling
Beyond the application, the repository holds the pieces a team would otherwise assemble separately.
There is compose-ha.yml for a high availability sample with nginx as the load balancer, granian-compose.yml for the alternative server, and valkey-compose.yml for a different Redis implementation. There is a performance/ directory, and the README has a section on performance profiling with Pyinstrument, which is also a dependency, so the profiling story is part of the project rather than an afterthought.
The tests use pytest with coverage, and the test group includes inline-snapshot for asserting on values without writing them out by hand, dirty-equals for approximate comparisons, and polyfactory for generating test data from the models. The coverage configuration sits in a .coveragerc at the root.
The tooling is strict. mypy runs in strict mode with the alembic directory excluded, ruff targets Python 3.14 with the usual pycodestyle, pyflakes, isort, bugbear, comprehensions and pyupgrade rules plus an unused-argument rule, and there is a pyrefly configuration that turns redundant-cast into a warning. The development group pins ruff at 0.15.22 and adds devtools, pyupgrade, ipython and tryceratops.
Then there is the alembic directory with its own configuration file, the scripts/ directory, the templates/ and static/ directories for the Jinja2 templates, and the db/ directory with the database image and the create.sql seed. The .env and .secrets files are split, which is the arrangement you want for a deployment where the secrets are mounted separately from the configuration.
Where this stack is the wrong starting point
There are cases where this repository is the wrong base, and the reasons are about its nature as a demonstration.
The first is the pinning. Every dependency is at an exact version and the Python floor is 3.14 only, which means this is a reproducible snapshot from a moment in time rather than a base you can start from and keep current. Copying it gives you a project that will need every dependency reviewed on its own schedule, and it inherits a Python version that many deployment platforms do not offer yet.
The second is the demonstration content. The dataset is Shakespeare, the authentication is a sample, and the endpoints are built to exercise the stack. None of that is wrong for a demonstration and all of it is wrong for a product, so the work of replacing it is the work you would have done anyway.
The third is the scheduler. APScheduler is in-process, and while the Redis and SQLAlchemy job stores are what make it behave across several workers, that is a configuration decision with operational consequences, and a project that needs guarantees about job execution may want a separate worker service instead.
The alternative approach is to build the stack yourself from the components, with unpinned or ranged dependencies, a migrations-first workflow and a scheduler that is its own service. The difference in approach is that this repository has already answered the questions that are hard to answer blind, which HTTP client to use, whether to pin, how to structure the Compose topology, how to get the event loop and the parser fast, and how to keep the logs queryable. What it does not do is answer them for your constraints, because it is built to demonstrate rather than to serve.
Editorial conclusion
fastapi-sqlalchemy-asyncpg suits a developer who wants to see one modern async Python stack assembled end to end, with the deployment pieces included rather than implied, and who plans to unpin and own it afterwards. It does not suit a team that wants a starting point to copy and keep current, because every dependency is pinned to an exact version and the Python floor is 3.14. Check three things before you adopt it as a base: that you are on Python 3.14, that the job scheduling approach fits your process model, since the scheduler is in-process and the Redis and SQLAlchemy job stores are what make it work across workers, and which parts of the sample are genuinely yours to keep, particularly the authentication, which is a demonstration. The last push to the repository was on 2026-08-02 and the project publishes no GitHub releases.
Frequently asked questions
What is the fastapi-sqlalchemy-asyncpg project?
It is a demonstration FastAPI application built on Pydantic 2 for validation, the SQLAlchemy ORM, an asynchronous connection to PostgreSQL 17 through asyncpg, Alembic migrations, JWT authentication with Redis token storage, Polars for spreadsheet import, Rotoger for structured logging and APScheduler for background jobs.
How do I run the fastapi-sqlalchemy-asyncpg application?
Through the Makefile: make docker-build, then make docker-up or make docker-up-granian, then make docker-apply-db-migrations and make docker-feed-database. For local development without Docker, uv sync followed by activating the virtual environment is the documented route.
Which Python and dependency versions does the project need?
Python 3.14 exactly, with requires-python set to 3.14 and below 3.15, and every runtime dependency pinned to an exact version, including fastapi 0.139.2, sqlalchemy 2.0.51, asyncpg 0.31.0, redis 8.0.1, polars 1.42.1 and apscheduler 4.0.0a6.
What logging setup does the project use?
Rotoger, a small library built on structlog, providing structured JSON logs that are easy to query, asynchronous non-blocking logging that does not hold up the event loop, orjson for fast serialisation, and built-in log rotation so external tools like logrotate are not needed.
How does the project schedule background jobs?
With APScheduler, an in-process scheduler with cron-like capabilities that is installed with the redis and sqlalchemy extras so the job store can be shared between workers and survive a restart, which matters once the application runs with more than one worker process.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/grillazz-fastapi-sqlalchemy-asyncpg)Was this useful?