FastAPI is a modern, high-performance web framework for building APIs with Python, based on standard Python type hints.
It was designed to be fast, easy to use, and highly compatible with other web frameworks and tools.
FastAPI leverages the power of async/await and Python 3.7+ type hints to provide an efficient and developer-friendly experience for building web applications and APIs.
Key Features and Importance of Best Pratices
Some key features of FastAPI include:
- High performance: FastAPI is built on top of Starlette and Pydantic, which makes it one of the fastest web frameworks available for Python.
- Easy to learn and use: FastAPI is designed to be simple and intuitive, making it easy for developers to get started and be productive quickly.
- Automatic documentation: FastAPI automatically generates interactive API documentation using OpenAPI (formerly Swagger) and JSON Schema.
- Type safety: FastAPI enforces type safety using Python type hints, which helps catch bugs and errors at development time rather than runtime.
- Async/await support: FastAPI supports asynchronous programming, allowing developers to build highly concurrent and scalable applications.
Following best practices when working with FastAPI is crucial for several reasons:
- Maintainability: Adhering to best practices ensures that your code is well-organized, easy to understand, and maintainable, making it simpler for other developers to contribute to your project.
- Scalability: Implementing best practices helps you create a solid foundation for your application, allowing it to scale efficiently as it grows in complexity and size.
- Security: Following best practices can help you avoid common security pitfalls and vulnerabilities, ensuring that your application is more secure and robust.
- Performance: By adhering to best practices, you can optimize your application’s performance, taking full advantage of FastAPI’s speed and efficiency.
- Community standards: Following best practices aligns your code with community standards and expectations, making it easier for others to collaborate, review, and provide feedback on your work.
Setting Up and Structuring Your FastAPI Project
Before starting your FastAPI project, it’s essential to set up a virtual environment to isolate your project’s dependencies from other Python projects on your system.
You can use tools like venv
or pipenv
to create a virtual environment. In this example, we’ll use venv
:
- Create a new directory for your project and navigate to it in your terminal.
- Create a virtual environment using the following command:
python3 -m venv venvpython3 -m venv venvpython3 -m venv venv
Enter fullscreen mode Exit fullscreen mode
This command creates a new virtual environment named venv in your project directory.
- Activate the virtual environment. The activation command depends on your operating system:
For Linux and macOS:
source venv/bin/activatesource venv/bin/activatesource venv/bin/activate
Enter fullscreen mode Exit fullscreen mode
For Windows:
venv\Scripts\activatevenv\Scripts\activatevenv\Scripts\activate
Enter fullscreen mode Exit fullscreen mode
With the virtual environment activated, install FastAPI and an ASGI server like Uvicorn or Hypercorn. In this example, we’ll use Uvicorn:
pip install fastapi uvicornpip install fastapi uvicornpip install fastapi uvicorn
Enter fullscreen mode Exit fullscreen mode
Structuring your project for scalability
Structuring your FastAPI project properly is crucial for maintaining a scalable and maintainable codebase.
Here’s a suggested project structure that you can adapt to your needs:
my_fastapi_project/│├── app/│ ├── __init__.py│ ├── main.py│ ├── api/│ │ ├── __init__.py│ │ ├── v1/│ │ │ ├── __init__.py│ │ │ ├── users.py│ │ │ ├── posts.py│ │ │ └── ...│ │ └── v2/│ │ └── ...│ ├── models/│ │ ├── __init__.py│ │ ├── user.py│ │ ├── post.py│ │ └── ...│ ├── config.py│ ├── database.py│ └── utils.py│├── tests/│ ├── __init__.py│ ├── test_users.py│ ├── test_posts.py│ └── ...│├── .env├── requirements.txt├── Dockerfile└── README.mdmy_fastapi_project/ │ ├── app/ │ ├── __init__.py │ ├── main.py │ ├── api/ │ │ ├── __init__.py │ │ ├── v1/ │ │ │ ├── __init__.py │ │ │ ├── users.py │ │ │ ├── posts.py │ │ │ └── ... │ │ └── v2/ │ │ └── ... │ ├── models/ │ │ ├── __init__.py │ │ ├── user.py │ │ ├── post.py │ │ └── ... │ ├── config.py │ ├── database.py │ └── utils.py │ ├── tests/ │ ├── __init__.py │ ├── test_users.py │ ├── test_posts.py │ └── ... │ ├── .env ├── requirements.txt ├── Dockerfile └── README.mdmy_fastapi_project/ │ ├── app/ │ ├── __init__.py │ ├── main.py │ ├── api/ │ │ ├── __init__.py │ │ ├── v1/ │ │ │ ├── __init__.py │ │ │ ├── users.py │ │ │ ├── posts.py │ │ │ └── ... │ │ └── v2/ │ │ └── ... │ ├── models/ │ │ ├── __init__.py │ │ ├── user.py │ │ ├── post.py │ │ └── ... │ ├── config.py │ ├── database.py │ └── utils.py │ ├── tests/ │ ├── __init__.py │ ├── test_users.py │ ├── test_posts.py │ └── ... │ ├── .env ├── requirements.txt ├── Dockerfile └── README.md
Enter fullscreen mode Exit fullscreen mode
Key components of the project structure:
- app/: Contains the main application logic, including the API routes, models, configuration, database, and utility functions.
- app/main.py: The entry point for your FastAPI application, where you initialize the app and mount the API routes.
- app/api/: Contains the API routes organized by version (e.g., v1, v2).
- app/models/: Contains the Pydantic data models used for data validation and serialization.
- app/config.py: Contains the application configuration settings.
- app/database.py: Contains the database connection and related functions.
- app/utils.py: Contains utility functions used throughout the application.
- tests/: Contains the unit tests for your application. .env: Contains environment variables for your application.
- requirements.txt: Lists the Python dependencies for your application.
- Dockerfile: Used for building a Docker image of your application.
- README.md: Provides an overview and documentation for your project.
Data Validation and Handling HTTP Requests
FastAPI uses Pydantic for data validation and serialization.
Pydantic enforces type safety and provides helpful error messages when invalid data is submitted.
To define a data model with Pydantic, create a class with the required attributes and their corresponding types:
from pydantic import BaseModelclass UserIn(BaseModel):username: stremail: strpassword: strclass UserOut(BaseModel):id: intusername: stremail: strclass Config:orm_mode = Truefrom pydantic import BaseModel class UserIn(BaseModel): username: str email: str password: str class UserOut(BaseModel): id: int username: str email: str class Config: orm_mode = Truefrom pydantic import BaseModel class UserIn(BaseModel): username: str email: str password: str class UserOut(BaseModel): id: int username: str email: str class Config: orm_mode = True
Enter fullscreen mode Exit fullscreen mode
In this example, we define two Pydantic models, UserIn and UserOut, for handling input and output user data, respectively.
Implementing path and query parameters
FastAPI allows you to define path and query parameters for your API endpoints.
Path parameters are part of the URL path, while query parameters are added to the URL as key-value pairs.
Example:
from fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI()class Item(BaseModel):name: strdescription: str@app.get("/items/{item_id}")async def read_item(item_id: int, q: str | None = None):return {"item_id": item_id, "q": q}@app.put("/items/{item_id}")async def update_item(item_id: int, item: Item):return {"item_id": item_id, "item": item}from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str description: str @app.get("/items/{item_id}") async def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} @app.put("/items/{item_id}") async def update_item(item_id: int, item: Item): return {"item_id": item_id, "item": item}from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str description: str @app.get("/items/{item_id}") async def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} @app.put("/items/{item_id}") async def update_item(item_id: int, item: Item): return {"item_id": item_id, "item": item}
Enter fullscreen mode Exit fullscreen mode
In this example, we define two API endpoints:
- /items/{item_id}: A GET endpoint that accepts an item_id path parameter and an optional q query parameter.
- /items/{item_id}: A PUT endpoint that accepts an item_id path parameter and a Pydantic Item model as the request body.
Handling HTTP requests and responses
FastAPI simplifies handling HTTP requests and responses.
You can define the required HTTP method (e.g., GET, POST, PUT, DELETE) for each endpoint, set status codes, and return responses with custom headers.
Example:
from fastapi import FastAPI, HTTPExceptionfrom fastapi.responses import JSONResponseapp = FastAPI()@app.get("/users", response_model=List[UserOut])async def get_users():users = await get_users_from_db()return users@app.post("/users", response_model=UserOut, status_code=201)async def create_user(user: UserIn):user_id = await create_user_in_db(user)return await get_user_by_id_from_db(user_id)@app.delete("/users/{user_id}", response_class=JSONResponse)async def delete_user(user_id: int):result = await delete_user_from_db(user_id)if not result:raise HTTPException(status_code=404, detail="User not found")return {"detail": "User deleted"}from fastapi import FastAPI, HTTPException from fastapi.responses import JSONResponse app = FastAPI() @app.get("/users", response_model=List[UserOut]) async def get_users(): users = await get_users_from_db() return users @app.post("/users", response_model=UserOut, status_code=201) async def create_user(user: UserIn): user_id = await create_user_in_db(user) return await get_user_by_id_from_db(user_id) @app.delete("/users/{user_id}", response_class=JSONResponse) async def delete_user(user_id: int): result = await delete_user_from_db(user_id) if not result: raise HTTPException(status_code=404, detail="User not found") return {"detail": "User deleted"}from fastapi import FastAPI, HTTPException from fastapi.responses import JSONResponse app = FastAPI() @app.get("/users", response_model=List[UserOut]) async def get_users(): users = await get_users_from_db() return users @app.post("/users", response_model=UserOut, status_code=201) async def create_user(user: UserIn): user_id = await create_user_in_db(user) return await get_user_by_id_from_db(user_id) @app.delete("/users/{user_id}", response_class=JSONResponse) async def delete_user(user_id: int): result = await delete_user_from_db(user_id) if not result: raise HTTPException(status_code=404, detail="User not found") return {"detail": "User deleted"}
Enter fullscreen mode Exit fullscreen mode
In this example, we define three API endpoints:
- /users: A GET endpoint that returns a list of users with a 200 OK status code.
- /users: A POST endpoint that creates a new user and returns the created user with a 201 Created status code.
- /users/{user_id}: A DELETE endpoint that deletes a user and returns a custom response with a 200 OK status code. If the user is not found, it raises an HTTPException with a 404 Not Found status code.
Exception Handling, Middleware, and CORS
FastAPI provides built-in support for exception and error handling.
You can define custom exception handlers to centralize the way your application handles specific exceptions, and you can set HTTP error responses for different status codes.
Example:
from fastapi import FastAPI, HTTPExceptionfrom fastapi.exceptions import RequestValidationErrorapp = FastAPI()@app.exception_handler(RequestValidationError)async def validation_exception_handler(request, exc):return JSONResponse(status_code=422,content={"detail": "Validation error", "errors": exc.errors()})@app.response_exception_handler(HTTPException)async def http_exception_handler(request, exc):return JSONResponse(status_code=exc.status_code,content={"detail": exc.detail})from fastapi import FastAPI, HTTPException from fastapi.exceptions import RequestValidationError app = FastAPI() @app.exception_handler(RequestValidationError) async def validation_exception_handler(request, exc): return JSONResponse( status_code=422, content={"detail": "Validation error", "errors": exc.errors()} ) @app.response_exception_handler(HTTPException) async def http_exception_handler(request, exc): return JSONResponse( status_code=exc.status_code, content={"detail": exc.detail} )from fastapi import FastAPI, HTTPException from fastapi.exceptions import RequestValidationError app = FastAPI() @app.exception_handler(RequestValidationError) async def validation_exception_handler(request, exc): return JSONResponse( status_code=422, content={"detail": "Validation error", "errors": exc.errors()} ) @app.response_exception_handler(HTTPException) async def http_exception_handler(request, exc): return JSONResponse( status_code=exc.status_code, content={"detail": exc.detail} )
Enter fullscreen mode Exit fullscreen mode
In this example, we define two custom exception handlers:
- validation_exception_handler: Handles RequestValidationError exceptions and returns a 422 Unprocessable Entity status code along with the validation errors.
- http_exception_handler: Handles HTTPException exceptions and returns the corresponding status code and error detail.
Creating and registering custom middleware
FastAPI allows you to create and register custom middleware to perform actions before or after processing a request.
Middleware is useful for tasks like logging, authentication, and rate limiting.
Example:
from fastapi import FastAPI, Requestapp = FastAPI()async def log_request_middleware(request: Request, call_next):request_start_time = time.monotonic()response = await call_next(request)request_duration = time.monotonic() - request_start_timelog_data = {"method": request.method,"path": request.url.path,"duration": request_duration}log.info(log_data)return responseapp.middleware("http")(log_request_middleware)from fastapi import FastAPI, Request app = FastAPI() async def log_request_middleware(request: Request, call_next): request_start_time = time.monotonic() response = await call_next(request) request_duration = time.monotonic() - request_start_time log_data = { "method": request.method, "path": request.url.path, "duration": request_duration } log.info(log_data) return response app.middleware("http")(log_request_middleware)from fastapi import FastAPI, Request app = FastAPI() async def log_request_middleware(request: Request, call_next): request_start_time = time.monotonic() response = await call_next(request) request_duration = time.monotonic() - request_start_time log_data = { "method": request.method, "path": request.url.path, "duration": request_duration } log.info(log_data) return response app.middleware("http")(log_request_middleware)
Enter fullscreen mode Exit fullscreen mode
In this example, we define a custom middleware called log_request_middleware.
This middleware logs the request method, path, and duration for each incoming request.
The middleware is then registered with the FastAPI app using app.middleware().
Enabling Cross-Origin Resource Sharing (CORS)
FastAPI provides built-in support for Cross-Origin Resource Sharing (CORS), which allows you to control which domains can access your API.
Enabling CORS is essential for modern web applications that rely on APIs to fetch and manipulate data.
Example:
from fastapi import FastAPIfrom fastapi.middleware.cors import CORSMiddlewareapp = FastAPI()origins = ["http://localhost","http://localhost:8080","https://example.com"]app.add_middleware(CORSMiddleware,allow_origins=origins,allow_credentials=True,allow_methods=["*"],allow_headers=["*"],)from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI() origins = [ "http://localhost", "http://localhost:8080", "https://example.com" ] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], )from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI() origins = [ "http://localhost", "http://localhost:8080", "https://example.com" ] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], )
Enter fullscreen mode Exit fullscreen mode
In this example, we enable CORS for the FastAPI app by adding the CORSMiddleware middleware.
The allow_origins parameter specifies a list of domains that are allowed to access the API.
The allow_credentials, allow_methods, and allow_headers parameters configure additional CORS settings.
Testing, Debugging, and Deployment
Testing and debugging are essential parts of the software development process.
FastAPI provides built-in support for testing and debugging, making it easier to ensure the quality and reliability of your
application.
Example of Testing with FastAPI:
# tests/test_users.pyfrom fastapi.testclient import TestClientfrom app.main import appclient = TestClient(app)def test_create_user():user_data = {"username": "testuser", "email": "test@example.com", "password": "test123"}response = client.post("/users", json=user_data)assert response.status_code == 201assert response.json()["username"] == user_data["username"]assert response.json()["email"] == user_data["email"]def test_get_users():response = client.get("/users")assert response.status_code == 200assert len(response.json()) >= 1# tests/test_users.py from fastapi.testclient import TestClient from app.main import app client = TestClient(app) def test_create_user(): user_data = {"username": "testuser", "email": "test@example.com", "password": "test123"} response = client.post("/users", json=user_data) assert response.status_code == 201 assert response.json()["username"] == user_data["username"] assert response.json()["email"] == user_data["email"] def test_get_users(): response = client.get("/users") assert response.status_code == 200 assert len(response.json()) >= 1# tests/test_users.py from fastapi.testclient import TestClient from app.main import app client = TestClient(app) def test_create_user(): user_data = {"username": "testuser", "email": "test@example.com", "password": "test123"} response = client.post("/users", json=user_data) assert response.status_code == 201 assert response.json()["username"] == user_data["username"] assert response.json()["email"] == user_data["email"] def test_get_users(): response = client.get("/users") assert response.status_code == 200 assert len(response.json()) >= 1
Enter fullscreen mode Exit fullscreen mode
In this example, we create two test cases for the /users endpoint using FastAPI’s TestClient.
The test_create_user function tests the creation of a new user, while the test_get_users function tests the retrieval of a list of users.
Debugging with the interactive debugger
To enable the interactive debugger in FastAPI, you can use the –reload and –debug flags when running the application with Uvicorn:
uvicorn app.main:app --reload --debuguvicorn app.main:app --reload --debuguvicorn app.main:app --reload --debug
Enter fullscreen mode Exit fullscreen mode
With these flags enabled, the application will automatically reload on code changes, and the interactive debugger will be activated when an unhandled exception occurs.
Deployment and scaling considerations
When deploying a FastAPI application, it’s essential to consider factors like performance, scalability, and security.
Some best practices for deploying and scaling FastAPI applications include:
- Using a production-ready ASGI server like Gunicorn or Hypercorn.
- Configuring a reverse proxy like Nginx to handle SSL termination, static file serving, and request routing.
- Using a process manager like systemd or supervisor to manage and monitor your application.
- Implementing caching and/or using a content delivery network (CDN) to improve performance.
- Horizontally scaling your application by running multiple instances behind a load balancer.
- Deploying your FastAPI application with Gunicorn and Nginx Gunicorn and Nginx are popular choices for deploying FastAPI applications in production.
Gunicorn is a Python WSGI HTTP Server for UNIX, while Nginx is a high-performance, open-source web server and reverse proxy.
Example Gunicorn configuration:
# gunicorn.conf.pyworkers = 4worker_class = "uvicorn.workers.UvicornWorker"# gunicorn.conf.py workers = 4 worker_class = "uvicorn.workers.UvicornWorker"# gunicorn.conf.py workers = 4 worker_class = "uvicorn.workers.UvicornWorker"
Enter fullscreen mode Exit fullscreen mode
In this example, we create a gunicorn.conf.py file to configure the Gunicorn server.
We set the number of workers to 4 and specify the UvicornWorker class for handling ASGI applications.
Example Nginx configuration:
# nginx.confserver {listen 80;server_name example.com;location / {include uwsgi_params;uwsgi_pass unix:/path/to/your/project/my_fastapi_project.sock;}}# nginx.conf server { listen 80; server_name example.com; location / { include uwsgi_params; uwsgi_pass unix:/path/to/your/project/my_fastapi_project.sock; } }# nginx.conf server { listen 80; server_name example.com; location / { include uwsgi_params; uwsgi_pass unix:/path/to/your/project/my_fastapi_project.sock; } }
Enter fullscreen mode Exit fullscreen mode
In this example, we create an nginx.conf file to configure the Nginx server.
We set the server to listen on port 80, specify the domain name, and configure the / location to proxy.
Conclusion
This guide presented essential best practices for building FastAPI applications.
By setting up and structuring your project for scalability, using Pydantic for data validation, handling HTTP requests and responses, implementing exception and error handling, custom middleware, and CORS, and testing and debugging your application, you can create robust and maintainable FastAPI applications.
Deploying your application with Gunicorn and Nginx ensures improved performance and scalability.
To stay updated with the latest FastAPI features and best practices, continue learning and engaging with the FastAPI community, exploring open-source projects, and experimenting with new tools and techniques.
Following these best practices will help you build high-quality APIs that meet your users’ needs and support your development goals.
原文链接:FastAPI Best Practices: A Condensed Guide with Examples
暂无评论内容