FastAPI¶
A simple working code to get started.
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"Hello": "JK"}
@app.get("/items/{item_id}")
def get_item(item_id: int, skip: int = 0, limit: int = 30):
return {
"item_id": item_id,
"limit": limit,
"skip": skip
}
Run fastapi dev main.py to start the server. By default it starts in localhost:8000
-
loclahost:8000/docs-> swagger UI documentation -
localhost:8000/redoc-> redoc UI documentation
OpenAPIis a specification that dictates how to define a schema of your API.
Path Parameter¶
A parameter that is part of the path and passed as an argument to the method is a path parameter. Path parameter is provided in python f-string style.
Path parameter can not be made optional while query parameter can be.
For additionla validation of Path parameter Path() functoin can be used.
ENUM used for predefined values¶

class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
return model_name
Path parameter order matters¶
If user/{user_id} is defined first in code and then user/me, a call to user/me could still route to user/{user_id}
Query Parameter¶
Query parameters are the extra parameter that are not part of the path. In a URL they are provided after a ? and separated by &
Example : http://localhost:8000/items/4?skip=0&limit=10
If a function arguement is both argyument and path its a
path parameter. If argument is not in path and just an argument of singular type likeint,float,str,booletc... then it is aquery parameter. If an argument is aPydantic Modelthen its aRequest Bodyused in post or put calls.
For query parameters additional validations can be done using Query() method in fastapi.
@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
Below are the list of additional validations that could be used with Query() method.
default
default_factory
alias
alias_priority
validation_alias
serialization_alias
title
description
gt
ge
lt
le
min_length
max_length
pattern
regex
discriminator
strict
multiple_of
allow_inf_nan
max_digits
decimal_places
example
examples
openapi_examples
deprecated
include_in_schema
json_schema_extra
**extra,
If query parameter is a list type q: Annotated[list[str] | None, Query()] then q=foo&q=bar multiple values can be passed to q.
Alias names can be use like q: Annotated[str | None, Query(alias="other name"
Pydantic provieds Aftervalidator and BeforeValidator for more customized validations.
def check_valid_id(id: str):
if not id.startswith(("isbn-", "imdb-")):
raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
return id
@app.get("/items/")
async def read_items(
id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
Query parameter can also be a model as shown below
class FilterParams(BaseModel):
limit: int = Field(100, gt=0, le=100)
offset: int = Field(0, ge=0)
order_by: Literal["created_at", "updated_at"] = "created_at"
tags: list[str] = []
@app.get("/items/")
async def read_items(filter_query: Annotated[FilterParams, Query()])
Request Body¶
To declare a request body, you use Pydantic models. Item is a pydantic model class. item parameter is of type Item , hence it is a request body and not query parameter.
Request body can also be of a singular datatype. To achieve this use Body() . This makes any parameter into a request body. Example below👇
from fastapi import Body
from typing import Annotated
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, q: str | None = None)
...
========OR=======
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Annotated[int, Body()], q: str | None = None)
...
Path()Query()Header()Cookie()Body()Form()File()
Cookie¶
Cookie is similar to a query parameter. In fastapi it is declared using a Cookie() function that has all the similarities of Path(), Query() etc...
Cookie parameter is also used to get information from the client but rather ssensitive information than what is shared as a query parameter.
If a session ID or authenticaiton tokens are sent as a query parameters, it is made visible in the URL and thus disclosing sensitive information. Cookies on the other hand are reliable in this aspect.
-
Cookies are commonly used to store session IDs or authentication tokens
-
Cookies can store user preferences like dark mode, language
ende...
@app.get("/items/")
async def read_items(ads_id: Annotated[str | None, Cookie()] = None):
return {"ads_id": ads_id}
Cookie can also be accepted as a pydantic model.
Header¶
hear strange_header is automatically converted into strange-header . To disable it
convert_underscore=False need to be set in Header() function.
@app.get("/items/")
async def read_items(
strange_header: Annotated[str | None, Header()] = None,
):
return {"strange_header": strange_header}
Header can also be accepted as a pydantic model.
Response Model¶
Response can be validated in two ways:
-
async def read_items() -> list[Item], return type annotation -
@app.post("/items/", response_model=Item), usnig response_model in decorator.
If both of the above are declared to a function response_model takes the priority.
Important Status Codes¶
Success Codes¶
| Status Code | Description |
|---|---|
| 200 | Request was successful and a response body is returned |
| 204 | Request was successful but the response body is empty |
Client-Side Status Codes¶
| Status Code | Description |
|---|---|
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden. The client (browser) is not allowed to access the requested resource. |
| 404 | Page not found |
Server-Side Status Codes¶
| Status Code | Description |
|---|---|
| 500 | Internal Server Error |
| 502 | Bad Gateway |
| 503 | Service Unavailable. The server is currently unavailable or turned off. |
Form¶
When you need to receive form fields instead of JSON, you can use Form
For example, in one of the ways the OAuth2 specification can be used (called "password flow") it is required to send a
usernameandpasswordas form fields.The spec requires the fields to be exactly named
usernameandpassword, and to be sent as form fields, not JSON.Forms can also be accepted as a pydantic model.
File¶
Files can be accepted in fastapi using File() and UploadFile methods.
While File() is similar to Body() method.
async def create_file(file: Annotated[bytes, File()]):
async def create_upload_file(file: UploadFile):
for multiple file uploads use list[UploadFile] type.
security¶
OAuth2 has different flows for authorization. Among that passowrd flow is one.
OAUth2 can be implemented as below
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
@app.post("/token")
def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
return {"access_token": form_data.username+form_data.password, "token_type": "bearer"}
-
define an object of
OAuth2PasswordBeare. This object is callable. Therefore it is called as a dependency. -
If tokenUrl is
/token, then define/tokenend point that must accept username and password as a form data. Readily availableOAuth2PasswordRequestFormcan be used.
This
/tokenendpoint must return a JSON response of structure {"access_token": ..., "token_type" : ...}, unless it wont work.
- Add
oauth2_schemeas dependency to those endpoints where security is needed.
Middleware¶
A "middleware" is a function that works with every request before it is processed by any specific path operation. And also with every response before returning it.
It get the request, performs the reqired action before sending the request to be processed by GET, POST etc... Then it gets the response from that function, works on it and send back to client.
this happens to all the request coming to the application
To create a middleware you use the decorator @app.middleware("http") on a function.
Mount Static files¶
app.mount("/static", StaticFiles(directory="static"), name="static")
Images, videos or any other file can be stored in directory named static and can be served. Access at http://localhost:8000/static/image.jpg
Testing¶
Testing of APIs are done by TestClient in Fastapi.
-
Import
TestClient -
Create a
TestClientby passing your FastAPI application to it. -
Create functions with a name that starts with
test_(this is standardpytestconventions).
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
Keep the testing logic in other file. Ex: test_main.py
from fastapi.testclient import TestClient
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"}
Debugging¶
You can debug your FastAPI application using the following code snippet. Run the code with the VS Code Python debugger enabled.
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
FastAPI deployment¶
To deploy an application means to perform the necessary steps to make it available to users.
Ensure that you use the appropriate version of FastAPI that is best suited for your production application. Using a dependency management tool like uv will handle this automatically.
fastapi[standard]>=0.112.0,<0.113.0
WARNING: Do not specify a version for the starlette library.
IMPORTANT : It is a common practice to have one program/HTTP server (TLS Termination Proxy) running on the server (the machine, host, etc.) and managing all the HTTPS parts like receiving the encrypted HTTPS requests, sending the decrypted HTTP requests to the actual HTTP application running in the same server (the FastAPI application, in this case), take the HTTP response from the application, encrypt it using the appropriate HTTPS certificate and sending it back to the client using HTTPS.
TLS Termination Proxy¶
-
Traefik (has certificate renewal feature)
-
Caddy (has certificate renewal feature)
-
Nginx (
certbotcan be used for cert renewal) -
Kubernetes with an Ingress Controller like Nginx ✅