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 to prominence, Python was a somewhat different language than it is 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 was built from the ground up to integrate modern Python features. It uses the ASGI standard for asynchronous, concurrent connectivity with clients, and it can work with WSGI if needed. Asynchronous functions are built-in for routes and endpoints. And FastAPI allows you to develop web applications efficiently with clean, modern Python code with type hints.

As the name implies, a primary use case of FastAPI is building API endpoints quickly. 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 environment. 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

Here's a simple FastAPI application:


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 application 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.
  • You can use decorators to add routes to an application. 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 asynchronous, so that any async components you deploy—e.g., an asynchronous database middleware connection—can run in those functions, too.

Note that there is nothing stopping you from using regular synchronous 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, and TRACE are all supported this way.

You can also support multiple methods on a given route simply by wrapping multiple 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 = 3):
    return userlist[start:start+limit]

This way, the query parameters start and limit would automatically be extracted from the URL and passed along in those named variables. If those parameters didn't exist, the default values would be assigned to them.

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 the examples 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.

If you want to generate HTML programmatically for an HTMLResponse, you can do that with Jinja2 or another templating engine of your choice.

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, the following 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 WebSockets in FastAPI

WebSocket endpoints in FastAPI are also simple:


from fastapi import FastAPI, WebSocket

@app.websocket("/ws")
async def ws_connection(websocket: WebSocket):
    await websocket.accept()
    while True:
        data = await websocket.receive_text()
        await websocket.send_text(f"You said: {data}")

This example receives a connection on the endpoint /ws, typically established in JavaScript with a WebSocket object, then waits for input and echoes back the response in a loop.

WebSockets, as a rule, don't have much in the way of authentication security. A common practice is to make a secured WebSockets connection, then send as the first message to the socket some kind of token or credential that authenticates the user. FastAPI doesn't provide additional mechanisms for securing WebSocket connections, so you'll have to build that functionality yourself.

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 a 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.

Conclusion

As Python evolves and adapts, the libraries used with it for common tasks evolve, too. FastAPI may be one of the newer web frameworks, but it's already achieved major prominence thanks to its forward-looking design. Any nontrivial Python web project deserves to consider it as a candidate.

Copyright © 2023 IDG Communications, Inc.