Get started with FastAPI

Take advantage of the FastAPI web framework and Python to quickly create snappy, OpenAPI-compliant web APIs — and full websites, too.

Get started with FastAPI
Thinkstock

When Python web frameworks like Flask and Django first rose in popularity and prominence, Python was a somewhat different language than it was today. Many elements of modern Python, like asynchronous execution and the ASGI (Asynchronous Server Gateway Interface) standard, were either in their infancy or didn’t exist yet.

FastAPI is a Python web framework that has been built from the ground up to make use of modern Python features. It uses the ASGI standard for asynchronous, concurrent connectivity with clients, and it can work with WSGI if needed. Async functions can be used for routes and endpoints. And FastAPI allows web applications to be written efficiently in clean, modern Python code with type hints.

As the name implies, a primary use case of FastAPI is building API endpoints. This you can accomplish as easily as returning Python dictionary data as JSON, or by using the OpenAPI standard including an interactive Swagger UI. But FastAPI is by no means limited to APIs. You can use it for just about everything else a web framework does, from delivering plain old web pages using the Jinja2 template engine to serving applications powered by WebSockets.

Install FastAPI

FastAPI can install quite a few components on its own, so it’s best to start any FastAPI project in a new, clean virtual enivronment. The core FastAPI components can be installed with pip install fastapi.

You will also need to install an ASGI server for local testing. FastAPI works well with Uvicorn, so we’ll use that in our examples here. You can use pip install uvicorn[standard] to install Uvicorn with the optimal component set with C libraries, or use pip install uvicorn to install a minimal, pure-Python version.

Simple FastAPI example

A simple FastAPI application looks like this:

from fastapi import FastAPI
app = FastAPI()

@app.get("/")
async def root():
    return {"greeting":"Hello world"}

Save this as main.py, then run it from within your “venv” with the command uvicorn main:app. The app object is what you’d use for your ASGI server. (Note that you can also use WSGI with an ASGI-to-WSGI adapter, but it’s best to use ASGI.)

Once things are running, navigate to localhost:8000 (the default for a Uvicorn test server). You’ll see {"greeting":"Hello world"} in the browser — a valid JSON response generated from the dictionary.

This should give you an idea of how easy FastAPI makes it to deliver JSON from an endpoint. All you need to do is create a route and return a Python dictionary, which will be automatically serialized into JSON. There are steps you can take for serializing tricky data types, which we’ll go into later.

The general outlines of a FastAPI app should be familiar to anyone who has worked with systems like Flask:

  • The app object is imported into the ASGI or WSGI server and used to run the application.
  • Adding routes to an app can be done with a decorator. For instance, @app.get("/") creates a GET method route on the site’s root, with the results returned by the wrapped function.

However, some differences should already stand out. For one, your route functions can be async, so that any async components you deploy — e.g., an async database middleware connection — can run in those functions, too.

Note that there is nothing stopping you from using regular sync functions if you need them. In fact, if you have an operation that is computationally expensive, as opposed to one that waits on I/O (as is the best use case for async ), it would be best to use a sync function and let FastAPI sort it out. The rest of the time, use async.

Route types in FastAPI

The @app decorator lets you set the method used for the route, e.g., @app.get or @app.post. GET, POST, PUT, DELETE and the less-used OPTIONS, HEAD, PATCH, TRACE are all supported this way.

You can also support multiple methods on a given route simply by wrapping multple route functions, e.g. @app.get("/") on one function and @app.post("/") on another.

Path, query, and form parameters in FastAPI

If you want to extract variables from the route’s path, you can do so by defining them in the route declaration, and then passing them to the route function.

@app.get("/users/{user_id}")
async def user(user_id: str):
    return {"user_id":user_id}

To extract query parameters from the URL, you can use typed declarations in the route function, which FastAPI will automatically detect:

userlist = ["Spike","Jet","Ed","Faye","Ein"]

@app.get("/userlist")
async def userlist_(start: int = 0, limit: int = 10):
    return userlist[start:start+limit]

Processing form data is a little more complex. First, you’ll have to install an additional library, python-multipart, to parse form data (pip install python-multipart). You then use a syntax similar to the query parameter syntax, but with a few changes:

