Skip to content

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:

main.py
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:

uvicorn main:main_app --reload --port 8000

Or executing main.py:

python 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:

Health API browser

Or using curl on in a terminal:

curl http://localhost:8000/health
{"health":"ok"}                            

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:

Health API Swagger

The interactive Swagger UI provides working example curl commands, for example:

curl -X 'GET' 'http://localhost:8000/health' -H 'accept: application/json'

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:

mkdir api

Move and modify the decorator of the health API into a new file under the api directory, for example:

api/health_api.py
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:

main.py
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.

python main.py

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:

non_ver_reqs.txt
fastapi
uvicorn[standard]
pydantic-settings
Install the package by running the following again:

pip install -r non_ver_reqs.txt

Remember to freeze your packages:

pip freeze > requirements.txt

Borrowing the naming conventions from Django, create a new directory that will serve as a place to keep project-wide settings:

mkdir config

Add the following file:

config/settings.py
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.

main.py
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:

export CONF_MAIN_APP_PORT=8080

And run the application:

python main.py

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:

curl 'http://localhost:8080/api/v1/health' -H 'accept: application/json' -v
   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.