FastAPI is a modern, high-performance web framework for building APIs with Python. It provides automatic data validation, serialization, and interactive documentation, making it an excellent choice for developing reliable and maintainable web services.
In this tutorial, you'll build a task management API using FastAPI and SQLite as the database backend. We'll leverage SQLModel for database interactions and Pydantic for data validation, implementing CRUD operations: creating, reading, updating, and deleting tasks.
Prerequisites
Before getting started, ensure you have:
- Python installed on your system (preferably Python 3.13 or higher)
- A basic understanding of Python and web development concepts
Step 1 — Setting up the FastAPI project
In this section, you'll create the directory structure and install the necessary dependencies for the FastAPI project.
First, create a new directory for your project and navigate into it:
Next, create a virtual environment and activate it:
Install FastAPI and other dependencies:
Here's a breakdown of these packages:
FastAPI– The web framework for building APIs.Uvicorn– An ASGI server required to run FastAPI applications.SQLModel– A library that combines SQLAlchemy and Pydantic for database interactions.python-dotenv– A package for working with environment variables.
Create a new file called main.py and set up a basic FastAPI application:
This script initializes a FastAPI application with metadata for the automatic documentation. It registers a simple route at /, which responds with a JSON message.
When executed directly, the script runs the application using Uvicorn with the reload option enabled for development.
Now, run the application:
You should see output similar to:
This means your FastAPI server is running. Open http://127.0.0.1:8000/ in a browser to see the welcome message, or use curl to test the API:
The output will be:
FastAPI also provides automatic interactive documentation. Visit http://127.0.0.1:8000/docs to explore the Swagger UI documentation for your API:
At this point, your FastAPI application is set up and running. In the next step, you'll configure the database using SQLModel.
Step 2 — Configuring the database with SQLModel
With the FastAPI application set up, the next step is to integrate a database to store and manage tasks. SQLModel simplifies database interactions by combining SQLAlchemy's ORM capabilities with Pydantic's validation features.
First, let's organize our project structure. Create the following directories:
This command creates the necessary directories for the project. The -p flag ensures parent directories are created as needed and prevents errors if directories already exist.
Following that, create the following files in the newly created directories:
This command creates empty Python files that mark each directory as a package, enabling clean imports between modules and establishing the application's namespace hierarchy.
These files can later contain initialization code, shared utilities, or package metadata.
Next, create a file for database configuration:
Now, update the app/database/config.py file with the following code:
This code sets up the database connection using SQLModel. It defines two key functions:
create_db_and_tables(): Creates all the tables defined in our models.get_session(): A dependency function that provides a database session for our endpoints.
The echo=True parameter enables SQL query logging for development, and connect_args={"check_same_thread": False} allows SQLite to work with FastAPI's asynchronous nature.
Now, let's create a task model using SQLModel. Create a new file:
Update the app/models/task.py file with the following code:
The TaskBase class serves as the foundation, containing shared fields like title, description, priority, and completed status. The Task class extends TaskBase and represents the database model, adding a unique id field generated using UUID, along with created_at and updated_at timestamps.
Three additional models are defined to handle different operations. The TaskCreate class validates data when creating new tasks, while the TaskRead class structures the data when retrieving tasks from the database.
Lastly, the TaskUpdate class allows partial updates, where fields can be optionally modified.
Using inheritance and type hinting enables automatic validation, serialization, and documentation.
Following that, update the main.py file to include the database initialization:
You've added an event handler using @app.on_event("startup") that calls create_db_and_tables() when the application starts, ensuring that all necessary tables exist.
When you save the file, the application server should restart automatically. If it doesn’t, you can manually restart it by running the following command:
You'll see additional SQL output in the console showing that the tables are being created:
The database is now integrated with your FastAPI application. You will see a tasks.db file in your project directory.
Now that you have set up the database, it's time to build the functionality that interacts with it. In the next step, you'll implement the endpoint for creating tasks.
Step 3 — Implementing the POST endpoint for creating tasks
Now that your models and database configuration are set up, let's implement the first API endpoint for creating new tasks. You'll organize your code by creating a separate module for all task-related routes.
To organize your route handlers, first create the necessary directory and files with the following command:
Now, update the app/routes/tasks.py file with the following code:
This code defines a POST endpoint for creating tasks. When a request is sent, FastAPI validates the input using the TaskCreate model.
The function then converts the request data into a database-compatible Task model and saves it to the database using SQLModel.
Once committed, the newly created task is returned as a response. This structured approach ensures efficient task management while maintaining data integrity.
Next, update the main.py file to include the task routes:
With the task routes included, save your file and use curl to create a new task.
You should receive a response like this:
You can also test the validation by trying to create a task with an invalid priority (outside the 1-5 range):
You'll receive a validation error:
This shows that Pydantic's validation is working automatically to ensure data consistency.
Now, visit http://127.0.0.1:8000/docs in your browser to see the interactive Swagger UI, which includes information about your task creation endpoint.
Now that you can create tasks, it's time to add functionality to retrieve them.
Step 4 — Implementing GET endpoints for retrieving tasks
Now that you can create tasks, let's implement endpoints to retrieve them. We'll start with an endpoint for listing all tasks with optional filtering.
Update the app/routes/tasks.py file to add the first GET endpoint:
The highlighted code defines a GET endpoint that retrieves a list of tasks with optional filtering and pagination. It is registered under /api/tasks/ and returns a list of tasks formatted using the TaskRead model.
To enable pagination, the offset and limit parameters allow clients to skip a specified number of tasks and limit the number of results returned.
Additionally, the completed parameter enables filtering tasks by their completion status. The query is adjusted to return only completed or incomplete tasks if provided.
The endpoint constructs a database query using select(Task), applies filters if necessary, and retrieves the results using session.exec().
The final list of tasks is then returned, ensuring efficient task retrieval with flexibility for filtering and pagination.
Now, test the endpoint for retrieving all tasks. First, let's create a few tasks to have some data to work with:
Next, test retrieving all tasks:
You should see a response with all your tasks:
You can also test filtering for completed tasks:
This should return only the tasks marked as completed:
Now that you have confirmed that the list endpoint works, you will implement the endpoint to retrieve a specific task by ID.
Add the following code to app/routes/tasks.py:
Test retrieving a specific task by its ID. Make sure to use an ID from your previous responses:
You should receive the specific task:
Try requesting a non-existent task to see the 404 error handling:
The interactive documentation at http://127.0.0.1:8000/docs is also automatically updated to include these new endpoints, complete with all the parameters and response models:
Now that you have implemented task retrieval, the next step is to provide users with the ability to update existing tasks efficiently.
Step 5 — Implementing the PUT endpoint for updating tasks
Now that you can create and retrieve tasks, the next step is to allow updates. You'll implement a PUT endpoint for performing full updates, meaning that all task fields must be provided in the request body. This ensures that the entire task is replaced with the new data, maintaining consistency across updates.
Update the app/routes/tasks.py file to add the PUT endpoint:
Test the PUT endpoint for full updates. This requires providing all task fields, even if they remain unchanged(Remember to replace with your actual task ID):
You should see a response with all fields updated:
Notice that the updated_at timestamp has been automatically updated to reflect the modification time.
Now that you've confirmed the PUT endpoint works for full updates, check the interactive documentation at http://127.0.0.1:8000/docs:
The documentation now includes the update endpoint, providing a clear interface for testing and verifying task updates directly from the browser.
Step 6 — Implementing the DELETE endpoint for removing tasks
To complete the CRUD functionality of your task API, the final step is to implement a DELETE endpoint. This will allow users to remove tasks from the database.
Update the app/routes/tasks.py file to add the delete endpoint:
This DELETE endpoint allows users to remove tasks by providing a valid task ID. When called, it first retrieves the task from the database. If the task doesn't exist, it raises a 404 Not Found error. Otherwise, it deletes the task and commits the changes. The endpoint returns a 204 No Content status code, indicating that the operation was successful without requiring a response body.
Now, test this endpoint by deleting one of your tasks(replace with your actual task ID):
The -v (verbose) flag will show the response headers, including the status code:
The HTTP status code 204 confirms that the task was successfully deleted. You can verify this by trying to retrieve the deleted task:
You should receive a 404 Not Found response:
With the DELETE endpoint in place, your task management API now fully supports Create, Read, Update, and Delete (CRUD) operations.
Final thoughts
This article has guided you through building a production-ready RESTful API using FastAPI while ensuring type safety and data validation throughout your application.
To deepen your understanding of FastAPI and enhance your skills, consider exploring the official documentation for FastAPI, SQLModel, and Pydantic. These resources provide valuable insights, best practices, and advanced techniques to help you build even more robust and scalable applications.
Thanks for reading and happy coding!