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?
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:
There is an second, alternative template called ReDoc. You can access it using the redoc
endpoint i.e. http://127.0.0.1/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.
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
.
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").
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.
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
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.
Really, really impressive!
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.