20 Martie 2024
I started writing this to clarify for myself how FastAPI works. It starts from a high-level and doesn't go very in depth. The reason I stopped is that I realised I'm not a fan of FastAPI's design choices. I realised I prefer the API design of Django Rest Framework and its little sister Django Ninja.
I decided to publish this anyway. I know it's pretty light, but maybe it helps create a clearer image in someones mind.
FastAPI is a Web Framework.
The Web works with a Client - Server Architecture.
An example of a Client is a Browser or a Library like Python's requests.
An example of a Server is nginx or Apache.
The Client sends a Request to the Server. The Server processes the Request and returns a Response.
FastAPI, just like the name says, allows you to build APIs (REST or GraphQL).
An API is a collection of Endpoints, with each Endpoint corresponding to a Resource.
An Example of a Resource is Author or BlogPost or Comment or anything else you can think of.
The REST convention is to use the plural in the resource Path, so the corresponding Paths are /authors
, /blogposts
, /comments
, /anythings
. I don't know about GraphQL, because I haven't used it.
Also, a specific Endpoint provides the 4 Operations that allow you to manage that Resource:
The most common way of using FastAPI is by defining Endpoints.
A FastAPI Endpoint consists of 2 parts:
The Decorator specifies things like
@app.get()
@app.get("/items")
@app.get("/items/{item_id}")
The Function specifies things like
So the function doesn't take the entire Request as an argument. It only specifies which parts of the request it needs. Then, it processes those, does its thing, and returns a Value that is further used to make up the Response. Notice, I didn't say it "returns the Response", because normally FastAPI is responsible for creating the Response from the Function's return value.
async def read_item(item_id: int):
You specify them in 2 ways:
async def read_item(skip: int = 0, limit: int = 10):
OR
async def read_items(skip: Annotated[int | None, Query()] = 0):
I assume FastAPI distinguishes between Path Parameters and Query Parameters by looking at the Path you defined. It matches any Parameters you defined there with Arguments you defined for the Function.
To speak in code, if you have this Endpoint:
@app.get("/items/{item_id}")
async def get_item(item_id: int):
pass
FastAPI knows that item_id
is coming from the Path.
However, if you make a mistake and you define this Endpoint:
@app.get("/items/")
async def get_item(item_id: int):
pass
FastAPI will look for item_id
among the Query Parameters.
async def read_items(user_agent: Annotated[str | None, Header()] = None):
async def read_items(ads_id: Annotated[str | None, Cookie()] = None):
By Capabilities I mean Functionality that you usually need in an API:
CORS (Cross-Origin Resource Sharing)
It exists as an Exception to Same-Origin Policy. The Same-Origin Policy allows a Browser App (like a Single-Page Application) or a simple Script to send Requests only Home. Home means the same Server where it came from. I guess Origin is the better word. So, normally the Script can only send Requests to its Origin Host.
CORS allows the Script to send Requests to other Hosts. It involves cooperation from the specific Host, because the Host must say in its Response that it allows Requests from the Script Origin. When it sees that, the Browser allows the Requests, so the Browser is the one that enforces this.
Capabilities can be implemented as Middleware.
Middleware can be visualised as a circle that surrounds an Endpoint. It processes a Request before it's passed to the Endpoint and it again processes the Response returned by the Endpoint.