1. Introduction
Web APIs are essential for building modern web applications that communicate with servers, databases, and other services. However, developing web APIs can be challenging, especially when it comes to ensuring performance, security, and reliability. That’s why you need a web framework that can help you create web APIs faster and easier, while following the best practices and standards.
FastAPI is a web framework for Python that is designed to build web APIs quickly and efficiently. FastAPI is based on the OpenAPI specification, which defines a standard and language-agnostic interface for RESTful APIs. FastAPI also uses Pydantic, a library that validates and parses data using Python type annotations. FastAPI offers many features and benefits, such as:
- Automatic documentation generation using Swagger UI and ReDoc.
- Easy integration with popular libraries and tools, such as SQLAlchemy, Starlette, and Uvicorn.
- High performance and scalability, thanks to its asynchronous and concurrent nature.
- Built-in support for JSON Web Tokens (JWT), OAuth2, and CORS.
- Extensive documentation and community support.
In this blog, you will learn some best practices and tips for building better web APIs with FastAPI. You will learn how to set up the project, design the API schema, implement the API endpoints, handle errors and exceptions, add authentication and authorization, test and document the API, and more. By the end of this blog, you will have a fully functional and well-documented web API that follows the best practices and standards.
Are you ready to start building better web APIs with FastAPI? Let’s begin!
2. Setting Up the Project
Before you can start building your web API with FastAPI, you need to set up the project and install the required dependencies. In this section, you will learn how to create a virtual environment, install FastAPI and other libraries, and create a basic app file.
A virtual environment is a way of isolating your Python project from the rest of your system, so that you can use different versions of packages and avoid conflicts. To create a virtual environment, you can use the venv module that comes with Python. To do so, follow these steps:
- Create a folder for your project and navigate to it in your terminal.
- Run the command
python -m venv env
to create a virtual environment namedenv
inside your project folder. - Activate the virtual environment by running the command
source env/bin/activate
on Linux or macOS, orenv\Scripts\activate
on Windows. - You should see
(env)
at the beginning of your terminal prompt, indicating that you are in the virtual environment.
Now that you have created and activated your virtual environment, you can install FastAPI and other libraries that you will need for your project. To install FastAPI, you can use the pip package manager that comes with Python. To do so, run the command pip install fastapi
in your terminal. This will install FastAPI and its dependencies, such as Starlette and Pydantic.
Next, you need to install a server that can run your FastAPI app. FastAPI is compatible with any ASGI server, such as Uvicorn, Hypercorn, or Daphne. In this tutorial, you will use Uvicorn, which is a fast and lightweight ASGI server. To install Uvicorn, run the command pip install uvicorn
in your terminal.
Finally, you need to create a basic app file that will contain your FastAPI code. To do so, create a file named main.py
in your project folder and open it in your preferred code editor. Then, write the following code:
# Import FastAPI from fastapi import FastAPI # Create an app instance app = FastAPI() # Define a root endpoint @app.get("/") def hello(): return {"message": "Hello, world!"}
This code imports the FastAPI class from the fastapi
module, creates an app instance, and defines a root endpoint that returns a JSON response with a greeting message. You can test your app by running the command uvicorn main:app --reload
in your terminal. This will start the Uvicorn server and reload the app whenever you make changes to the code. You can then visit http://localhost:8000 in your browser and see the JSON response.
Congratulations, you have successfully set up your project and created a basic FastAPI app. In the next section, you will learn how to design the API schema for your web API.
3. Designing the API Schema
One of the best practices for building web APIs is to design the API schema before implementing the API endpoints. The API schema is a specification that defines the structure and behavior of the API, such as the available endpoints, the HTTP methods, the request and response formats, the parameters, the headers, the status codes, and the errors. By designing the API schema first, you can ensure that your API is consistent, clear, and easy to use.
FastAPI makes it easy to design the API schema, as it is based on the OpenAPI specification, which is a widely used and standardized format for describing web APIs. FastAPI uses Python type annotations and Pydantic models to automatically generate the OpenAPI schema from your code. You can then view and interact with the schema using the built-in Swagger UI or ReDoc interfaces, which are available at http://localhost:8000/docs and http://localhost:8000/redoc respectively.
To design the API schema for your web API, you need to think about the following aspects:
- What are the main resources or entities that your API will deal with? For example, if your API is for a blog application, you might have resources such as posts, comments, users, and tags.
- What are the operations or actions that your API will support for each resource? For example, you might want to create, read, update, and delete posts, or get a list of posts by a specific user or tag.
- What are the parameters or filters that your API will accept for each operation? For example, you might want to specify the page number, the page size, the sort order, or the search query for getting a list of posts.
- What are the data models or schemas that your API will use for each resource? For example, you might want to define the fields and types of a post, such as the title, the content, the author, the date, and the tags.
- What are the status codes and error messages that your API will return for each operation? For example, you might want to return a 200 OK code for a successful request, a 404 Not Found code for a non-existent resource, or a 400 Bad Request code for a validation error.
Once you have a clear idea of the API schema, you can start writing the code for your FastAPI app. In the next section, you will learn how to implement the API endpoints for your web API.
4. Implementing the API Endpoints
After designing the API schema, you can start implementing the API endpoints for your web API. An API endpoint is a URL that represents a specific resource or operation of your API. For example, http://localhost:8000/posts
could be an endpoint for getting a list of posts, while http://localhost:8000/posts/1
could be an endpoint for getting a single post with the ID of 1.
FastAPI provides a simple and intuitive way to define and implement API endpoints using Python decorators. A decorator is a function that modifies another function, adding some functionality or behavior. FastAPI uses decorators to register functions as API endpoints, specifying the HTTP method, the path, and the parameters. For example, the following code defines an endpoint for getting a list of posts:
# Import FastAPI and Pydantic from fastapi import FastAPI, Query from pydantic import BaseModel # Create an app instance app = FastAPI() # Define a data model for a post class Post(BaseModel): id: int title: str content: str author: str date: str tags: list[str] # Define a sample list of posts posts = [ Post(id=1, title="Hello, world!", content="This is my first post.", author="Alice", date="2021-01-01", tags=["intro", "welcome"]), Post(id=2, title="FastAPI rocks!", content="This is my second post.", author="Bob", date="2021-01-02", tags=["fastapi", "web"]), Post(id=3, title="Goodbye, world!", content="This is my last post.", author="Charlie", date="2021-01-03", tags=["outro", "farewell"]) ] # Define an endpoint for getting a list of posts @app.get("/posts") def get_posts(page: int = Query(1, ge=1), size: int = Query(10, ge=1)): # Validate and paginate the posts start = (page - 1) * size end = start + size if start >= len(posts): return {"error": "Page number out of range."} else: return {"posts": posts[start:end]}
This code imports FastAPI and Pydantic, creates an app instance, defines a data model for a post using Pydantic, defines a sample list of posts, and defines an endpoint for getting a list of posts using the @app.get
decorator. The decorator takes the path as an argument, and the function takes the parameters as arguments. The parameters are annotated with the Query
class, which validates and parses the query parameters. The function returns a JSON response with the list of posts, or an error message if the page number is out of range.
You can test this endpoint by visiting http://localhost:8000/posts or http://localhost:8000/posts?page=2&size=2 in your browser, or by using the Swagger UI or ReDoc interfaces. You can also view the OpenAPI schema for this endpoint by visiting http://localhost:8000/openapi.json in your browser.
In the same way, you can define and implement other API endpoints for your web API, using different HTTP methods, paths, and parameters. In the next subsections, you will learn some tips and tricks for using path and query parameters, validating and parsing request data, handling errors and exceptions, and adding authentication and authorization to your API endpoints.
4.1. Using Path and Query Parameters
Path and query parameters are two types of parameters that you can use to pass information to your API endpoints. Path parameters are part of the URL path, while query parameters are appended to the URL with a question mark. For example, in the URL http://localhost:8000/posts/1?size=10
, 1
is a path parameter and size=10
is a query parameter.
Path and query parameters are useful for specifying the resource or operation that you want to access, or for filtering or sorting the results. For example, you can use path parameters to get a single post by its ID, or query parameters to get a list of posts by a specific author or tag.
FastAPI allows you to define and use path and query parameters easily and conveniently, using Python type annotations and Pydantic classes. For example, the following code defines an endpoint for getting a single post by its ID, using a path parameter:
# Import FastAPI and Pydantic from fastapi import FastAPI, Path from pydantic import BaseModel # Create an app instance app = FastAPI() # Define a data model for a post class Post(BaseModel): id: int title: str content: str author: str date: str tags: list[str] # Define a sample list of posts posts = [ Post(id=1, title="Hello, world!", content="This is my first post.", author="Alice", date="2021-01-01", tags=["intro", "welcome"]), Post(id=2, title="FastAPI rocks!", content="This is my second post.", author="Bob", date="2021-01-02", tags=["fastapi", "web"]), Post(id=3, title="Goodbye, world!", content="This is my last post.", author="Charlie", date="2021-01-03", tags=["outro", "farewell"]) ] # Define an endpoint for getting a single post by its ID, using a path parameter @app.get("/posts/{post_id}") def get_post(post_id: int = Path(..., ge=1)): # Validate and return the post for post in posts: if post.id == post_id: return {"post": post} return {"error": "Post not found."}
This code defines a path parameter named post_id
, which is annotated with the Path
class. The Path
class validates and parses the path parameter, using the arguments that are passed to it. In this case, the arguments are ...
, which means that the parameter is required, and ge=1
, which means that the parameter must be greater than or equal to 1. The function returns a JSON response with the post, or an error message if the post is not found.
You can test this endpoint by visiting http://localhost:8000/posts/1 or http://localhost:8000/posts/4 in your browser, or by using the Swagger UI or ReDoc interfaces. You can also view the OpenAPI schema for this endpoint by visiting http://localhost:8000/openapi.json in your browser.
In the same way, you can define and use query parameters, using the Query
class instead of the Path
class. For example, the following code defines an endpoint for getting a list of posts by a specific author, using a query parameter:
# Import FastAPI and Pydantic from fastapi import FastAPI, Query from pydantic import BaseModel # Create an app instance app = FastAPI() # Define a data model for a post class Post(BaseModel): id: int title: str content: str author: str date: str tags: list[str] # Define a sample list of posts posts = [ Post(id=1, title="Hello, world!", content="This is my first post.", author="Alice", date="2021-01-01", tags=["intro", "welcome"]), Post(id=2, title="FastAPI rocks!", content="This is my second post.", author="Bob", date="2021-01-02", tags=["fastapi", "web"]), Post(id=3, title="Goodbye, world!", content="This is my last post.", author="Charlie", date="2021-01-03", tags=["outro", "farewell"]) ] # Define an endpoint for getting a list of posts by a specific author, using a query parameter @app.get("/posts") def get_posts(author: str = Query(...)): # Validate and filter the posts filtered_posts = [] for post in posts: if post.author == author: filtered_posts.append(post) if filtered_posts: return {"posts": filtered_posts} else: return {"error": "No posts found by this author."}
This code defines a query parameter named author
, which is annotated with the Query
class. The Query
class validates and parses the query parameter, using the arguments that are passed to it. In this case, the argument is ...
, which means that the parameter is required. The function returns a JSON response with the list of posts, or an error message if no posts are found by this author.
You can test this endpoint by visiting http://localhost:8000/posts?author=Alice or http://localhost:8000/posts?author=David in your browser, or by using the Swagger UI or ReDoc interfaces. You can also view the OpenAPI schema for this endpoint by visiting http://localhost:8000/openapi.json in your browser.
As you can see, FastAPI makes it easy to use path and query parameters in your API endpoints, using Python type annotations and Pydantic classes. You can also use other types of parameters, such as header, cookie, body, or form parameters, using the corresponding classes from FastAPI. For more information, you can check the FastAPI documentation on parameters.
4.2. Validating and Parsing Request Data
Another important aspect of building web APIs is validating and parsing the request data that your API receives from the clients. Request data can be of different types, such as JSON, form data, files, or headers. For example, you might want to receive a JSON object with the details of a new post, or a file with an image of a post.
Validating and parsing request data is essential for ensuring the quality and security of your API. You want to make sure that the request data is valid, complete, and consistent with your data models and schemas. You also want to avoid any malicious or erroneous data that could compromise your API or your database.
FastAPI makes it easy to validate and parse request data, using Pydantic models and Python type annotations. Pydantic is a library that allows you to define data models and schemas using Python classes and type hints. Pydantic automatically validates and parses the data, checking for types, constraints, and defaults. FastAPI integrates seamlessly with Pydantic, allowing you to use Pydantic models as arguments for your API endpoints. For example, the following code defines an endpoint for creating a new post, using a Pydantic model as a request body:
# Import FastAPI and Pydantic from fastapi import FastAPI, Body from pydantic import BaseModel # Create an app instance app = FastAPI() # Define a data model for a post class Post(BaseModel): id: int title: str content: str author: str date: str tags: list[str] # Define a sample list of posts posts = [ Post(id=1, title="Hello, world!", content="This is my first post.", author="Alice", date="2021-01-01", tags=["intro", "welcome"]), Post(id=2, title="FastAPI rocks!", content="This is my second post.", author="Bob", date="2021-01-02", tags=["fastapi", "web"]), Post(id=3, title="Goodbye, world!", content="This is my last post.", author="Charlie", date="2021-01-03", tags=["outro", "farewell"]) ] # Define an endpoint for creating a new post, using a Pydantic model as a request body @app.post("/posts") def create_post(post: Post = Body(...)): # Validate and append the post posts.append(post) return {"message": "Post created successfully."}
This code defines a Pydantic model for a post, and uses it as an argument for the endpoint. The argument is annotated with the Body
class, which indicates that the data is expected as a request body. The Body
class also validates and parses the data, using the arguments that are passed to it. In this case, the argument is ...
, which means that the data is required. The function appends the post to the list of posts, and returns a JSON response with a success message.
You can test this endpoint by sending a POST request to http://localhost:8000/posts with a JSON object as the request body, or by using the Swagger UI or ReDoc interfaces. You can also view the OpenAPI schema for this endpoint by visiting http://localhost:8000/openapi.json in your browser.
As you can see, FastAPI makes it easy to validate and parse request data, using Pydantic models and Python type annotations. You can also use other types of request data, such as form data, files, or headers, using the corresponding classes from FastAPI. For more information, you can check the FastAPI documentation on request forms and files.
4.3. Handling Errors and Exceptions
When you build a web API, you need to anticipate and handle possible errors and exceptions that might occur during the execution of your code. Errors and exceptions are abnormal events that disrupt the normal flow of your program and cause it to fail or behave unexpectedly. For example, you might encounter errors and exceptions when:
- The client sends invalid or incomplete data to your API.
- The server cannot connect to the database or another service.
- The server runs out of memory or disk space.
- The server encounters an unexpected bug or logic error in your code.
Handling errors and exceptions properly is important for several reasons, such as:
- Improving the reliability and robustness of your API.
- Providing meaningful and helpful feedback to the client and the developer.
- Preventing security breaches and data leaks.
- Debugging and troubleshooting your code more easily.
In this section, you will learn some best practices and tips for handling errors and exceptions in your FastAPI app. You will learn how to:
- Use built-in and custom exceptions to raise and catch errors.
- Use HTTP status codes to indicate the outcome of the request.
- Use error handlers to customize the error response.
- Use logging to record and monitor the errors.
Let’s start by learning how to use exceptions in FastAPI.
4.4. Adding Authentication and Authorization
Authentication and authorization are two important aspects of web API security. Authentication is the process of verifying the identity of the client who is making the request to your API. Authorization is the process of granting or denying access to the resources and operations of your API based on the client’s identity and permissions.
There are many ways to implement authentication and authorization in your web API, such as using JSON Web Tokens (JWT), OAuth2, Basic Auth, API keys, and more. In this section, you will learn how to use FastAPI’s built-in support for OAuth2 with JWT to add authentication and authorization to your web API. You will learn how to:
- Create a user model and a database for storing user information.
- Create an endpoint for registering new users and hashing their passwords.
- Create an endpoint for logging in existing users and generating JWT tokens.
- Create a dependency function for verifying and decoding the JWT tokens.
- Create a permission-based system for restricting access to certain endpoints.
Let’s start by creating a user model and a database for storing user information.
5. Testing and Documenting the API
Testing and documenting your web API are essential steps for ensuring its quality, functionality, and usability. Testing your web API helps you to find and fix bugs, errors, and vulnerabilities in your code, as well as to verify that your API meets the requirements and expectations of your users. Documenting your web API helps you to describe and explain the features, parameters, and responses of your API, as well as to provide examples and tutorials for your users.
In this section, you will learn some best practices and tips for testing and documenting your web API with FastAPI. You will learn how to:
- Write unit and integration tests for your API endpoints using pytest and TestClient.
- Generate interactive documentation for your API using Swagger UI and ReDoc.
- Customize and enhance your documentation using additional responses, callbacks, and custom responses.
Let’s start by learning how to write unit and integration tests for your API endpoints.
5.1. Writing Unit and Integration Tests
Unit and integration tests are two types of automated tests that you can use to verify the functionality and correctness of your web API. Unit tests are tests that check the behavior of a single unit of code, such as a function, a class, or an endpoint. Integration tests are tests that check the interaction and integration of multiple units of code, such as the communication between your API and the database, or the compatibility of your API with different clients.
Writing unit and integration tests for your web API can help you to:
- Detect and prevent bugs and errors in your code.
- Ensure that your code meets the specifications and requirements of your API.
- Refactor and improve your code without breaking its functionality.
- Measure and improve the quality and coverage of your code.
In this section, you will learn how to write unit and integration tests for your web API using pytest and TestClient. Pytest is a popular and powerful testing framework for Python that allows you to write and run tests easily and efficiently. TestClient is a tool that comes with FastAPI that allows you to simulate requests to your API and check the responses.
Let’s start by creating a test file and importing the required modules.
5.2. Generating Interactive Documentation
One of the great features of FastAPI is that it automatically generates interactive documentation for your web API based on the OpenAPI specification. The OpenAPI specification is a standard and language-agnostic way of describing and documenting RESTful APIs. FastAPI uses the information that you provide in your code, such as the parameters, responses, and dependencies, to create the OpenAPI schema for your API. You can access the OpenAPI schema as a JSON file at http://localhost:8000/openapi.json.
But the OpenAPI schema is not very user-friendly or readable by itself. That’s why FastAPI also provides two interactive documentation interfaces that use the OpenAPI schema to display and test your API endpoints. These interfaces are:
- Swagger UI: A web-based interface that allows you to explore, execute, and debug your API endpoints. You can access Swagger UI at http://localhost:8000/docs.
- ReDoc: A web-based interface that allows you to view and navigate your API documentation in a more structured and organized way. You can access ReDoc at http://localhost:8000/redoc.
Both interfaces are automatically updated whenever you make changes to your code, so you don’t need to worry about keeping your documentation in sync with your code. You can also customize and enhance your documentation using additional responses, callbacks, and custom responses, as you will learn in the next section.
Let’s take a look at how to use Swagger UI and ReDoc to interact with your web API.
6. Conclusion
In this blog, you have learned some best practices and tips for building better web APIs with FastAPI, a modern and fast web framework for Python. You have learned how to:
- Set up the project and install the required dependencies.
- Design the API schema using Python type annotations and Pydantic models.
- Implement the API endpoints using FastAPI decorators and dependency injection.
- Use path and query parameters to customize the API requests and responses.
- Validate and parse request data using Pydantic validators and schemas.
- Handle errors and exceptions using built-in and custom exceptions, HTTP status codes, error handlers, and logging.
- Add authentication and authorization using OAuth2 with JWT, user models, and permission-based systems.
- Test and document the API using pytest, TestClient, Swagger UI, and ReDoc.
By following these best practices and tips, you can create web APIs that are fast, reliable, secure, and easy to use. You can also improve your coding skills and learn new technologies and tools that can help you with your web development projects.
We hope that you have enjoyed this blog and found it useful and informative. If you have any questions, feedback, or suggestions, please feel free to leave a comment below. Thank you for reading and happy coding!