I put together this list of questions to test and reinforce my understanding of FastAPI after building projects with it and working through the official documentation. I decided to share it here in case it’s helpful to others as well.
This is a curated set of FastAPI interview questions covering core concepts such as setup, routing, validation, Pydantic models, database integration, WebSockets, and deployment.
Feel free to connect with me:
LinkedIn: https://www.linkedin.com/in/ali-r-652a79170/
X (Twitter): https://x.com/AliRezaeiX
Contributions are very welcome. Feel free to submit pull requests to add new questions, improve existing ones, or open issues if you spot any mistakes or want to discuss the wording or accuracy of a question. I check GitHub regularly and will review and merge contributions as soon as possible.
FastAPI is a modern Python web framework for building APIs. It uses Python type hints for validation and automatic documentation. It is designed around ASGI for high concurrency and supports both async and sync endpoints. In practice it is commonly used for RESTful services, microservices, and ML inference APIs where performance and schema clarity matter.
FastAPI requires Python 3.6 or above. Its core features rely on async/await and typing, so older Python versions are not supported. Newer FastAPI releases may raise the minimum version, so you should check the current release notes when upgrading.
FastAPI is built on Starlette (ASGI framework) and Pydantic (data validation). Starlette handles routing, middleware, and networking while Pydantic handles parsing, validation, and schema generation. This separation lets FastAPI focus on developer experience while relying on battle-tested components.
FastAPI is fully compatible with OpenAPI and JSON Schema standards. This enables automatic docs, client generation, and strong schema-based validation. It also ensures your API contract is machine-readable for tooling and governance.
The documentation highlights high performance, fast development speed, fewer human errors, and ease of learning. It also emphasizes automatic docs and strong IDE support via type hints. These benefits combine to reduce boilerplate and increase confidence in request/response correctness.
FastAPI was created by Sebastian Ramirez in December 2018. It was built to modernize Python API development with async support and type-driven validation. The project is open source and maintained with a large community.
It supports API standards, provides strong validation, and is designed for reliable deployment, not just experimentation. Its ASGI foundation and tooling integration make it suitable for real production workloads. Features like dependency injection, middleware support, and OpenAPI docs simplify larger systems.
The documentation notes that FastAPI performance is on par with NodeJS and Go due to its ASGI foundation. Actual throughput depends on your server, I/O patterns, and implementation details. Using async I/O correctly and choosing an efficient ASGI server are key factors.
Use pip to install FastAPI:
pip3 install fastapiYou typically install it inside a virtual environment and pair it with an ASGI server like Uvicorn for local development. For extra tooling, some projects use pip install "fastapi[all]" to include optional extras.
FastAPI does not include a built-in server. You run it with an ASGI server such as Uvicorn, which handles HTTP connections and the async event loop. The server is responsible for concurrency, lifecycle, and socket management.
Minimal install:
pip3 install uvicornStandard install (adds extras):
pip3 install "uvicorn[standard]"The standard extras bring in performance and protocol dependencies that are commonly used in production. It is the usual choice unless you want a minimal dependency set.
The standard install adds WebSocket support and extra dependencies such as PyYAML. It also enables faster event loop and HTTP parsing options when available. This can improve throughput and reduce latency under load.
Use pip3 freeze to list installed packages and versions. You can also use pip show fastapi or pip list for quick inspection. In CI, a pinned requirements file helps keep versions consistent.
A virtual environment isolates project dependencies and avoids conflicts with system packages. It also makes deployments more reproducible across machines. This is especially useful when multiple projects require different versions.
A minimal app imports FastAPI, creates an app instance, and defines a route:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def index():
return {"message": "Hello World"}This is enough to serve JSON responses and verify your setup with an ASGI server. The file can be named main.py or similar and referenced by Uvicorn.
It creates the main ASGI application object that Uvicorn serves. This object holds routes, middleware, and dependency injection configuration. You can also pass metadata like title, version, and description here.
Use a decorator and a path operation function:
@app.get("/")
async def index():
return {"message": "Hello World"}The decorator registers the function under the GET method for the given path. You can define sync or async functions depending on your I/O needs.
It can return dict, list, str, int, or Pydantic model objects. FastAPI serializes them to JSON and can also accept Response objects for full control. This makes it easy to return plain data or custom responses.
Run:
uvicorn main:app --reloadThis is intended for local development and will restart the server when files change. It is convenient but adds overhead and should not be used in production.
It enables auto-reload so code changes restart the server automatically. This uses file watching and should be disabled in production. In containers, you may need to mount code for reload to work.
It provides interactive API documentation for your FastAPI app. The UI is generated from the OpenAPI schema. It lets users explore endpoints, see schemas, and send test requests.
FastAPI uses Swagger UI for the /docs endpoint. It is interactive and lets you try requests directly against your API. This is useful for manual testing and onboarding new developers.
It returns the generated OpenAPI schema in JSON format. The schema is built from your routes, parameters, and models at runtime. Tools like Swagger UI, ReDoc, and client generators consume this endpoint.
JSON Schema is used to describe the request and response data structures inside OpenAPI. This drives validation, docs, and client generation. Constraints like lengths and numeric ranges are encoded here.
/redoc provides an alternative documentation UI based on ReDoc. It is primarily for browsing, not for interactive testing. Many teams prefer it for clean, structured reference docs.
It includes API title, version, paths, operations, and response schemas. It can also include components, parameters, tags, and security definitions. This schema is the source of truth for the API contract.
Uvicorn is an ASGI server that runs async Python web apps. It implements the ASGI spec so frameworks like FastAPI can handle concurrency efficiently. It manages connections, HTTP parsing, and the event loop.
WSGI is synchronous and cannot efficiently handle async tasks or WebSockets. It typically relies on threads or processes, which limits scalability for I/O-bound workloads. ASGI is designed for long-lived connections and async I/O.
The documentation mentions uvloop for the event loop and httptools for HTTP. These provide faster async I/O and HTTP parsing compared to pure-Python defaults. They are included in the standard extras.
Example:
uvicorn main:app --reloadThis points to the module and app instance, and is the typical way to run in development. You can add --host and --port to customize the bind address.
Call uvicorn.run() in your script:
if __name__ == "__main__":
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)This is useful for embedding the server in a Python entrypoint or custom startup script. It also allows conditional configuration based on environment variables.
Use --host and --port, for example --host 127.0.0.1 --port 8000. You can also configure workers, log level, and reload behavior via CLI options. In containers, --host 0.0.0.0 is typically required.
Type hints document expected types, enable validation, and improve tooling support. FastAPI reads them to generate schemas and error responses automatically. This reduces manual parsing and inconsistent error handling.
Use annotations after parameter names:
def division(x: int, y: int):
return x / yFastAPI uses these annotations to parse inputs and reject invalid values. Static analyzers and IDEs also use them for hints and warnings.
Add -> type before the colon:
def division(x: int, y: int) -> float:
return x / yReturn type hints help with editor tooling and can be used by FastAPI for response documentation. You can override this with response_model for stricter output control.
Python does not enforce hints at runtime, so invalid types can still raise errors unless validated. FastAPI relies on Pydantic to enforce types at request time. Outside of FastAPI, type hints are mostly for tools and readers.
MyPy is a static type checker. Run mypy your_file.py to catch type mismatches before runtime, often as part of CI. It helps keep large codebases consistent as they evolve.
They let you specify element types, such as List[str] or Dict[str, int]. This improves validation and schema generation for nested structures. In newer Python, you can use list[str] and dict[str, int] as well.
IDEs can suggest methods and detect errors based on the declared type. They also enable static analysis and refactoring support. This reduces runtime bugs from incorrect attribute usage.
With name: str, the IDE knows it is a string and suggests string methods like capitalize(). This reduces mistakes and speeds up development. It also clarifies intent for other readers.
If a parameter is typed as a class, the IDE can autocomplete its attributes and methods. This also helps type checkers ensure correct usage across modules. It improves maintainability when models are reused across services.
FastAPI uses type hints to build request/response schemas for OpenAPI. The generated schema feeds Swagger UI and client code generation tools. Type hints also help FastAPI decide whether a parameter is a path, query, or body field.
REST is a resource-based architecture for web APIs using HTTP methods for operations. It emphasizes predictable URLs, statelessness, and uniform interfaces. Resources are typically nouns and actions are expressed via HTTP verbs.
- Uniform interface
- Statelessness
- Client-server
- Cacheability
- Layered system
- Code on demand These are the classic REST constraints that guide API design and scalability. Code on demand is optional and less commonly used.
They enable scalability, simplicity, modifiability, reliability, portability, and visibility. They also support caching and loose coupling between client and server. This makes APIs easier to evolve without breaking clients.
POST = Create, GET = Read, PUT = Update, DELETE = Delete. PATCH is commonly used for partial updates. HEAD and OPTIONS are also relevant for metadata and CORS.
Resources are exposed through path operations and HTTP methods. Each path operation maps to a handler function with typed parameters. This keeps resource definitions explicit and discoverable in docs.
Each request must contain all the information needed for processing, without server-side session state. Authentication data is typically sent on every request. This improves scalability because any server instance can handle any request.
A path parameter is a variable part of a URL path, such as {name}. It is required for the route to match. It typically identifies a specific resource instance.
Use braces in the path string:
@app.get("/hello/{name}")
async def hello(name: str):
return {"name": name}FastAPI matches the placeholder name to the function argument and validates it using the type hint. You can add constraints using Path(...) for additional validation.
Define a function argument with the same name as the path parameter. FastAPI injects the converted value automatically. The value is already validated and typed.
Yes, for example /hello/{name}/{age}. Each placeholder must be unique and will be validated independently. Order matters because it affects routing.
Type hints like age: int force conversion and validation for that parameter. Invalid values result in a 422 validation error before your handler runs. This prevents manual parsing in your business logic.
FastAPI returns a validation error with details such as type_error.integer. The response includes the field location and message. This makes client debugging straightforward.
Path parameters are part of the URL path, while query parameters follow ? in the URL. Path params are required; query params are optional unless declared required. Query params are commonly used for filtering and pagination.
Swagger UI lists path parameters as required inputs for the endpoint. It shows their names, types, and constraints from your annotations. Required fields are clearly marked.
Any function parameter not declared in the path is treated as a query parameter. Defaults make them optional; missing required values trigger validation errors. Complex types can also be declared with Query(...) for extra metadata.
Example:
@app.get("/hello")
async def hello(name: str, age: int):
return {"name": name, "age": age}Both values are taken from the query string and converted to the declared types. If a value is missing and required, FastAPI returns a 422 error.
Use ?key=value pairs joined by &, for example /hello?name=Ravi&age=20. Values arrive as strings and are converted by FastAPI. Repeating a key can create list values.
Yes. Example: /hello/{name} plus query parameters like ?age=20. This is common for filtering or pagination. Path params identify the resource, query params refine the result.
They appear under the Parameters section for the endpoint. Swagger UI marks them as required or optional based on defaults. It also shows constraints like min/max or regex patterns when provided.
FastAPI returns a 422 validation error with a detail section. The response includes the missing field location and a human-readable message. This happens before your endpoint logic runs.
Use the Path class:
from fastapi import Path
@app.get("/hello/{name}")
async def hello(name: str = Path(..., min_length=3, max_length=10)):
return {"name": name}Path lets you add constraints and metadata to path parameters that appear in docs and validation errors. Using ... (Ellipsis) marks the parameter as required.
Pass min_length and max_length to Path or Field for string parameters. These constraints are enforced before your handler runs. The same pattern applies to Query and Body validations.
Use gt, ge, lt, and le for numeric ranges. gt/lt are strict, while ge/le are inclusive bounds. This helps prevent invalid IDs or range inputs.
Use the Query class:
from fastapi import Query
@app.get("/report")
async def report(percent: float = Query(..., ge=0, le=100)):
return {"percent": percent}Query provides the same validation and metadata features as Path for query parameters. Constraints are reflected in the docs and enforced automatically.
It includes detail with fields like loc, msg, and type. This structure makes it easy to identify which input failed validation. The loc shows where the error occurred (path, query, body).
It forces keyword-only arguments so FastAPI can parse them correctly when using Path and Query together. This avoids ambiguity with positional arguments. It also makes function signatures clearer.
Swagger UI lists constraints such as min/max length or numeric ranges. These are derived from your Path/Query/Field metadata. The same constraints are exported in the OpenAPI schema.
It validates and parses request bodies and response data using type hints. Pydantic also powers schema generation for OpenAPI. This ensures consistent validation across input and output.
Example:
from pydantic import BaseModel
from typing import List
class Student(BaseModel):
id: int
name: str
subjects: List[str] = []Pydantic models provide defaults, validation, and helpful error messages for invalid input. They are the core building blocks for request and response schemas.
It converts compatible types, such as a numeric string to an int, when possible. You can opt into stricter validation in newer Pydantic versions. This helps accept common client input while still enforcing types.
It raises a ValidationError with details about the failed fields. FastAPI catches this and returns a structured 422 response. The client gets clear feedback on what to fix.
Field adds metadata and validation rules like max_length or titles to model fields. It can also define defaults, examples, and descriptions. This metadata is reflected in OpenAPI docs.
Call .dict() on the model instance. In Pydantic v2, the equivalent method is .model_dump(). You can also pass flags like exclude_unset to control output.
orm_mode = True allows Pydantic models to read ORM objects directly. This lets you return ORM instances and still get proper serialization. It prevents you from manually converting ORM objects to dicts.
Declare the model as a function parameter:
@app.post("/students/")
async def student_data(s1: Student):
return s1FastAPI reads the JSON body, validates it against the model, and injects a typed instance. If multiple body parameters are declared, FastAPI expects a JSON object with matching keys.
POST is typically used to send JSON request bodies. PUT and PATCH also commonly include request bodies for updates. GET requests generally do not include a body.
Use the Body class:
from fastapi import Body
@app.post("/students")
async def student_data(name: str = Body(...), marks: int = Body(...)):
return {"name": name, "marks": marks}Body marks these values as coming from the request body instead of the query string. You can also set embed=True to change how the body is structured.
Add them all to the function signature, for example:
@app.post("/students/{college}")
async def student_data(college: str, age: int, student: Student):
return {"college": college, "age": age, **student.dict()}FastAPI pulls each value from the correct location and validates them independently. Path and query values are parsed from the URL, while the model comes from the JSON body.
It displays a Request body section with the JSON schema for the model. This includes required fields, types, and example structure when provided. It helps clients understand exactly what shape to send.
It converts them to JSON automatically, including Pydantic models. Internally it uses a JSON-compatible encoder to handle types like datetime. You can override serialization with a custom response class if needed.
Yes. If you return the model instance, FastAPI serializes it back to JSON. You can still apply response_model filtering to remove sensitive fields. This is common for create endpoints that echo the created resource.
Compute values inside the function before returning, such as adding a percentage field. You can also define optional fields in the response model for derived values. This keeps calculations close to business logic and documented in OpenAPI.
Return an HTMLResponse with HTML content. You can also set response_class=HTMLResponse on the decorator. This is useful for simple pages or template rendering.
Use fastapi.responses.HTMLResponse. It sets the Content-Type to text/html for proper rendering. This ensures browsers render the content as HTML.
The documentation uses Jinja2 for templating. It supports template inheritance, filters, and server-side rendering. Jinja2 is a common choice in Python web apps.
Example:
from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates")This sets the folder where your HTML templates live and enables template loading. You can point it to any directory that contains your templates.
Jinja2Templates requires the request object for context and URL generation. It enables helpers like url_for inside templates. Without it, URL generation and some context features won't work.
Use {{ variable_name }} in the HTML. Jinja2 will render values from the context dictionary you pass. You can also use control structures like {% for %} and {% if %}.
Provide it in the TemplateResponse context:
return templates.TemplateResponse("hello.html", {"request": request, "name": name})All context values are available in the template as Jinja2 variables. This is the standard way to inject data into templates.
Static files are assets such as images, CSS, and JavaScript that do not change per request. They are served directly without template rendering. Serving them separately improves caching and performance.
You need aiofiles to serve static files with FastAPI. It provides async file I/O used by StaticFiles. Without it, static file serving will fail.
Example:
from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="static"), name="static")Mounting creates a sub-application that serves files under the /static URL path. The name is used with url_for in templates.
Use url_for:
<img src="{{ url_for('static', path='fa-logo.png') }}" />Using url_for keeps paths correct if your app is mounted under a prefix. It also makes refactoring easier if you change the mount path later.
Use a script tag with url_for:
<script src="{{ url_for('static', path='hello.js') }}"></script>This ensures the correct URL is generated in different environments. It also keeps template references consistent with your static mount.
Static assets are served unchanged and are easier to cache and manage separately. Templates remain focused on dynamic HTML rendering. This separation improves maintainability and performance.
Create a GET route that returns a TemplateResponse for the form HTML. Use a separate POST route to handle submission. This follows the common GET/POST pattern for forms.
POST is commonly used to submit form data. GET is used for retrieving the form, not sending the form body. POST keeps submitted data out of the URL.
It selects the encoding. Use multipart/form-data for file uploads and the default for plain text fields. Without the correct enctype, file data will not be sent.
Set the form action to the POST endpoint URL you define in FastAPI. Ensure the form method matches your route and consider using url_for in templates. This keeps routes consistent if you change URL prefixes later.
Use fastapi.Form to parse form fields. It tells FastAPI to read fields from form-encoded bodies. Without it, FastAPI treats the values as query parameters.
Install python-multipart. It is required for parsing both standard form data and file uploads. This dependency handles multipart encoding.
Example:
from fastapi import Form
@app.post("/submit/")
async def submit(nm: str = Form(...), pwd: str = Form(...)):
return {"username": nm}Using Form(...) makes the fields required; defaults make them optional. You can add validation constraints with Form(..., min_length=...).
Standard form submissions use application/x-www-form-urlencoded. File uploads require multipart/form-data. The enctype on the HTML form controls this.
Yes. You can build a model from the form fields and return it as response_model. This keeps validation and response serialization consistent. It also documents the response shape automatically.
Use UploadFile and File in the endpoint signature. This tells FastAPI to parse multipart form data and provide a file object. You can also accept additional form fields alongside the file.
UploadFile = File(...) is the common pattern in FastAPI. You can also use bytes for small files, but it loads the whole file into memory. UploadFile is safer for large uploads.
Example:
import shutil
with open("destination.png", "wb") as buffer:
shutil.copyfileobj(file.file, buffer)UploadFile exposes a file-like object so you can stream to disk without loading everything into RAM. Remember to close the file after saving if you keep it open.
The form must use enctype="multipart/form-data". Without it, the file field will not be parsed correctly. This encoding allows binary file data in the request.
It provides a file-like object and metadata like filename. It also supports streaming and spooling to disk for large files. This keeps memory usage stable under heavy uploads.
Cookies store small pieces of data on the client for later requests. They are commonly used for sessions, preferences, or tracking. Cookies are sent automatically by the browser on subsequent requests.
Example:
response.set_cookie(key="username", value="admin")You typically set cookies on a Response object so they are sent in the final response headers. You can also set attributes like httponly, secure, and max_age.
Use Cookie dependency:
from fastapi import Cookie
async def read_cookie(username: str = Cookie(None)):
return {"username": username}If no default is provided, the cookie becomes required and missing values will trigger a 422 error. You can also set an alias if the cookie name differs from the parameter name.
They appear as parameters for the endpoint when Cookie is used. Swagger UI shows their names, types, and required status. Note that browsers often block setting cookies from the docs UI.
The parameter value is None if you provide a default of None. If the cookie is required, FastAPI returns a 422 validation error. This makes missing cookie handling explicit.
Use the Header dependency:
from fastapi import Header
async def read_header(accept_language: str = Header(None)):
return {"Accept-Language": accept_language}Header parameters are case-insensitive and converted to valid Python identifiers. You can also provide aliases for custom header names.
Hyphens are converted to underscores in parameter names (accept-language -> accept_language). You can use the alias parameter if you need a specific header name. This keeps Python identifiers valid.
Return a JSONResponse with a headers dict:
return JSONResponse(content=data, headers={"X-Web-Framework": "FastAPI"})You can also set headers on a Response parameter for the same effect. Custom headers may need to be exposed via CORS for browsers to read them.
Provide them in the headers dict, for example Content-Language. The same approach works for both custom and standard headers. Response objects also allow setting headers directly.
In the response section under Headers after executing the endpoint. You only see them after clicking "Execute" in the docs. This is useful for debugging cookie and header behavior.
It defines the shape of the response and validates output data. It is also used to filter out any fields not declared in the model. This is a key security feature to prevent leaking internal fields.
Only fields defined in the response_model are included in the response. Extra keys are removed before sending the response. Filtering applies recursively to nested models.
It generates the response schema for the endpoint in OpenAPI docs. This drives documentation and client generation. Clients can rely on this schema for validation.
Yes. FastAPI will filter the output to the response_model fields. This is a common way to hide sensitive data. It lets you return ORM objects while exposing only safe fields.
It validates that the returned data conforms to the response_model types. If it does not, FastAPI raises a server error because the backend returned invalid data. This protects clients from inconsistent responses.
Return a full model but set response_model to a smaller one that excludes sensitive fields. This keeps internal fields out of responses without changing business logic. It is common to have separate input and output models.
Use another BaseModel as a field type in your model. Pydantic will validate nested objects recursively. This helps represent complex JSON structures cleanly.
Yes. Use List[Model] or Tuple[Model] to define nested collections. This maps cleanly to arrays in JSON. Each element is validated using the nested model.
They appear as separate schemas with references in the parent schema. Swagger UI lets you drill into each component model. This improves readability for complex responses.
They produce nested JSON objects and arrays that mirror the model structure. This keeps complex payloads organized and predictable. It also keeps schemas consistent across endpoints.
They keep complex data organized and validated at each level. Nested models also improve reusability across endpoints. They make large payloads easier to reason about.
It is a mechanism to provide shared logic or resources to multiple routes. Dependencies can be sync or async and are resolved per request. This is commonly used for DB sessions, auth, and configuration.
It moves common parameters into a shared dependency function or class. This centralizes validation and reduces boilerplate in handlers. It also keeps endpoint signatures cleaner.
Example:
async def dependency(id: str, name: str, age: int):
return {"id": id, "name": name, "age": age}Dependencies can also perform side effects or raise HTTPException when validation fails. They can be async or sync depending on the work they do.
Use Depends in the function signature:
@app.get("/user/")
async def user(dep: dict = Depends(dependency)):
return depFastAPI resolves the dependency before calling the path operation function. The resolved value is injected into the handler parameter.
Define a class with init and use it with Depends:
class Dependency:
def __init__(self, id: str, name: str, age: int):
self.id = id
self.name = name
self.age = ageClass-based dependencies can hold state per request and support complex initialization logic. They are useful when you want to encapsulate behavior and configuration.
Use the dependencies parameter on the decorator:
@app.get("/user/", dependencies=[Depends(validate)])These run even if their return values are not used by the handler. They are often used for auth, logging, or rate limiting.
A dependency can raise HTTPException when data is invalid. This is a clean way to enforce auth or business rules across routes. It centralizes validation logic in one place.
Use yield to provide a resource and perform cleanup afterward (for example, closing a DB session). Code after the yield runs even if the request fails. This is similar to a context manager pattern.
CORS controls whether browsers can call your API from different origins. It affects preflight requests and is critical for frontend-backend integrations. Without proper CORS settings, browsers will block cross-origin requests.
An origin is the combination of protocol, domain, and port. If any of these differ, the request is cross-origin. For example, https://example.com and http://example.com are different origins.
Add CORSMiddleware:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)These settings control which browsers can access your API and what headers they can send. In production you should restrict origins to trusted domains.
They define which origins, HTTP methods, and headers are permitted. You can also set allow_credentials and expose_headers for advanced cases. These settings directly impact browser behavior.
Create, Read, Update, Delete. These map to standard HTTP methods in REST APIs. Most API designs are organized around these operations.
Use a POST route to append an item to storage (for example, a list). It commonly returns the created resource with status 201. You often return a Location header pointing to the new resource.
Define a GET route that returns the full list of items. In practice, you often add pagination and filtering. This prevents returning huge datasets in a single response.
Use a GET route with a path parameter and return the matching item. If the item is missing, return a 404 error. This is the standard pattern for resource retrieval.
Use a PUT route with a path parameter to replace an item in storage. PUT typically replaces the resource, not just part of it. Use PATCH when only partial updates are needed.
Use a DELETE route with a path parameter to remove an item. Common responses are 204 No Content or the deleted resource. Many APIs return 204 with an empty body.
It is not persistent and is not safe for concurrent or multi-process use. Data resets on restart and does not scale across workers. It is fine for demos but not production.
SQLAlchemy provides ORM support to map Python classes to SQL tables. It also manages sessions, queries, and transactions across databases. This reduces manual SQL and keeps models consistent.
Example:
from sqlalchemy import create_engine
engine = create_engine("sqlite:///./test.db", connect_args={"check_same_thread": False})The connect_args flag is needed for SQLite when using multiple threads. The connection string determines where the database file lives.
A session is the database handle used to run queries and transactions. It manages the unit of work and commits changes to the database. You typically create and close a session per request.
It creates the base class for defining ORM models. All mapped classes inherit from this base to share metadata. This metadata is used to create tables and relationships.
Declare a class with tablename and Column fields. Columns define types, constraints, and primary keys. You can also define relationships between tables.
Call Base.metadata.create_all(bind=engine). In production you typically use migrations rather than auto-creation. Tools like Alembic handle schema changes safely.
Create a Pydantic model with matching fields and enable orm_mode. This allows reading ORM instances via attribute access. You often define separate models for create and read to avoid exposing internal fields.
It allows Pydantic models to read ORM objects directly. That makes response serialization work without manual dict conversion. In Pydantic v2, this is done with from_attributes.
Create an ORM instance, add it, commit, and refresh it. Refreshing loads generated fields like primary keys. You can also use flush to get IDs before commit.
Use db.query(Model).all() or filter(...).first() and return the results. In production you usually map ORM results to Pydantic response models. Filtering and pagination are often added to avoid large result sets.
MongoDB is a schema-less document store that fits flexible JSON-style data. It is useful when your data shape evolves frequently or is highly nested. This aligns well with JSON APIs.
The documentation uses PyMongo. For async access in FastAPI, Motor is the common alternative. Motor wraps PyMongo with async support.
Create a client with MongoClient(). You pass a connection string and then select a database and collection. Authentication and TLS settings are typically included in the URI.
Example:
result = book_collection.insert_one(book.dict())The result object includes the inserted ID, which you can return or store. You often convert it to a string for JSON responses.
Use collection.distinct("field"). You can also pass a filter to limit which documents are considered. This is useful for tag lists or categories.
Use find_one({"bookID": id}) or similar filters. If you use MongoDB's _id, you may need to convert to/from ObjectId. Failing to convert will result in no match.
GraphQL provides a single endpoint and allows clients to request only the data they need. This can reduce overfetching and improve frontend flexibility. It can be useful for complex client-driven queries.
The documentation recommends Strawberry. It offers a Pythonic, type-annotated API that integrates with FastAPI. Its schema is derived from type hints.
Decorate classes and methods:
@strawberry.type
class Book:
title: str
@strawberry.type
class Query:
@strawberry.field
def book(self) -> Book:
return Book(title="Computer Fundamentals")Type annotations define the schema, and @strawberry.field marks resolver functions. The resolvers implement how data is fetched.
Create a schema and mount it:
graphql_app = GraphQL(schema)
app.add_route("/book", graphql_app)
app.add_websocket_route("/book", graphql_app)The websocket route enables subscriptions if your GraphQL setup supports them. Mounting integrates GraphQL into your FastAPI routing tree.
The GraphiQL IDE is available at the GraphQL route, such as /book. It provides an in-browser query editor and explorer. This is helpful for testing and schema exploration.
WebSockets provide persistent, full-duplex communication over a single TCP connection, unlike HTTP request/response. They keep a connection open for real-time messaging. This avoids repeated HTTP handshakes.
FastAPI provides a WebSocket class and a websocket route decorator. Under the hood it uses Starlette's WebSocket implementation. You can handle text or binary messages with async methods.
Accept the connection, receive text, and send responses in a loop. You should also handle disconnects to clean up resources. A try/except for WebSocketDisconnect is common.
Example:
from fastapi import WebSocket
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message text was: {data}")You must call await websocket.accept() before reading or writing messages. After that, you can await receive/send methods in a loop.
They enable real-time updates such as chat, live notifications, and streaming data. They reduce latency by avoiding repeated HTTP polling. This is ideal for interactive apps.
Use JavaScript:
var ws = new WebSocket("ws://localhost:8000/ws");
ws.send("hello");Use wss:// in production when your site is served over HTTPS. You typically add event handlers like onmessage and onclose.
It sends back the same message it receives, often prefixed with a label. This is a basic way to verify bidirectional communication. It is often the first test for WebSocket support.
They are lifecycle hooks that run when the application starts or stops. They are typically used to initialize or clean up shared resources. These hooks run once per process.
Use the decorator:
@app.on_event("startup")
async def startup_event():
...Startup handlers are executed once when the application process starts. They are useful for opening DB connections or warming caches.
Use the decorator:
@app.on_event("shutdown")
async def shutdown_event():
...Shutdown handlers run during graceful shutdown to release resources. They should close DB sessions, flush logs, or stop background tasks.
Initialize resources on startup and clean them up on shutdown. Common examples include DB connections, caches, and background schedulers. This keeps resource management centralized.
A sub-application is another FastAPI app mounted under a path in a main app. It can have its own routes, middleware, and docs. This is useful for splitting large systems or exposing versioned APIs.
Use app.mount("/subapp", subapp). The sub-app will handle all requests under that prefix. The mounted app can be any ASGI app, not just FastAPI.
Requests under the mount path are handled by the sub-app's routes. The main app does not see those requests. Mounted apps are isolated from the main app's routes and dependencies.
They appear under the mount path, for example /subapp/docs. Each sub-app generates its own OpenAPI schema. This keeps documentation separate for each sub-application.
Middleware is code that runs for every request and response. It is commonly used for logging, security headers, and metrics. Middleware can inspect or modify requests and responses.
Use the @app.middleware("http") decorator:
@app.middleware("http")
async def addmiddleware(request: Request, call_next):
response = await call_next(request)
return responseCustom middleware can inspect or modify the request and response objects. It should always call call_next to continue processing.
It forwards the request to the next handler and returns the response. This is how middleware passes control down the stack. You can modify the response before returning it.
CORSMiddleware, HTTPSRedirectMiddleware, TrustedHostMiddleware, and GZipMiddleware. The order you add middleware affects execution order. Middlewares are executed in the order they are added and unwound in reverse.
It runs before the route handler and after the handler returns a response. Middleware layers wrap each other in the order they are added. This forms a request/response pipeline.
Wrap the Flask app with WSGIMiddleware and mount it on a path. This lets you serve legacy WSGI apps inside an ASGI stack. It is a bridge for gradual migrations.
It adapts a WSGI app (Flask/Django) to run under an ASGI server. This enables interoperability between async and sync frameworks. It does not add async capabilities to the WSGI app itself.
Whatever prefix you pass to mount, such as /flask. The WSGI app only handles routes under that prefix. Requests outside the prefix go to the FastAPI app.
Yes. Each app runs under its own route prefix. FastAPI can serve ASGI routes while WSGI routes are bridged via middleware. This lets you incrementally migrate endpoints.
Production deployment requires a public endpoint and a hosting platform, not just a local Uvicorn process. You also need process management, logging, security, and scaling concerns. Production setups often use multiple workers and a reverse proxy.
Deta is a cloud platform with a CLI. You log in and deploy with deta new, which provisions a micro and uploads your code. It provides a simple way to publish small services quickly.
It creates a new micro, installs dependencies, and deploys your app to an endpoint. It also generates a live URL for the service. The CLI sets up the project structure for you.
Use the endpoint URL given by Deta, and add /docs for Swagger UI. The OpenAPI schema is at /openapi.json. This makes it easy to validate the deployment.
APIRouter is a router class for grouping related endpoints in separate modules. It acts like a mini FastAPI app so you can keep routes organized, reuse prefixes, tags, responses, and dependencies, and then include them in the main app. This keeps large projects maintainable and avoids duplicated configuration. It also helps teams work in parallel on separate modules.
Create a router in a module, define routes on it, import it in main, and call app.include_router(router). The router routes are added to the application and appear in the OpenAPI docs. You can include the same router multiple times with different prefixes if needed. This is useful for versioning like /api/v1 and /api/latest.
Set those options in the APIRouter constructor or pass them to app.include_router(...). Prefixes should not end with a trailing slash, and the options apply to every route in that router. This is useful when you include routers you do not control. It ensures consistent metadata and auth across a group of endpoints.
Router-level dependencies run first, then dependencies declared in the path operation decorator, and then parameter dependencies. All dependencies are resolved before the endpoint function runs. FastAPI also caches dependency results per request by default. You can disable caching with use_cache=False if needed.
It sets the default HTTP status code for successful responses and documents it in OpenAPI. You can pass an int or constants from fastapi.status or http.HTTPStatus. You can still override it dynamically via a Response parameter. For create endpoints, 201 is the common choice.
Tags group endpoints in the interactive docs and OpenAPI. You can pass tags=[...], and for consistency you can define tags in an Enum or constants. This keeps large APIs navigable in Swagger and ReDoc. Tags also help organize client SDKs.
Use summary= and description= on the path operation decorator. These values show up in OpenAPI and Swagger UI and help clarify endpoint intent. Summaries appear in endpoint lists, while descriptions provide longer context.
Write a docstring inside the path operation function and FastAPI will use it as the description. Markdown in the docstring is rendered in the docs and can span multiple lines. This keeps documentation close to the code.
It sets the response description in OpenAPI. If omitted, FastAPI uses a default like "Successful response". OpenAPI requires every response to have a description. This is separate from the endpoint description.
Pass deprecated=True to the path operation decorator. The docs UI will mark the endpoint as deprecated but it will still work. This is useful when phasing out old endpoints.
Accept a Response parameter and set response.status_code inside the function. You can still return normal data and keep response_model filtering. Dependencies can also set the status code, with the last one winning. This enables conditional responses like 200 vs 201.
BackgroundTasks lets you schedule work to run after the response is sent. Use it for small side effects like emails or logging that should not block the response. For heavy jobs, use a task queue like Celery. Background tasks still run in the same process as the request.
Declare BackgroundTasks in dependencies or sub-dependencies; FastAPI reuses the same instance and aggregates tasks. All added tasks run after the response is sent, in the order they were added. This makes it easy to enqueue work from shared dependencies.
Raise HTTPException(status_code=..., detail=...). The detail can be any JSON-serializable data and becomes the error response body. Raising the exception stops further processing. This is the standard way to return controlled errors.
Pass a headers dict when raising HTTPException. This is useful for auth challenges or rate limit hints like WWW-Authenticate. It keeps error responses standards-compliant.
Use @app.exception_handler(CustomError) and return a response, commonly a JSONResponse. The handler receives the Request and the exception instance. This lets you centralize error formatting. It is also useful for logging or mapping internal errors to public messages.
Register a handler with @app.exception_handler(RequestValidationError) to customize validation output. Avoid leaking internal file or line details when returning errors to clients, especially in production. You can log details internally while returning safe messages.
jsonable_encoder converts Pydantic models and complex types (like datetime) into JSON-compatible data structures. It returns Python objects ready for JSON serialization or storage in JSON-based databases. This is commonly used before writing to NoSQL stores. It is the same encoder FastAPI uses internally for responses.
Use a model with optional fields and call model.model_dump(exclude_unset=True) (or .dict() in Pydantic v1) to get only provided values. Merge into the stored model with model_copy(update=...) and save the result. This prevents default values from overwriting existing data. The same pattern can be used with PUT when you want partial behavior.
FastAPI supports UUID, datetime/date/time, timedelta, Decimal, bytes, sets, and more. They parse from strings and serialize to JSON-friendly formats like ISO 8601 strings, floats, or lists. This lets you use rich types in signatures without manual parsing. The schema will reflect these formats in OpenAPI.
Define a Pydantic model and declare it with Annotated[Model, Query()] in the endpoint signature. FastAPI pulls each field from the query string and validates it. This is useful when many query params belong together. It also keeps signatures cleaner.
Set the model config to forbid extra fields (for example extra = "forbid"). Extra query parameters will produce a 422 validation error. This enforces strict API contracts. The error type is typically extra_forbidden.
Use Annotated[HeaderModel, Header()] for grouped headers. To keep underscores, pass Header(convert_underscores=False), noting some proxies reject underscores. This is useful when integrating with nonstandard headers. The model lets you validate a group of related headers at once.
Browsers restrict JavaScript from setting arbitrary cookies, so the docs UI may not send them. The cookies still appear in docs, but execution can fail without real browser cookies. Use a real browser session or a tool like curl to test. This is a browser security restriction, not a FastAPI issue.
Create a Pydantic model and declare it with Annotated[Model, Form()] in the endpoint. FastAPI extracts form fields into the model and validates them. This requires python-multipart to be installed. It is a clean way to handle many form fields at once.
response_class lets you choose the Response subclass in the decorator, which sets the media type and OpenAPI docs. JSON response classes still apply response_model filtering to returned data. It is useful for HTML, streaming, or optimized JSON responses. It ensures the Content-Type is documented correctly.
Return a Response directly when you need full control over headers, cookies, media type, or streaming. You bypass response_model filtering and automatic OpenAPI documentation for that response. You must ensure content and headers are correct yourself. This approach is often used for file downloads or streaming responses.
Accept a Response parameter and set headers or cookies on it, such as response.headers[...] or response.set_cookie(...). FastAPI merges those into the final response while still serializing your returned data. Dependencies can also set these values. This avoids returning a Response manually.
Create a Settings class that inherits from BaseSettings (from pydantic_settings) and instantiate it. Pydantic reads env vars, parses types, and can also load from a .env file. This keeps secrets out of source code. It also validates configuration at startup.
Provide a get_settings dependency that returns a Settings instance (often cached with lru_cache) and use it with Depends. In tests, override the dependency to supply test settings. This avoids global state and improves test isolation. It also makes configuration consistent across endpoints.
Mark tests with @pytest.mark.anyio, create an httpx.AsyncClient(app=app, base_url="http://test"), and await requests. Use a lifespan manager if your tests need startup or shutdown events. This mirrors real async behavior more closely than TestClient. It also lets you await async database calls in tests.
Run the server with --forwarded-allow-ips to trust X-Forwarded-* headers so redirects and URLs use the public scheme and host. Use --root-path when a proxy strips a prefix so OpenAPI and docs point to the correct path. This is common behind Nginx or Traefik. Without these settings, docs and redirects may point to the wrong URL.
Use response_model_exclude_unset, response_model_exclude_defaults, or response_model_exclude_none in the decorator. These control which fields are omitted during response serialization. This is useful for sparse models and large payloads. It also reduces response size for partial data.
Access Request when you need raw request details like client host or the full body. The tradeoff is that data read directly is not validated or documented by FastAPI. Use this sparingly and validate manually if needed. You still can combine it with normal parameter validation for other fields.