---
title: 'You''re Probably Building FASTAPI Apps WRONG (Clean Architecture)'
source: 'https://youtube.com/watch?v=H9Blu0kWdZE'
video_id: 'H9Blu0kWdZE'
date: 2026-06-16
duration_sec: 0
---

# You're Probably Building FASTAPI Apps WRONG (Clean Architecture)

> Source: [You're Probably Building FASTAPI Apps WRONG (Clean Architecture)](https://youtube.com/watch?v=H9Blu0kWdZE)

## Summary

This video demonstrates how to build a FastAPI application using clean architecture principles, separating domain, application, and infrastructure layers. It covers setting up entities, database, authentication, rate limiting, testing, and Docker deployment to create a scalable, professional backend.

### Key Points

- **Clean Architecture Overview** [0:00] — The video introduces a clean architecture for FastAPI with domain (entities), application (business logic), and infrastructure (database, rate limiting) layers, plus testing.
- **Project Setup and Dependencies** [1:53] — Starts with a source directory, main.py, requirements files, and installs dependencies for development and production.
- **Logging Configuration** [2:37] — Creates a logging.py file to set up log levels (info, warning, error, debug) for monitoring and debugging.
- **Rate Limiting Implementation** [4:21] — Implements rate limiting using slowapi to protect endpoints from abuse, based on IP address.
- **Entities: Users and Todos** [5:34] — Creates SQLAlchemy models for users and todos with UUID primary keys, foreign keys, and relationships.
- **Database Setup and Environment Variables** [7:42] — Sets up database core with SQLAlchemy engine, session, and dependency injection; uses .env file for database URL.
- **Authentication Module (OAuth2, JWT, bcrypt)** [9:30] — Builds auth module with controller, service, and models for registration, login, token creation/verification, and password hashing.
- **Users and Todos Modules** [14:16] — Creates separate modules for users (get user, change password) and todos (CRUD operations) with services and controllers.
- **Exception Handling** [18:39] — Adds custom exception classes for cleaner error handling and status codes.
- **API Router and Main Application** [19:16] — Registers all routers in api.py and configures logging in main.py; runs uvicorn with reload.
- **Unit and End-to-End Testing** [21:00] — Creates test suite with unit tests for services and end-to-end tests for endpoints using pytest and a test SQLite database.
- **Docker Deployment** [25:16] — Creates Dockerfile and docker-compose.yml to deploy FastAPI app with PostgreSQL; runs with docker compose up --build.

## Transcript

knowing how to architect your
application is a big deal when it comes
to building a fast API product we don't
just want to build a backend application
we want to build something that is clean
and easy to scale so in this video we'll
be creating a fast API application that
is following all of the best practices
which includes the domain layer like
where the enti development lives the
application layer where all the business
logic of the application lives the
infrastructure layer which is where the
database and rate limiting lives and
then we're also going to include all of
the unit testing and end to end testing
this video will cover everything you
need to get started with a clean
architecture for your next product if
you're new to the channel I'm Eric Roby
a software engineer with over a decade
of experience and I've helped over
100,000 developers learn and grow within
their craft all right so with that let's
dive into some code all right so I
already have a environment created so I
have a directory that's already created
called clean architecture which is
hosting our application inside our
directory I just have a source directory
with a empty main.py file and then I
have a requirements. text file which has
all of our dependencies that we need to
run our application in a production
environment so not necessarily what we
need to run on our local machine but
what we need for a production
environment now in my requirements dd.
text this is what we need for for our
development environment which is like
you right now on your personal machine
if you want to code so what we can do
here is PIP install - R requirements
ddev
dotex this will install all the
dependencies that we need for our entire
project so now what we can do is let's
go ahead and jump into our main.py file
the first thing that we want to do is
just add all of the Imports that we're
going to be creating in the future and
that we need for this main.py file which
is just our database our entities our
API and our fast API and then what we
want to add here is simply our app which
is equal to fast API and then we're
going to have this base. metadata.
create all bind equals engine this we
only want to uncomment when we're
creating new tables when we run our
application for the first time this will
break our tests later on so I leave it
uncommented but it is a good thing to
have just in case you want to rapidly be
able to create new entities perfect now
we're going to get a bunch of errors and
that's okay cuz we're going to be
building everything step by step now the
next thing I'm going to just Implement
just so we have all of our
configurations down pth is something
called logging so I'm going to create
logging piy and inside logging it's
going to be a way for us to be able to
document kind of all of our endpoints so
if something happens in production or
happens on our environment where we
don't have to necessarily jump in and
start debugging everything with code we
can go ahead and just kind of look at
the logs that our application is
creating and and just be able to
identify hey that's where the error is
just by looking at the terminal so in
here I'm going to add this logging which
we're going to import logging we're
going to import an En from our string
and then we're going to just have this
log format debug which is showing us how
our log is going to format um debugs
then we're going to have our log levels
which is going to be info worn air and
debug and what this does is when we add
these into our code it'll tell us like
hey this is just information like maybe
this is just something that you might
want to know we might be like hey this
is a warning in our code if this happens
this is specifically going to cause um
Downstream effects then there's like air
like our application's broken and then
we can have debug errors for like in the
future so if you have like an object
that's getting weird we can just throw
in a debug so then when we're running
our application it'll be like hey this
is um the object debugged now what our
configure logging does is it takes in
one of these log levels and then we kind
of just do different things so it takes
it it capitalized which it already is
capitalized we find what level it is in
our log levels and then we're just kind
of setting up different permissions
based on the log level and then we just
enable our basic configuration to be
logging and if you've never logged
before we're going to be implementing
this in our application so you'll be
able to see exactly what's happening but
overall this is what it's going to look
like when you want to set it up from
scratch the next thing we're going to be
setting up is our rate limiting so
inside our source directory let's go
ahead and create a new file called rate
limiting and inside here we want to
implement our slow API which was what we
installed now this will allow us to be
able to do rate limiting on an endpoint
so what that means is we are saying hey
maybe our register we only allow a
certain user to be able to hit our
register three times before maybe we're
saying you need to take a break and this
allows us to protect from like DDS
attacks or some other kind of attacks
where people are trying to spam or use
Bots to call our endpoints over and over
and over again just to kind of collect
data we're putting rate limiting on in
this git remote address means it's going
to collect the IP address and we're
going to say hey based on your IP
address we are not going to allow you to
call certain endpoints you know X number
of times because it could be security
maybe the the endpoints cost money it's
something just to be like whoa slow down
you're calling way too many in points
way too um too fast so that's what we're
implementing right here and then we're
going to be adding it in our endpoints
here in a little bit and now what we
want to do is make sure we make this a
python package by saying underscore
uncore and knit underscore uncore .p
that means we can now call these very
easily within our project and now let's
go ahead and create our first package
now our first package is going to be
called entities now let's go ahead and
just say entities just like we did
before inside entities we need to say
new file
uncore and
nitpy that makes entities a python
package and now in this project we're
going to have two entities one for our
to-dos and one for our users so we can
go ahead and create um two more files in
here to-do and
users now first let's go into our to.py
and let's add some code right here so
what we're saying is we are simply going
to be adding our SQL Alchemy we're going
to be using uu ID for our um primary key
and then we're going to be using date
time and some other things in here so
here we're just saying what the priority
of this to-do is which is going to be 0
to 4 we're going to create this now
to-do class and we're going to name this
table todos plural we have an ID of
column uid as uu ID is true it's going
to be our primary key and the default is
to is to use The UU ID for we're going
to have a user ID which is going to be a
foreign key back to the users which we
haven't created yet and that's also
going to be a uu ID then we're going to
have description due date is completed
created at and completed at and then um
our priority which is going to be part
of our numerations up here and then
simply we're just going to have this
string which returns all the data inside
we now want to do something very similar
for our users where we're going to have
a new table of user which is going to be
called and this table is going to be
called users our ID is also going to be
a uu ID and we're going to have an email
first name last name and password now
the only difference is we can see that
it's the to-dos have a foreign key back
to our users but our users don't have a
foreign key to our to-dos and that's
because it's a one to manyi relationship
there's only one user who can have
multiple to-dos but these todos can't
have multiple users right so we're going
to have one user that can hold multiple
todos now the next thing we're going to
add is this database and I kind of want
to put this in its own folder which is
going to be called database now inside
our database we're going to create a new
file of uncore uncore and
nitor
dopy and inside here we're going to
create a new python class called core
dopy now this is going to have quite a
bit of information inside so we're going
to have everything we need we're going
to load from ourv if you want to stick
this URL in a EnV file or we could use
sqlite or we could just hardcode
postgress right here we're going to have
our create engine session local base and
our git DB which creates a database
session and then we're going to have
this DB session which equals our
dependency injection DB session that we
can pass into functions so just for um
for practice let's go ahead and create a
new file called EnV now EnV files are
often a way for you not to have to pass
in your private secret information into
a source code repository so what we
could do is instead of passing this
directly into code we can create a new
thing called database
URL which is going to be equal to
something and we can just grab this
information paste it right
here and now I'm going to comment out
this as
well and I'm going to un comment here so
what's happening right now is we are
grabbing the database URL from a EnV
file and we're making sure that's set as
our database URL so when we create our
engine now let's go ahead and create a
new package right here called
off and inside our off we're going to
create a new file of uncore
anitore
dopy and now is when we're really going
to start seeing this clean architecture
design where we're going to have a
controller which consumes an endpoint a
service which does the um business logic
and then the models which is the pantic
data validation schemas so inside here
let's go ahead and create a new file of
controller.
Pi a new file of service.
piy and a new file of
model.py we're going to go backwards
from the model to the service to the
controller just so we can see how it's
created so what are the models that
we're going to need well we're going to
need a uuid and pantic as Imports but
we're going to really just have a couple
different ones for the authentication
which is to to register your user and to
be able to sign in a user so to register
you a new user we need a a email a first
name a last name and a password and then
we're going to do whatever we need to do
with that to save it to a database we
need a class of token which just Returns
the token back to the user for um
authentication which returns an access
token and a token type which is going to
be like JWT bear token and then we want
a class token data which is what are we
going to give back to the user once we
validate a token and this standpoint
we're going to get their uu ID so then
we can fetch whatever information we
need for that user based on their
primary key let's then jump into our
service doy which this is going to start
having quite a bit of data here where we
are going to have a secret key and a
secret algorithm again we can move that
into a EnV file just like we did with
our database core and I kind of showed
you how to do that so feel free to go
ahead and do that this is definitely
something that you're going to want to
keep secret and probably not push around
in you know different um repositories
we're going to say the algorithm is
hs256 and then the access token expires
is 30 minutes so our uh token is going
to expire in 30 minutes oaf 2 Bearer
token is going to be inside our off.
token path so this is our um which we
haven't created yet in our controller
but it's going to be slof SL token and
then our bcrypt contacts we're going to
be using bcrypt as our hashing um
contact so we're going to be saving all
of our database at database passwords as
a bcrypt hash um we have a couple
functions here like verify a password
where we're checking the plain password
a user passes in with the hashed
password to make sure that they're
validated we're going to be able to
fetch hash passwords and we need to be
able to authenticate users so if a user
you know passes in their email and
password we want to be able to query by
that user fetch them verify that user if
it fails well then we're going to log a
warning for anybody to see in our actual
like logging of our application which
says failed authentication attempt for
the specific user that tried to log in
if not we're just going to return a user
and you'll see that we have this
authenticate user getting called below
we have our create access token which is
our JWT code which takes in a sub email
ID and expiration date and then we
encode that JWT with our secret key and
algorithm so to make it like valid we
can then verify a token which returns a
models of token data so if we look up
here we can see that we're importing our
models and then we have our payload our
user ID we return the token data if
everything works out or we throw a
warning and we throw an authentication
error of token verification failed same
thing for our register user we're
passing in a new database session we
request a users's register request we
validate it and do everything that we
need to do for that and if not we throw
a registered user and we log it we can
get our user this is what we're going to
be using for um our dependency injection
for validating current users when we're
trying to deal with to-dos we have this
as an annotation now to fetch the
current user which is going to get our
current current user and then we have
our login for Access token which just uh
takes in a username and password and we
just hey does it check out we call
different functions or we throw an error
and then what we want to do here is add
in our controller our router for our API
routing which we're saying you can only
call register user five times an hour so
we're like hey you just can't sit here
and just keep creating new accounts over
and over again we're going to you know
slow it down now this request looks like
it's not being used but it's required
for our rate limiter to fetch the IP
address and then we also have a login
function which Returns the token now
this is from rate limiting limiter we
need to make sure this is from rate
limiting and perfect that's how we're
going to implement our off we're then
going to create a new folder called
users inside users we want the same
exact stuff so we're going to saycore
uncore
init.py
we also want to just say um controller.
py
service. and
model.py let's start with our model.py
where we're going to have a user
response which is going to have an ID
email first name and last name and a
password change of our current password
new password and new password
confirmation so a user once they log in
so once we hit the off endpoint we log
in a user we're going to return a jwbt
token and then when a user tries to log
in we'll grab the user token we so when
a user logs in they are able to fetch
information abouts well they can't fetch
all the information we save in the
database like their password cuz why
would we just give that out to people
but what we will give them is their ID
their email their first name and last
name and then we're also going to allow
users to be able to change their
password so you can pass in your current
password what you want your new password
to be and then kind of the confirmation
for the new password to make sure the
passwords match and these are going to
be the base models for the pantic data
validation that we're going to be using
here inside our service um we're going
to say get user by ID where we pass in
our database session and then here we
can just kind of fetch our user and
we're going to say get user by ID we're
going to fetch that user and then if
everything works out we're going to
return our user as a user response so a
user can say hey I want to fetch my
information by my ID after I validate
that I'm a the actual user and then
we'll have change password which takes
in the database session the user ID and
our password change mod model that we
just created in our model where we can
fetch the user by their ID we can verify
um their passwords or the current
password so the password that they typed
in is that really their password do the
new passwords match so the new password
and the new password confirm and then we
can finally update the password at the
end and then lastly in our controllers
we're going to have it prefixed as/
users where we're going to have our
current user as our current user and
we're going to get our user by the ID in
the the same thing right here perfect
and now let's go ahead and check out our
to-dos so we can create a new folder
called
to-dos where again we're going to do the
exact same thing of uncore uncore in nit
uncore uncore dopy then we want to
create a new file called controller. py
a new file called service dopy and a new
file called
model.py now inside here we can just
paste some information we can say we
want our to-do base which is going to be
like if you want to create a new to-do
description due date priority create is
going to use exact same thing and then
our to-do response is just going to have
our ID is complete completed at and then
we're going to say hey we want this
model configuration to be um config
dictionary so we can consume and use the
data a little bit easier let's then jump
into our service where we can say hey we
want this to um be create Todo with our
current user token data d session to-do
model. create where we've done this a
ton of different times where we're just
passing in data and we're creating a new
to-do log it if necessary same thing for
our to-do get to-dos so based on an
authenticated user fetch all the to-dos
based on The UU
ID get to-do by ID so we can just pass
in The UU ID after we verify the user
fetch the specific to-do by its ID
return it back to the user update a new
to-do which allows us to be able to just
up update to-dos and then we have
complete to-do which makes the is
completed value the opposite so true or
false and then we save it to our
database and then we have our delete
to-do which allows us to delete a to-do
and then lastly we have our controller
which allows us to be able to call each
of those endpoints so like create a new
to-do get to-dos get to-do update to-do
complete to-do and delete to-do all
based on validating a specific user now
one thing that we are and this is all
model let me just go through this real
quick and now one more thing that we
want to implement is exception handling
so we can see like in our service we're
throwing some exception
handlers and let's just go into here
create a new file called
exceptions. piy and here we just want to
add some exceptions so our to-do error
um to-do not found to-do creation error
userbase error and we're just doing a
super call back up to the initialization
of the error that we're calling
beforehand and this just allow us to be
able to see a little things a little bit
cleaner be able to throw different
status codes and be able to kind of just
be able to scale our application a
little easier now the last thing I'm
going to add here inside our source I'm
going to say new file I'm going to name
this api. py where we're going to add
now all of our routers right here and
then in our main.py we already have
Register App register routes we can go
over here and just say hey we want to
register our routes of our app and then
we also want to make sure that we add
our configuration for us to be able to
throw some logs and then add them right
here so with everything that we just
added if we jump into our terminal and
we say uicorn
source. main colon app-- reload
we're getting an error inside our source
do off oops cannot import models oops
where am I saying models that should be
model that is my
bad and we also doing it in here yeah
that should be
model see even when you think you have
everything planned coding can throw some
errors at you oh goodness I did it in
all of them all right let's go back into
users uh should be
model model
model model all
right now everything should be good all
right so now if we open up our
application we go to our Port there it
is here's our whole application
everything looks great and if you try
and get something it's going to throw a
not authenticated because you have to
register an account and then grab the
token stick the token up here to be able
to to handle all the endpoints but now
let's go over how we can test this
application so there's two ways we can
really test it we can test it with unit
testing and we can test it with in to
end testing and how we can do that is
let's jump into our source let's go
ahead and say new folder oops let's jump
outside of our Force um our source and
let's create a new folder called tests
and now inside our tests we want to
create a newcore uncore and
nitor do Pi file and the first thing
we're going to create in here is
something called a com test file so com
test.py this is more of like all the
stuff to get our application ready to be
tested so here we know this is actually
that and this is limiting but what we're
doing here is we're just setting up a
fake SQL light test or SQL light test
database for our application we're um
doing everything we need so we're
setting up all of our entities for our
test database and then we're just
setting up our High test fixtures which
are more or less ways to set up data for
our tests to be called and we're doing
that all over in here first thing we're
going to do is our off test service so
we can come in here and we can say test
off
service. we just add in all of this
information where we're going to be able
to test against our verify password
authenticate user test login for Access
token be able to create a user or
testing a against everything inside our
service we can come over here and create
another file called
testore
users service. py where we're just
adding everything inside of here where
we're now going to be testing our um G
user information and then be able to
change a password and we're testing
really every way someone could try and
change their password and making sure
that all of our configurations are
correct and then the last thing is our
test to so our testore todos um service.
py we want to make sure all these are
good so here there's a whole bunch of
different tests about creating a to-do
getting to-dos get todos by ID
completing to-dos deleting to-dos
everything we need there and really what
we need to do here is we can just say
pie
test if you run pie test we can see our
um tests start running and everything
looks good we have all of our things
passed we can ignore this right now it's
saying something is going to be
potentially removed in the future but it
doesn't matter because a lot of times
these deprecation warnings last way past
the version date or sometimes they don't
even ever get fixed so that's okay we
can see that all of our all of our tests
pass and we have our test DB but now we
want to do in to end testing so what
that means is we are starting at the end
point and we're testing everything down
and everything back from the endpoint so
inside our test let's create a new file
called e2e which stands for end to end
and I'm going to say new file of
testore off uncore npoints
dop this is now going to test all of our
endpoints literally calling the
endpoints we can say we're calling the
off SL token we're calling the off SL
token again we are doing our endpoint
testing all the way down to our service
and back instead of just our
services we're going to do the same
thing here for testore
users uncore endpoints dopy and this is
going to test everything about a
specific user based on the endpoint all
the way down we're testing hey are we
getting the the right status code are we
getting the right responses we're just
making sure that everything looks really
good and now lastly we want to say
testore todos uncore um
endpoints we're again we are just
testing everything from a to-do
perspective based on the endpoint so
awesome stuff let's go ahead and just do
pie test again and now we should be
testing all of our endpoints as well and
this percent over here is just like it's
incrementing up to 100% where 100% is
done that's not like code coverage so
now we have all of our tests passing
cool so now for remember we added our
postgress right here and we want to make
sure that that is like the new
information so I'm just going to say uh
source. EnV from my standpoint that sets
all the environmental variables for um
my my local environment but what we want
to do now is let's deploy all of this on
Docker so if you don't already have it
um go ahead and download Docker from
your application and here you can see
that I don't have very much installed on
my Docker at the moment and what we're
going to do here is make sure that we
can run our application from Docker and
then also postgress will be created in
Docker so we're going to deploy our
application on Docker and we're going to
deploy our postgress on Docker so the
very first thing we need to do is is
create a new file inside our main thing
called a Docker file and inside here
we're saying use Python 3.11 from our
work directory of app we're going to
install our requirements. text we want
to copy the project files of our source
source and then expose on Port 8000
using our Command that we use to run our
application and now we need a
Docker a Docker
compose yaml file
which is going to do everything else for
us so right here we can see that it's
going to spin up a postgress environment
which is going to be called clean fast
API on our port and we already set this
information up in our Docker in our um
database core file and then we're going
to deploy this postgress environment so
we're going to deploy our postgress
environment and we're going to run our
fast API
application and we can do that by saying
Docker compose up-- build when you do
this it's going to pull the image from
the latest postgress if you don't
already have it it's going to spin up
that postgress environment and then it's
going to deploy our application our
application is going to be communicating
with that postgress environment so it
says application startup was complete if
we come back over here and we refresh
this application is literally running
from a postgress database now in our
Docker environment if we open up Docker
we can see right here we have our
postgress running and our uh product
running if we like
but it's all in the same container so if
we just wanted to stop it we can just
stop that
container and our application crashes
just like expected because we turned it
off in Docker so awesome awesome stuff
guys this is I know this was a lot um
the the code is you can download the
code directly into descriptions I just
kind of wanted to walk through what
exactly is happening in case you wanted
to you know dive more into it so you can
scale and build from here this is
already a very professional application
which is using Docker tests in to end
tests unit tests structured in a way to
really scale our application so use this
how you like I hope you enjoyed it and I
will see you in the next video
