Skip to main content

Python - Fast API - Create your JSON API in Python in one minute

ยท 7 min read
Christophe
Markdown, WSL and Docker lover ~ PHP developer ~ Insatiable curious.

Python - Fast API - Create your JSON API in Python in one minute

TLDR: one minute is the time you'll need to copy/paste two files' content and to run one Docker statement.

Sounds crazy but it's TRUE. You'll just need one minute to create the example provided here below i.e. create your project's directory, create two files and run two Docker statements and ... it's done.

As PHP programmer, when I've taken time to read a blog article about FastAPI I thought Nodidju ! ร‡' n'est nรฉn pussibe (For Christ's sake! It can't be true).

With just two files, we'll build our own Docker image with Python and FastAPI installed and to code our REST API application. No more than two!

Impossible to not try immediately and ... wow ... that's TRUE!

Creating our first APIโ€‹

Like always, we'll build a fully working example. It couldn't be simpler; you'll see.

Please create a dummy folder and jump in it: mkdir /tmp/fastapi && cd $_.

In that folder, please create a new file called Dockerfile with the following content:

Dockerfile
# We'll use the latest version of Python and the smaller image in size (i.e. `slim`)
FROM python:slim

# We'll define the default folder in the image to /app
WORKDIR /app

# The only dependency we need is fastapi
RUN pip install --no-cache-dir fastapi[standard]

# We need to copy our Python script in the image
COPY main.py main.py

# And we'll run FastAPI and call our script. We'll expose the script on port 82
CMD ["fastapi", "run", "main.py", "--port", "82"]

The final image size will be about 184MB i.e. almost nothing.

As you've seen, we need a file called main.py; let's create it:

main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
return {"Hello": "World"}

This very straightforward code defines a route called /. It's a GET method and we'll return a JSON object:

{
"Hello": "World"
}

You know what? It's already done.

Running our APIโ€‹

We need to build our Docker image and run it:

docker build -t python-fastapi . && docker run -p 82:82 python-fastapi

Once done, just surf to http://127.0.0.1:82 and you'll obtain your first JSON answer; crazy no?

Hello World

Automated documentation of your APIโ€‹

And it's just the beginning: FastAPI comes with a self-documented API based on the OpenAPI schema.

Please jump to http://127.0.0.1:82/docs and you'll see it in action:

Automated documentation - Swagger UI

There is an second, alternative template called ReDoc. You can access it using the redoc endpoint i.e. http://127.0.0.1/redoc:

Automated documentation - Redoc

Let's play - Creating a joke generatorโ€‹

We'll update build a joke generator script. Our objective will be to get a random joke or a specific one (like Give me the third joke you know).

For this, we'll update our main.py script and because we'll probably make more than one change, we'll mount our host folder to the container.

Why? Mounting our folder inside the container will allow us to make changes to the script and just refresh the web page to see the change.

The only thing we need to do is to run our container like this: docker run -v .:/app -p 82:82 python-fastapi ... but it didn't work as expected.

FastAPI is using Uvicorn under the hood

Uvicorn is a web server implementation for Python. Uvicorn has a built-in cache mechanism so even if we've updated the source of our main.py script, we'll still get the old version.

We should start Uvicorn with a "hot reload" mechanism.

Recreating the Docker image for hot reloadโ€‹

First stop the running container: go back to your console and press CTRL+C to stop the running container. We'll also remove the container. You can do this by going to Docker Desktop; click on the Containers menu and kill your Python - Fastapi container. Remove also the image called python-fastapi.

note

python-fastapi is the name of the Docker image we've built earlier.

If you prefer the command line, you can achieve the same result by running these two commands:

docker rm $(docker ps -aq --filter "ancestor=python-fastapi")
docker rmi python-fastapi --force

Now, please copy/paste the following content to your existing Dockerfile:

Dockerfile
# We'll use the latest version of Python and the smaller image in size (i.e. `slim`)
FROM python:slim

# We'll define the default folder in the image to /app
WORKDIR /app

# The only dependency we need is fastapi
RUN pip install --no-cache-dir fastapi[standard]

# We need to copy our Python script in the image
COPY main.py main.py

# Run Uvicorn with hot reload so any changes to our main.py script will force
# the server to invalidate the cache and refresh the page.
CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0", "--port", "82"]

Rebuild the image and run a new container by running these commands:

OK, we'll run the container again but, now, with a volume:

docker build -t python-fastapi . && docker run -v .:/app -p 82:82 python-fastapi

Now with a Docker image with hot reload and we've mounted our folder in the container.

Please edit your main.py script like this:

main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
return {"Hello": "Belgium!"}

Refresh your web page, you'll see Hello Belgium!. Change your main.py and replace Belgium! by France. Reload your web page; you'll see Hello France. So, nice, we've a hot reload and we can really start to play.

Our joke generatorโ€‹

We'll update our main.py script like this:

from random import choice
from fastapi import FastAPI

app = FastAPI()

jokes = [
"Why do programmers always mix up Halloween and Christmas? Because Oct 31 == Dec 25",
"Why don't programmers like nature? Because it's full of bugs!",
"How many programmers does it take to change a light bulb? None. That's a hardware problem.",
"What do you call 8 hobbits? A hobbyte",
"What is this [โ€œhipโ€, โ€hipโ€]? hip hip array!"
]

@app.get("/jokes")
async def get_jokes():
"""
Returns a random joke from the list.
"""
return {"joke": choice(jokes)}

@app.get("/jokes/{joke_id}")
def read_item(joke_id: int):
"""
Returns a specific joke (between 0 and 4).
"""
try:
return {"joke": jokes[joke_id]}
except IndexError:
return {"error": f"Joke with ID {joke_id} not found."}, 404

You immediately see it I think:

  • I've defined an array with five, hardcoded, jokes;
  • I've defined a new route called /jokes and that one will display a random joke
  • And finally I've defined a jokes/{joke_id} to be able to target a specific joke (like "Give me the second joke you know").
Use an external file instead of hardcoding jokes

As an exercise; just remove the initialisation part of the jokes array and, instead, read jokes from a text file. It would be really easy to do.

Go back to your browser and surf to the jokes endpoint (http://127.0.0.1:82/jokes) and, hop, you've a random joke.

Getting a random joke

Just refresh the page; again and again. Every time you'll get a random joke (from a list of 5).

If you want a specific one, just put an ID after like http://127.0.0.1:82/jokes/1

A specific joke

note

Please note that the array start at position 0 so the first joke is this one: http://127.0.0.1:82/jokes/0.

Our jokes endpoints are documented automaticallyโ€‹

And looking back to the documentation (http://127.0.0.1:82/docs); we've now three routes and, take a look, the Python docstring is used to describe the route.

Swagger UI - With new routes

Really, really impressive!

Docs are interactive

And, more impressive, you can play directly from the documentation i.e. you can run the jokes endpoint. There is a button Try it out then Execute to see the endpoint in action.

More info https://fastapi.tiangolo.com/tutorial/first-steps/#interactive-api-docs

Discover FastAPIโ€‹

Don't wait any longer, go to https://fastapi.tiangolo.com/ to see more examples.

Also read the long tutorial on Real Python - Using FastAPI to Build Python Web APIs.