from fastapi import Form

@app.post("/lookup")
async def userlookup(username: str = Form(...), user_id: str = Form("")):
    return {"username": username, "user_id":user_id}

The Form object extracts the named parameter (username, user_id) from the submitted form and passes it along. Note that if you use Form(...) in the declaration, that’s a hint that the parameter in question is required, as with username here. For an optional form element, pass the default value for that element into Form, as with user_id here (Form("")).

Response types in FastAPI

The default response type for FastAPI is JSON, and so far all of the examples we’ve seen return data that is automatically serialized as JSON. But you can return other kinds of responses as well. For example:

from fastapi.responses import HTMLResponse

@app.get("/")
def root():
    return HTMLResponse("<b>Hello world</b>")

The fastapi.responses module supports many common response types:

  • HTMLResponse or PlainTextResponse: Returns text as HTML or plain text.
  • RedirectResponse: Redirects to a provided URL.
  • FileResponse: Returns a file from a provided path, streamed asynchronously.
  • StreamingResponse: Takes in a generator and streams the results out to the client.

You can also use a generic Response object, and provide your own customized status code, headers, content, and media type.

The Response object in FastAPI

Note that when you want to work with a response, for instance by setting cookies or setting headers, you can do so by accepting a Response object as a parameter in your route function.

from fastapi import Response

@app.get("/")
def modify_header(response:Response):
    response.headers["X-New-Header"] = "Hi, I'm a new header!
    return {"msg":"Headers modified in response"}

Cookies in FastAPI

Retrieving cookies from the client works something like handling query or form parameters:

from fastapi import Cookie

@app.get("/")
async def main(user_nonce: Optional[str]=Cookie(none)):
    return {"user_nonce": user_nonce}

Setting cookies is done by using the .set_cookie() method on a Response object:

from fastapi import Response

@app.post("/")
async def main(response: Response):
    response.set_cookie(key="user_nonce", value="")
    return {"msg":"User nonce cookie cleared"}

Using Pydantic models with FastAPI

Types in Python are generally optional, but FastAPI is more of a stickler about using types than many other Python frameworks. FastAPI uses the Pydantic library to declaratively verify submitted data, so you don’t have to write logic to do that yourself.

Here is an example of how Pydantic could be used to validate incoming JSON:

from typing import List, Optional
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()

class Movie(BaseModel):
    name: str
    year: int
    rating: Optional[int] = None
    tags: List[str] = []

@app.post("/movies/", response_model=Movie)
async def create_movie(movie: Movie):
    return movie

This snippet would accept JSON data via POST (not an HTML form!) with the fields name, year, rating, and tags. The types of each of these fields would then be validated. For instance, this data would be valid:

{
    "name":"Blade Runner 2049",
    "year": 2018,
    "rating": 5,
    "tags": ["science fiction","dystopia"]
}

If year were a string that could be interpreted as an integer (e.g. "2018") it would be converted automatically to the right data type. But if we had a year value that could not be interpreted as an integer, it would be rejected.

Using Swagger/OpenAPI in FastAPI

OpenAPI, previously known as Swagger, is a JSON-formatted standard for describing API endpoints. A client can read an OpenAPI definition for an endpoint and automatically determine the schemas for data sent and received by a website’s APIs.

FastAPI automatically generates OpenAPI definitions for all of your website’s endpoints. If you visit /openapi.json at the root of a FastAPI site, you’ll get a JSON file that describes each endpoint, the data it can receive, and the data it returns.

Another convenience that FastAPI provides is automatically generated documentation interfaces for your APIs, which you can interact with through a web interface. If you navigate to /docs, you’ll see a page for your APIs as generated by ReDoc; go to /docs and you’ll see one generated by the Swagger UI (older, less advanced). Both documentation user interfaces are configurable.

FastAPI also provides hooks for extending or modifying the auto-generated schema, or generating it conditionally or even disabling it.

fastapi IDG

FastAPI automatically generates OpenAPI specifications for all endpoints, which you can interact with through a web interface also automatically created by FastAPI. This interface can be disabled if needed.

Copyright © 2021 IDG Communications, Inc.

How to choose a low-code development platform