Stop Building FastAPI Apps Wrong!
45sOpens with a controversial statement that challenges developers' current practices, making them want to learn the 'right' way.
▶ Play ClipThis 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.
The video introduces a clean architecture for FastAPI with domain (entities), application (business logic), and infrastructure (database, rate limiting) layers, plus testing.
Starts with a source directory, main.py, requirements files, and installs dependencies for development and production.
Creates a logging.py file to set up log levels (info, warning, error, debug) for monitoring and debugging.
Implements rate limiting using slowapi to protect endpoints from abuse, based on IP address.
Creates SQLAlchemy models for users and todos with UUID primary keys, foreign keys, and relationships.
Sets up database core with SQLAlchemy engine, session, and dependency injection; uses .env file for database URL.
Builds auth module with controller, service, and models for registration, login, token creation/verification, and password hashing.
Creates separate modules for users (get user, change password) and todos (CRUD operations) with services and controllers.
Adds custom exception classes for cleaner error handling and status codes.
Registers all routers in api.py and configures logging in main.py; runs uvicorn with reload.
Creates test suite with unit tests for services and end-to-end tests for endpoints using pytest and a test SQLite database.
Creates Dockerfile and docker-compose.yml to deploy FastAPI app with PostgreSQL; runs with docker compose up --build.
What are the three layers of clean architecture used in this FastAPI project?
Domain layer (entities), application layer (business logic), infrastructure layer (database, rate limiting).
Which library is used for rate limiting in the video?
slowapi
4:21
What type of primary key is used for the user and todo entities?
UUID (uuid4)
6:35
How is the database URL configured to avoid hardcoding?
It is read from a .env file using os.getenv('DATABASE_URL').
8:56
What hashing algorithm is used for passwords?
bcrypt
11:44
What is the default token expiration time for JWT tokens?
30 minutes
11:30
How many times per hour can a user call the register endpoint according to the rate limiter?
5 times per hour
13:47
What command is used to run the application with uvicorn?
uvicorn source.main:app --reload
19:51
What type of database is used for testing?
SQLite (in-memory test database)
21:49
What command runs all tests?
pytest
23:18
What Docker command builds and starts the application with PostgreSQL?
docker compose up --build
26:53
Clean Architecture Layers
Defines the core architectural pattern used throughout the project: domain, application, and infrastructure layers.
Rate Limiting for Security
Demonstrates practical protection against DDoS and brute-force attacks using slowapi.
4:21Full Authentication Flow
Covers registration, login, JWT token generation/verification, and bcrypt password hashing in a clean service-controller pattern.
9:30Comprehensive Testing Strategy
Shows both unit tests (services) and end-to-end tests (endpoints) using pytest and a test SQLite database.
21:00Docker Deployment with PostgreSQL
Illustrates containerizing the application and database for production-ready deployment.
25:16[00:00] knowing how to architect your
[00:01] application is a big deal when it comes
[00:04] to building a fast API product we don't
[00:06] just want to build a backend application
[00:09] we want to build something that is clean
[00:11] and easy to scale so in this video we'll
[00:14] be creating a fast API application that
[00:16] is following all of the best practices
[00:19] which includes the domain layer like
[00:21] where the enti development lives the
[00:23] application layer where all the business
[00:25] logic of the application lives the
[00:27] infrastructure layer which is where the
[00:29] database and rate limiting lives and
[00:31] then we're also going to include all of
[00:33] the unit testing and end to end testing
[00:36] this video will cover everything you
[00:37] need to get started with a clean
[00:39] architecture for your next product if
[00:41] you're new to the channel I'm Eric Roby
[00:43] a software engineer with over a decade
[00:45] of experience and I've helped over
[00:47] 100,000 developers learn and grow within
[00:50] their craft all right so with that let's
[00:52] dive into some code all right so I
[00:54] already have a environment created so I
[00:57] have a directory that's already created
[01:00] called clean architecture which is
[01:01] hosting our application inside our
[01:03] directory I just have a source directory
[01:06] with a empty main.py file and then I
[01:09] have a requirements. text file which has
[01:12] all of our dependencies that we need to
[01:15] run our application in a production
[01:18] environment so not necessarily what we
[01:19] need to run on our local machine but
[01:22] what we need for a production
[01:24] environment now in my requirements dd.
[01:27] text this is what we need for for our
[01:30] development environment which is like
[01:32] you right now on your personal machine
[01:33] if you want to code so what we can do
[01:35] here is PIP install - R requirements
[01:38] ddev
[01:43] dotex this will install all the
[01:45] dependencies that we need for our entire
[01:48] project so now what we can do is let's
[01:50] go ahead and jump into our main.py file
[01:53] the first thing that we want to do is
[01:54] just add all of the Imports that we're
[01:56] going to be creating in the future and
[01:58] that we need for this main.py file which
[02:01] is just our database our entities our
[02:03] API and our fast API and then what we
[02:05] want to add here is simply our app which
[02:08] is equal to fast API and then we're
[02:11] going to have this base. metadata.
[02:13] create all bind equals engine this we
[02:16] only want to uncomment when we're
[02:19] creating new tables when we run our
[02:21] application for the first time this will
[02:23] break our tests later on so I leave it
[02:25] uncommented but it is a good thing to
[02:27] have just in case you want to rapidly be
[02:29] able to create new entities perfect now
[02:32] we're going to get a bunch of errors and
[02:33] that's okay cuz we're going to be
[02:34] building everything step by step now the
[02:37] next thing I'm going to just Implement
[02:38] just so we have all of our
[02:40] configurations down pth is something
[02:41] called logging so I'm going to create
[02:43] logging piy and inside logging it's
[02:46] going to be a way for us to be able to
[02:47] document kind of all of our endpoints so
[02:49] if something happens in production or
[02:51] happens on our environment where we
[02:52] don't have to necessarily jump in and
[02:54] start debugging everything with code we
[02:55] can go ahead and just kind of look at
[02:57] the logs that our application is
[02:59] creating and and just be able to
[03:00] identify hey that's where the error is
[03:02] just by looking at the terminal so in
[03:05] here I'm going to add this logging which
[03:07] we're going to import logging we're
[03:09] going to import an En from our string
[03:12] and then we're going to just have this
[03:13] log format debug which is showing us how
[03:16] our log is going to format um debugs
[03:19] then we're going to have our log levels
[03:20] which is going to be info worn air and
[03:23] debug and what this does is when we add
[03:26] these into our code it'll tell us like
[03:28] hey this is just information like maybe
[03:30] this is just something that you might
[03:31] want to know we might be like hey this
[03:33] is a warning in our code if this happens
[03:36] this is specifically going to cause um
[03:39] Downstream effects then there's like air
[03:41] like our application's broken and then
[03:43] we can have debug errors for like in the
[03:45] future so if you have like an object
[03:46] that's getting weird we can just throw
[03:48] in a debug so then when we're running
[03:49] our application it'll be like hey this
[03:50] is um the object debugged now what our
[03:53] configure logging does is it takes in
[03:55] one of these log levels and then we kind
[03:57] of just do different things so it takes
[03:59] it it capitalized which it already is
[04:01] capitalized we find what level it is in
[04:04] our log levels and then we're just kind
[04:05] of setting up different permissions
[04:07] based on the log level and then we just
[04:09] enable our basic configuration to be
[04:11] logging and if you've never logged
[04:12] before we're going to be implementing
[04:13] this in our application so you'll be
[04:15] able to see exactly what's happening but
[04:17] overall this is what it's going to look
[04:18] like when you want to set it up from
[04:20] scratch the next thing we're going to be
[04:21] setting up is our rate limiting so
[04:23] inside our source directory let's go
[04:25] ahead and create a new file called rate
[04:27] limiting and inside here we want to
[04:30] implement our slow API which was what we
[04:32] installed now this will allow us to be
[04:34] able to do rate limiting on an endpoint
[04:37] so what that means is we are saying hey
[04:39] maybe our register we only allow a
[04:42] certain user to be able to hit our
[04:43] register three times before maybe we're
[04:46] saying you need to take a break and this
[04:48] allows us to protect from like DDS
[04:50] attacks or some other kind of attacks
[04:52] where people are trying to spam or use
[04:54] Bots to call our endpoints over and over
[04:55] and over again just to kind of collect
[04:57] data we're putting rate limiting on in
[04:59] this git remote address means it's going
[05:00] to collect the IP address and we're
[05:02] going to say hey based on your IP
[05:03] address we are not going to allow you to
[05:05] call certain endpoints you know X number
[05:08] of times because it could be security
[05:10] maybe the the endpoints cost money it's
[05:12] something just to be like whoa slow down
[05:14] you're calling way too many in points
[05:16] way too um too fast so that's what we're
[05:18] implementing right here and then we're
[05:19] going to be adding it in our endpoints
[05:21] here in a little bit and now what we
[05:22] want to do is make sure we make this a
[05:24] python package by saying underscore
[05:27] uncore and knit underscore uncore .p
[05:30] that means we can now call these very
[05:32] easily within our project and now let's
[05:34] go ahead and create our first package
[05:37] now our first package is going to be
[05:38] called entities now let's go ahead and
[05:40] just say entities just like we did
[05:43] before inside entities we need to say
[05:45] new file
[05:46] uncore and
[05:49] nitpy that makes entities a python
[05:53] package and now in this project we're
[05:54] going to have two entities one for our
[05:57] to-dos and one for our users so we can
[06:00] go ahead and create um two more files in
[06:02] here to-do and
[06:06] users now first let's go into our to.py
[06:09] and let's add some code right here so
[06:12] what we're saying is we are simply going
[06:14] to be adding our SQL Alchemy we're going
[06:16] to be using uu ID for our um primary key
[06:21] and then we're going to be using date
[06:22] time and some other things in here so
[06:23] here we're just saying what the priority
[06:26] of this to-do is which is going to be 0
[06:28] to 4 we're going to create this now
[06:30] to-do class and we're going to name this
[06:32] table todos plural we have an ID of
[06:35] column uid as uu ID is true it's going
[06:39] to be our primary key and the default is
[06:41] to is to use The UU ID for we're going
[06:44] to have a user ID which is going to be a
[06:46] foreign key back to the users which we
[06:49] haven't created yet and that's also
[06:50] going to be a uu ID then we're going to
[06:52] have description due date is completed
[06:54] created at and completed at and then um
[06:57] our priority which is going to be part
[06:58] of our numerations up here and then
[07:01] simply we're just going to have this
[07:02] string which returns all the data inside
[07:05] we now want to do something very similar
[07:07] for our users where we're going to have
[07:10] a new table of user which is going to be
[07:12] called and this table is going to be
[07:13] called users our ID is also going to be
[07:16] a uu ID and we're going to have an email
[07:19] first name last name and password now
[07:22] the only difference is we can see that
[07:23] it's the to-dos have a foreign key back
[07:26] to our users but our users don't have a
[07:28] foreign key to our to-dos and that's
[07:30] because it's a one to manyi relationship
[07:32] there's only one user who can have
[07:34] multiple to-dos but these todos can't
[07:36] have multiple users right so we're going
[07:38] to have one user that can hold multiple
[07:40] todos now the next thing we're going to
[07:42] add is this database and I kind of want
[07:44] to put this in its own folder which is
[07:46] going to be called database now inside
[07:49] our database we're going to create a new
[07:51] file of uncore uncore and
[07:53] nitor
[07:55] dopy and inside here we're going to
[07:58] create a new python class called core
[08:01] dopy now this is going to have quite a
[08:04] bit of information inside so we're going
[08:06] to have everything we need we're going
[08:07] to load from ourv if you want to stick
[08:11] this URL in a EnV file or we could use
[08:15] sqlite or we could just hardcode
[08:17] postgress right here we're going to have
[08:19] our create engine session local base and
[08:22] our git DB which creates a database
[08:25] session and then we're going to have
[08:26] this DB session which equals our
[08:28] dependency injection DB session that we
[08:30] can pass into functions so just for um
[08:34] for practice let's go ahead and create a
[08:36] new file called EnV now EnV files are
[08:41] often a way for you not to have to pass
[08:44] in your private secret information into
[08:48] a source code repository so what we
[08:50] could do is instead of passing this
[08:53] directly into code we can create a new
[08:56] thing called database
[08:58] URL which is going to be equal to
[09:01] something and we can just grab this
[09:05] information paste it right
[09:08] here and now I'm going to comment out
[09:11] this as
[09:13] well and I'm going to un comment here so
[09:17] what's happening right now is we are
[09:19] grabbing the database URL from a EnV
[09:23] file and we're making sure that's set as
[09:25] our database URL so when we create our
[09:28] engine now let's go ahead and create a
[09:30] new package right here called
[09:34] off and inside our off we're going to
[09:37] create a new file of uncore
[09:40] anitore
[09:42] dopy and now is when we're really going
[09:45] to start seeing this clean architecture
[09:47] design where we're going to have a
[09:49] controller which consumes an endpoint a
[09:50] service which does the um business logic
[09:53] and then the models which is the pantic
[09:54] data validation schemas so inside here
[09:57] let's go ahead and create a new file of
[10:00] controller.
[10:02] Pi a new file of service.
[10:08] piy and a new file of
[10:13] model.py we're going to go backwards
[10:15] from the model to the service to the
[10:17] controller just so we can see how it's
[10:19] created so what are the models that
[10:21] we're going to need well we're going to
[10:22] need a uuid and pantic as Imports but
[10:25] we're going to really just have a couple
[10:27] different ones for the authentication
[10:28] which is to to register your user and to
[10:30] be able to sign in a user so to register
[10:32] you a new user we need a a email a first
[10:35] name a last name and a password and then
[10:37] we're going to do whatever we need to do
[10:38] with that to save it to a database we
[10:40] need a class of token which just Returns
[10:43] the token back to the user for um
[10:45] authentication which returns an access
[10:47] token and a token type which is going to
[10:49] be like JWT bear token and then we want
[10:51] a class token data which is what are we
[10:54] going to give back to the user once we
[10:55] validate a token and this standpoint
[10:56] we're going to get their uu ID so then
[10:58] we can fetch whatever information we
[10:59] need for that user based on their
[11:01] primary key let's then jump into our
[11:03] service doy which this is going to start
[11:06] having quite a bit of data here where we
[11:08] are going to have a secret key and a
[11:10] secret algorithm again we can move that
[11:13] into a EnV file just like we did with
[11:16] our database core and I kind of showed
[11:17] you how to do that so feel free to go
[11:19] ahead and do that this is definitely
[11:21] something that you're going to want to
[11:22] keep secret and probably not push around
[11:23] in you know different um repositories
[11:26] we're going to say the algorithm is
[11:27] hs256 and then the access token expires
[11:30] is 30 minutes so our uh token is going
[11:33] to expire in 30 minutes oaf 2 Bearer
[11:36] token is going to be inside our off.
[11:38] token path so this is our um which we
[11:40] haven't created yet in our controller
[11:41] but it's going to be slof SL token and
[11:44] then our bcrypt contacts we're going to
[11:45] be using bcrypt as our hashing um
[11:48] contact so we're going to be saving all
[11:49] of our database at database passwords as
[11:51] a bcrypt hash um we have a couple
[11:54] functions here like verify a password
[11:55] where we're checking the plain password
[11:57] a user passes in with the hashed
[11:59] password to make sure that they're
[12:01] validated we're going to be able to
[12:02] fetch hash passwords and we need to be
[12:05] able to authenticate users so if a user
[12:08] you know passes in their email and
[12:10] password we want to be able to query by
[12:12] that user fetch them verify that user if
[12:15] it fails well then we're going to log a
[12:17] warning for anybody to see in our actual
[12:21] like logging of our application which
[12:23] says failed authentication attempt for
[12:25] the specific user that tried to log in
[12:27] if not we're just going to return a user
[12:28] and you'll see that we have this
[12:30] authenticate user getting called below
[12:33] we have our create access token which is
[12:34] our JWT code which takes in a sub email
[12:37] ID and expiration date and then we
[12:39] encode that JWT with our secret key and
[12:42] algorithm so to make it like valid we
[12:44] can then verify a token which returns a
[12:46] models of token data so if we look up
[12:49] here we can see that we're importing our
[12:52] models and then we have our payload our
[12:54] user ID we return the token data if
[12:56] everything works out or we throw a
[12:58] warning and we throw an authentication
[13:00] error of token verification failed same
[13:02] thing for our register user we're
[13:04] passing in a new database session we
[13:06] request a users's register request we
[13:09] validate it and do everything that we
[13:10] need to do for that and if not we throw
[13:13] a registered user and we log it we can
[13:16] get our user this is what we're going to
[13:18] be using for um our dependency injection
[13:21] for validating current users when we're
[13:23] trying to deal with to-dos we have this
[13:25] as an annotation now to fetch the
[13:27] current user which is going to get our
[13:28] current current user and then we have
[13:30] our login for Access token which just uh
[13:32] takes in a username and password and we
[13:34] just hey does it check out we call
[13:36] different functions or we throw an error
[13:38] and then what we want to do here is add
[13:41] in our controller our router for our API
[13:45] routing which we're saying you can only
[13:47] call register user five times an hour so
[13:51] we're like hey you just can't sit here
[13:52] and just keep creating new accounts over
[13:54] and over again we're going to you know
[13:56] slow it down now this request looks like
[13:58] it's not being used but it's required
[14:00] for our rate limiter to fetch the IP
[14:03] address and then we also have a login
[14:05] function which Returns the token now
[14:07] this is from rate limiting limiter we
[14:09] need to make sure this is from rate
[14:13] limiting and perfect that's how we're
[14:14] going to implement our off we're then
[14:16] going to create a new folder called
[14:19] users inside users we want the same
[14:22] exact stuff so we're going to saycore
[14:24] uncore
[14:25] init.py
[14:27] we also want to just say um controller.
[14:32] py
[14:37] service. and
[14:39] model.py let's start with our model.py
[14:42] where we're going to have a user
[14:44] response which is going to have an ID
[14:45] email first name and last name and a
[14:47] password change of our current password
[14:49] new password and new password
[14:50] confirmation so a user once they log in
[14:53] so once we hit the off endpoint we log
[14:56] in a user we're going to return a jwbt
[14:58] token and then when a user tries to log
[15:00] in we'll grab the user token we so when
[15:02] a user logs in they are able to fetch
[15:04] information abouts well they can't fetch
[15:06] all the information we save in the
[15:08] database like their password cuz why
[15:09] would we just give that out to people
[15:11] but what we will give them is their ID
[15:13] their email their first name and last
[15:14] name and then we're also going to allow
[15:16] users to be able to change their
[15:17] password so you can pass in your current
[15:20] password what you want your new password
[15:21] to be and then kind of the confirmation
[15:23] for the new password to make sure the
[15:24] passwords match and these are going to
[15:26] be the base models for the pantic data
[15:28] validation that we're going to be using
[15:29] here inside our service um we're going
[15:32] to say get user by ID where we pass in
[15:35] our database session and then here we
[15:37] can just kind of fetch our user and
[15:39] we're going to say get user by ID we're
[15:41] going to fetch that user and then if
[15:43] everything works out we're going to
[15:44] return our user as a user response so a
[15:46] user can say hey I want to fetch my
[15:48] information by my ID after I validate
[15:50] that I'm a the actual user and then
[15:52] we'll have change password which takes
[15:54] in the database session the user ID and
[15:57] our password change mod model that we
[15:59] just created in our model where we can
[16:01] fetch the user by their ID we can verify
[16:04] um their passwords or the current
[16:06] password so the password that they typed
[16:07] in is that really their password do the
[16:09] new passwords match so the new password
[16:11] and the new password confirm and then we
[16:13] can finally update the password at the
[16:16] end and then lastly in our controllers
[16:19] we're going to have it prefixed as/
[16:21] users where we're going to have our
[16:23] current user as our current user and
[16:26] we're going to get our user by the ID in
[16:28] the the same thing right here perfect
[16:32] and now let's go ahead and check out our
[16:34] to-dos so we can create a new folder
[16:38] called
[16:39] to-dos where again we're going to do the
[16:41] exact same thing of uncore uncore in nit
[16:44] uncore uncore dopy then we want to
[16:46] create a new file called controller. py
[16:50] a new file called service dopy and a new
[16:55] file called
[16:57] model.py now inside here we can just
[16:59] paste some information we can say we
[17:01] want our to-do base which is going to be
[17:02] like if you want to create a new to-do
[17:04] description due date priority create is
[17:06] going to use exact same thing and then
[17:08] our to-do response is just going to have
[17:09] our ID is complete completed at and then
[17:11] we're going to say hey we want this
[17:13] model configuration to be um config
[17:15] dictionary so we can consume and use the
[17:17] data a little bit easier let's then jump
[17:19] into our service where we can say hey we
[17:23] want this to um be create Todo with our
[17:27] current user token data d session to-do
[17:29] model. create where we've done this a
[17:32] ton of different times where we're just
[17:33] passing in data and we're creating a new
[17:35] to-do log it if necessary same thing for
[17:39] our to-do get to-dos so based on an
[17:41] authenticated user fetch all the to-dos
[17:43] based on The UU
[17:45] ID get to-do by ID so we can just pass
[17:48] in The UU ID after we verify the user
[17:51] fetch the specific to-do by its ID
[17:54] return it back to the user update a new
[17:56] to-do which allows us to be able to just
[17:58] up update to-dos and then we have
[18:00] complete to-do which makes the is
[18:02] completed value the opposite so true or
[18:05] false and then we save it to our
[18:06] database and then we have our delete
[18:08] to-do which allows us to delete a to-do
[18:11] and then lastly we have our controller
[18:13] which allows us to be able to call each
[18:17] of those endpoints so like create a new
[18:19] to-do get to-dos get to-do update to-do
[18:22] complete to-do and delete to-do all
[18:24] based on validating a specific user now
[18:27] one thing that we are and this is all
[18:31] model let me just go through this real
[18:36] quick and now one more thing that we
[18:39] want to implement is exception handling
[18:41] so we can see like in our service we're
[18:43] throwing some exception
[18:45] handlers and let's just go into here
[18:47] create a new file called
[18:50] exceptions. piy and here we just want to
[18:53] add some exceptions so our to-do error
[18:56] um to-do not found to-do creation error
[18:59] userbase error and we're just doing a
[19:02] super call back up to the initialization
[19:04] of the error that we're calling
[19:06] beforehand and this just allow us to be
[19:08] able to see a little things a little bit
[19:09] cleaner be able to throw different
[19:10] status codes and be able to kind of just
[19:12] be able to scale our application a
[19:14] little easier now the last thing I'm
[19:16] going to add here inside our source I'm
[19:20] going to say new file I'm going to name
[19:22] this api. py where we're going to add
[19:25] now all of our routers right here and
[19:29] then in our main.py we already have
[19:31] Register App register routes we can go
[19:33] over here and just say hey we want to
[19:35] register our routes of our app and then
[19:38] we also want to make sure that we add
[19:41] our configuration for us to be able to
[19:43] throw some logs and then add them right
[19:46] here so with everything that we just
[19:49] added if we jump into our terminal and
[19:51] we say uicorn
[19:53] source. main colon app-- reload
[20:00] we're getting an error inside our source
[20:02] do off oops cannot import models oops
[20:06] where am I saying models that should be
[20:10] model that is my
[20:13] bad and we also doing it in here yeah
[20:16] that should be
[20:20] model see even when you think you have
[20:24] everything planned coding can throw some
[20:26] errors at you oh goodness I did it in
[20:28] all of them all right let's go back into
[20:30] users uh should be
[20:35] model model
[20:38] model model all
[20:40] right now everything should be good all
[20:42] right so now if we open up our
[20:44] application we go to our Port there it
[20:47] is here's our whole application
[20:49] everything looks great and if you try
[20:50] and get something it's going to throw a
[20:52] not authenticated because you have to
[20:54] register an account and then grab the
[20:56] token stick the token up here to be able
[20:58] to to handle all the endpoints but now
[21:00] let's go over how we can test this
[21:02] application so there's two ways we can
[21:03] really test it we can test it with unit
[21:05] testing and we can test it with in to
[21:07] end testing and how we can do that is
[21:10] let's jump into our source let's go
[21:13] ahead and say new folder oops let's jump
[21:16] outside of our Force um our source and
[21:18] let's create a new folder called tests
[21:22] and now inside our tests we want to
[21:24] create a newcore uncore and
[21:27] nitor do Pi file and the first thing
[21:30] we're going to create in here is
[21:31] something called a com test file so com
[21:34] test.py this is more of like all the
[21:37] stuff to get our application ready to be
[21:39] tested so here we know this is actually
[21:42] that and this is limiting but what we're
[21:45] doing here is we're just setting up a
[21:47] fake SQL light test or SQL light test
[21:50] database for our application we're um
[21:53] doing everything we need so we're
[21:54] setting up all of our entities for our
[21:56] test database and then we're just
[21:57] setting up our High test fixtures which
[21:59] are more or less ways to set up data for
[22:02] our tests to be called and we're doing
[22:05] that all over in here first thing we're
[22:07] going to do is our off test service so
[22:09] we can come in here and we can say test
[22:12] off
[22:14] service. we just add in all of this
[22:18] information where we're going to be able
[22:19] to test against our verify password
[22:22] authenticate user test login for Access
[22:25] token be able to create a user or
[22:27] testing a against everything inside our
[22:30] service we can come over here and create
[22:32] another file called
[22:34] testore
[22:36] users service. py where we're just
[22:40] adding everything inside of here where
[22:42] we're now going to be testing our um G
[22:45] user information and then be able to
[22:47] change a password and we're testing
[22:49] really every way someone could try and
[22:51] change their password and making sure
[22:53] that all of our configurations are
[22:55] correct and then the last thing is our
[22:57] test to so our testore todos um service.
[23:03] py we want to make sure all these are
[23:05] good so here there's a whole bunch of
[23:07] different tests about creating a to-do
[23:09] getting to-dos get todos by ID
[23:12] completing to-dos deleting to-dos
[23:14] everything we need there and really what
[23:16] we need to do here is we can just say
[23:18] pie
[23:19] test if you run pie test we can see our
[23:22] um tests start running and everything
[23:25] looks good we have all of our things
[23:27] passed we can ignore this right now it's
[23:29] saying something is going to be
[23:30] potentially removed in the future but it
[23:32] doesn't matter because a lot of times
[23:34] these deprecation warnings last way past
[23:36] the version date or sometimes they don't
[23:38] even ever get fixed so that's okay we
[23:40] can see that all of our all of our tests
[23:42] pass and we have our test DB but now we
[23:45] want to do in to end testing so what
[23:47] that means is we are starting at the end
[23:49] point and we're testing everything down
[23:51] and everything back from the endpoint so
[23:53] inside our test let's create a new file
[23:55] called e2e which stands for end to end
[23:58] and I'm going to say new file of
[24:02] testore off uncore npoints
[24:08] dop this is now going to test all of our
[24:11] endpoints literally calling the
[24:12] endpoints we can say we're calling the
[24:14] off SL token we're calling the off SL
[24:16] token again we are doing our endpoint
[24:19] testing all the way down to our service
[24:21] and back instead of just our
[24:23] services we're going to do the same
[24:25] thing here for testore
[24:28] users uncore endpoints dopy and this is
[24:33] going to test everything about a
[24:35] specific user based on the endpoint all
[24:37] the way down we're testing hey are we
[24:39] getting the the right status code are we
[24:40] getting the right responses we're just
[24:42] making sure that everything looks really
[24:44] good and now lastly we want to say
[24:47] testore todos uncore um
[24:51] endpoints we're again we are just
[24:53] testing everything from a to-do
[24:55] perspective based on the endpoint so
[24:58] awesome stuff let's go ahead and just do
[25:00] pie test again and now we should be
[25:02] testing all of our endpoints as well and
[25:04] this percent over here is just like it's
[25:06] incrementing up to 100% where 100% is
[25:09] done that's not like code coverage so
[25:11] now we have all of our tests passing
[25:13] cool so now for remember we added our
[25:16] postgress right here and we want to make
[25:19] sure that that is like the new
[25:21] information so I'm just going to say uh
[25:22] source. EnV from my standpoint that sets
[25:26] all the environmental variables for um
[25:28] my my local environment but what we want
[25:29] to do now is let's deploy all of this on
[25:32] Docker so if you don't already have it
[25:35] um go ahead and download Docker from
[25:38] your application and here you can see
[25:40] that I don't have very much installed on
[25:42] my Docker at the moment and what we're
[25:44] going to do here is make sure that we
[25:46] can run our application from Docker and
[25:49] then also postgress will be created in
[25:52] Docker so we're going to deploy our
[25:53] application on Docker and we're going to
[25:54] deploy our postgress on Docker so the
[25:57] very first thing we need to do is is
[25:58] create a new file inside our main thing
[26:00] called a Docker file and inside here
[26:03] we're saying use Python 3.11 from our
[26:05] work directory of app we're going to
[26:08] install our requirements. text we want
[26:10] to copy the project files of our source
[26:12] source and then expose on Port 8000
[26:15] using our Command that we use to run our
[26:16] application and now we need a
[26:20] Docker a Docker
[26:25] compose yaml file
[26:28] which is going to do everything else for
[26:30] us so right here we can see that it's
[26:31] going to spin up a postgress environment
[26:34] which is going to be called clean fast
[26:37] API on our port and we already set this
[26:39] information up in our Docker in our um
[26:41] database core file and then we're going
[26:43] to deploy this postgress environment so
[26:46] we're going to deploy our postgress
[26:48] environment and we're going to run our
[26:49] fast API
[26:51] application and we can do that by saying
[26:53] Docker compose up-- build when you do
[26:57] this it's going to pull the image from
[26:59] the latest postgress if you don't
[27:01] already have it it's going to spin up
[27:04] that postgress environment and then it's
[27:05] going to deploy our application our
[27:07] application is going to be communicating
[27:09] with that postgress environment so it
[27:10] says application startup was complete if
[27:12] we come back over here and we refresh
[27:16] this application is literally running
[27:18] from a postgress database now in our
[27:20] Docker environment if we open up Docker
[27:22] we can see right here we have our
[27:24] postgress running and our uh product
[27:26] running if we like
[27:29] but it's all in the same container so if
[27:30] we just wanted to stop it we can just
[27:31] stop that
[27:32] container and our application crashes
[27:34] just like expected because we turned it
[27:36] off in Docker so awesome awesome stuff
[27:38] guys this is I know this was a lot um
[27:41] the the code is you can download the
[27:43] code directly into descriptions I just
[27:45] kind of wanted to walk through what
[27:47] exactly is happening in case you wanted
[27:49] to you know dive more into it so you can
[27:51] scale and build from here this is
[27:54] already a very professional application
[27:56] which is using Docker tests in to end
[27:58] tests unit tests structured in a way to
[28:01] really scale our application so use this
[28:03] how you like I hope you enjoyed it and I
[28:05] will see you in the next video
⚡ Saved you time reading this? Transcribe any YouTube video for free — no signup needed.