Getting Started
We’re now ready to start writing code and build a FastAPI application.
First Application
First, create a file called main.py
in the root of your project directory. This is the most basic example, a basic health check:
import uvicorn
from fastapi import FastAPI, status, Response
main_app = FastAPI()
@main_app.get('/health', status_code=status.HTTP_200_OK)
def health_check_api(response: Response):
response.headers["X-Health-Check"] = "ok"
return {
'health': "ok"
}
if __name__ == '__main__':
uvicorn.run(main_app, host='0.0.0.0', port=8000)
Running through this project several times, it became apparent to name things
explicitly to avoid confusion and conflicts in the future. Here, I'm naming API functions with
a tailing _api
, for example health_check_api
.
The example includes the three imports from fastapi
and the uvicorn
server (a
lightning-fast ASGI server implementation), a server to run the application.
Then it creates an instance of a FastAPI object called main_app
and defines a
function decorated with the HTTP verb get
. Finally, Uvicorn uses the FastAPI object and specifies the host and port definition.
Start the application either using uvicorn
directly:
Or executing main.py
:
So far, this is as basic as things can be, but visit http://localhost:8000/health in a browser and see the JSON response rendered, or use the command line:
API Response:
Or using curl
on in a terminal:
Swagger
FastAPI also ships with Swagger UI, which means you can interact with your APIs out of the box. Go to http://localhost:8000/docs to explore and try out APIs via the browser:
Swagger UI:
The interactive Swagger UI provides working example curl
commands, for example:
API Router
It is a good idea to structure a project from the beginning. For example, APIs can live under a directory using a router.
Create a new api
directory in your project:
Move and modify the decorator of the health API into a new file under the api directory, for example:
from fastapi import APIRouter, status, Response
router = APIRouter()
@router.get('/health', status_code=status.HTTP_200_OK)
async def health_check_api(response: Response):
response.headers["X-Health-Check"] = "ok"
return {
'health': "ok"
}
Tip
FastAPI enables functions to use the async prefix. You can omit that but the framework may apply some
speed magic under the hood even if you're not calling them using await.
When you do want to use await
, the program will fail to run when calling function that are not
defined as async
. So, for the rest of this documentation all functions decorated with a route will use async
for good measure.
Now update main.py
by using the function configure()
to include configure_routing()
where
you can include any imported router:
import uvicorn
from fastapi import FastAPI
from api import health_api
main_app = FastAPI()
def configure():
configure_routing()
def configure_routing():
main_app.include_router(health_api.router)
if __name__ == '__main__':
configure()
uvicorn.run(main_app, host='0.0.0.0', port=8000)
else:
configure()
You can run main.py
again, and everything will function the same as before, only now the project is better organized.
Environment Variables
Pydantic, a supporting package to FastAPI, removes the need to implement something to handle passing in environment variables to use as settings within your project, which is neat!
However, since Pydantic version 2 we require an additional package:
Install the package by running the following again:Remember to freeze your packages:
Borrowing the naming conventions from Django, create a new directory that will serve as a place to keep project-wide settings:
Add the following file:
import pathlib
from pydantic_settings import BaseSettings
# Project Directories
ROOT = pathlib.Path(__file__).resolve().parent.parent
class Settings(BaseSettings):
CONFIG_API_VERSION_STR: str = "/api/v1"
CONFIG_MAIN_APP_HOST: str = "0.0.0.0"
CONFIG_MAIN_APP_PORT: int = 8000
settings = Settings()
These settings provide the string variable CONFIG_API_VERSION_STR
,
but because the Settings
class inherits from the Pydantic BaseSettings
environment variables are
covered. In other words, define defaults in settings.py
.
Any value exported as an environment variable will override these defaults.
Update main.py
to use the CONFIG_API_VERSION_STR
to prefix the URLs of any
route you define. Moreover, replace the hardcoded IP address and port
numbers with CONFIG_MAIN_APP_HOST
and CONFIG_MAIN_APP_PORT
.
import uvicorn
from fastapi import FastAPI
from api import health_api
from config.settings import settings # NEW
main_app = FastAPI()
def configure():
configure_routing()
def configure_routing():
main_app.include_router(health_api.router, prefix=settings.CONFIG_API_VERSION_STR, tags=["health"]) # UPDATED
if __name__ == '__main__':
configure()
uvicorn.run(main_app,
host=settings.CONFIG_MAIN_APP_HOST, # UPDATED
port=settings.CONFIG_MAIN_APP_PORT) # UPDATED
else:
configure()
Once again, everything will function the same as before except now you can override key settings using environment variables, for example export an alternative port number:
And run the application:
You'll notice that the path to the API is now /api/v1/health
since
the path in prefixed, and by using tags=["health"]
APIs via the swagger docs are
organized into tagged groups with headings. Moreover, in this example the application is now listening on
port 8080
:
Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /api/v1/health HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.88.1
> accept: application/json
>
< HTTP/1.1 200 OK
< date: Mon, 31 Jul 2023 17:41:12 GMT
< server: uvicorn
< content-length: 15
< content-type: application/json
< x-health-check: ok
<
* Connection #0 to host localhost left intact
{"health":"ok"}
You can undo the environment variable using unset
, for example unset CONFIG_MAIN_APP_PORT
.