Litestar is a high-performance, asynchronous API framework for Python designed with type safety, a strong developer experience, and modern features.
It provides a simple approach to building web applications and APIs, offering powerful dependency injection, automatic schema validation, and comprehensive performance optimizations.
In this tutorial, you'll build a blog API using Litestar and SQLAlchemy as the database ORM. We'll cover essential Litestar concepts while implementing CRUD operations like creating, reading, updating, and deleting blog posts.
Prerequisites
Before getting started, ensure you have:
- Python installed on your system (preferably Python 3.13 or higher)
- A basic understanding of Python and async programming concepts
Step 1 — Setting up the Litestar project
In this section, you'll create the directory structure and install the necessary dependencies for the Litestar API project.
First, create a new directory for your project and navigate into it:
Next, create a virtual environment and activate it:
Install Litestar and other dependencies:
Here's a breakdown of these packages:
Litestar[standard]: The core web framework for building APIs, with the standard extras that include Uvicorn.SQLAlchemy: A powerful database toolkit for managing database interactions.aiosqlite: It provides asynchronous access to SQLite databases, which is what allows Litestar to work with SQLite in an async fashion.greenlet: Required for SQLAlchemy's async support
Create a new file called app.py and set up a basic Litestar application:
This script defines a simple function decorated with @get("/"), which responds to GET requests at the root path with a JSON message.
The app object is a Litestar application instance that includes our route handler in the list of route handlers passed as the first argument.
Now, run the application using the Litestar CLI:
You should see output similar to:
This means your Litestar server is running. Open http://127.0.0.1:8000/ in a browser, or alternatively, use curl to test the API:
The output will be:
For optimal development experience, run the server in "reload mode," which will automatically restart when you change your code.
Add the --reload flag like this:
At this point, your Litestar API is set up and running. In the next step, you'll integrate a database using SQLAlchemy.
Step 2 — Configuring the database with SQLAlchemy
With the Litestar application set up, the next step is integrating a database to store and manage blog posts.
Litestar works smoothly with SQLAlchemy to provide robust database interactions. This section will guide you through configuring SQLite as the database backend.
First, create a directory structure for your project components:
Now, create a database configuration file at src/db/config.py:
The configuration file sets up an async SQLAlchemy engine using the database URL from environment variables, creates a session factory, and defines a Base class for your models. The get_db_session function will be a dependency that provides database sessions to your route handlers.
Now, create __init__.py files to make each directory a proper Python package:
Next, update your app.py file to incorporate the database configuration:
The updated code adds a db_lifespan context manager that handles database setup and teardown. It creates database tables when the application starts and properly disposes of the database engine when it shuts down.
It also registers the get_db_session function as a dependency, making it available to all route handlers that need database access.
If everything is set up correctly, the Litestar server will auto-restart, and you should see output indicating that the database engine initialized successfully:
You can confirm the database file's existence by checking your file system:
You should see the blog.db file in your directory.
The database is now integrated with your Litestar application, but it's currently empty. Next, you'll create a data model for blog posts.
Step 3 — Defining the post model and schemas
With the database configured, it's time to define a data model for blog posts. In this step, you'll create a SQLAlchemy model that represents the structure of blog posts in the database, complete with proper data types and constraints.
Create a new file at src/models/post.py:
This model defines a Post class with several key attributes:
id: A unique identifier using UUID, stored as a stringtitle: The post title with a maximum length of 100 characterscontent: The main body of the post, stored as textpublished: A boolean flag indicating whether the post is publishedcreated_at: A timestamp indicating when the post was createdupdated_at: A timestamp that updates whenever the post is modified
The model uses SQLAlchemy 2.0-style type hints with the Mapped type for better type safety and IDE integration.
Next, update the src/db/config.py file to import the Post model to ensure it's registered with the Base metadata:
You can verify that the table was created using the SQLite command-line tool:
With the Post model defined, you now have a structured way to interact with the database.
With our database model in place, you need a way to validate incoming request data and serialize database objects to JSON responses. Litestar works well with standard Python dataclasses for request validation and response serialization.
Create a new schema file at src/schemas/post.py:
These dataclasses define the API schemas, handling different aspects of request validation and response formatting. The PostBase schema provides common fields, while PostCreate validates new post requests.
The PostUpdate schema ensures only specified fields are modified, and PostResponse formats database records into API responses.
Litestar leverages these schemas to validate incoming data, generate API documentation, serialize models into JSON, and return clear error messages when validation fails.
Step 4 — Creating blog posts
Now that you have the database model and validation schemas, let's implement the first API endpoint for creating new blog posts. You'll use the controller-based approach for better organization of our route handlers.
Create a new file at src/controllers/post.py:
The PostController class groups blog post-related routes under the /api/posts path. The @post() decorator registers a POST endpoint at the controller's base path.
The create_post method validates incoming data using the PostCreate schema, creates a new Post instance, saves it to the database, and returns the created post using the PostResponse schema.
Now, update your app.py file to include the controller:
This update adds the PostController to the application's route handlers, making the POST endpoint available.
Save your file, and once the changes are detected, the server will restart automatically.
Now you can create a new blog post by sending a POST request to /api/posts. Test it using curl:
You should receive a response like this:
Litestar automatically generates interactive API documentation, which your browser can access at http://127.0.0.1:8000/schema/swagger:
This documentation shows all available endpoints and their expected request and response formats and allows for testing the API directly from the browser.
Step 5 — Retrieving blog posts
Now that you can create blog posts, let's implement functionality to retrieve them. First, we'll add a method to get all posts with optional filtering.
Update your PostController in src/controllers/post.py to add the get_posts endpoint:
The get_posts method allows retrieving blog posts with an optional published parameter, enabling filtering based on their publication status. It constructs an SQLAlchemy query, applying the filter if specified, and sorts the results by creation date, ensuring the newest posts appear first.
Finally, it serializes the retrieved posts using the PostResponse schema before returning them as a structured response.
Now you can test this endpoint. List all posts:
You should see a list of all the posts you've created so far:
Now, create a draft post to test filtering:
Test the filtering functionality by retrieving only published posts:
This should return only the published posts. Similarly, you can retrieve only draft posts:
This should return only the unpublished drafts:
Now, let's add the endpoint to retrieve a specific post by its ID. Update your PostController again:
This method retrieves a blog post using its ID, which is passed as a path parameter. It queries the database for the matching post and returns it in a structured format using the PostResponse schema.
If the requested post does not exist, it raises a NotFoundException, returning a 404 error response.
Test this endpoint by retrieving a specific post by ID (replace with an actual ID from your database):
You should receive the specific post details in the response:
Try requesting a non-existent post to verify error handling:
You should see a 404 response with an appropriate error message.
Take a moment to check the interactive API documentation at http://127.0.0.1:8000/schema/swagger in your browser.
You'll notice it now includes the new GET endpoints you've added. The documentation automatically updates to reflect your API's capabilities, showing each endpoint's parameters, request formats, and response structures.
This interactive documentation makes it easy to understand and test your API, even for users who aren't familiar with the implementation details.
With these two endpoints, you've now implemented the read operations for your API. Users can list all posts, filter posts by publication status, and retrieve specific posts by ID.
Step 6 — Updating blog posts
With the ability to create and retrieve blog posts, the next step is implementing functionality for updating existing posts. This will allow users to modify post content, title, or publication status.
Update your PostController in src/controllers/post.py to add the PUT method:
The update_post method handles PUT requests to update an existing post. It first retrieves the post by ID, raises a 404 error if not found, updates only the fields that were provided in the request, and commits the changes to the database.
The method uses the PostUpdate schema, which has all fields marked as optional. This allows for partial updates—clients can update only the fields they want to change without having to provide values for fields they don't want to modify.
Now, test the endpoint by updating one of the posts you created earlier. Be sure to replace the post ID with an actual ID from your database:
You should receive a response with the updated post:
Notice that while the created_at timestamp remains the same, the updated_at timestamp has been updated to reflect the modification time.
You can also try updating just the publication status of a post:
The response should show the post with its publication status changed to false, while keeping the other fields the same:
The API documentation at http://127.0.0.1:8000/schema/swagger will now show the new PUT endpoint, with information about the request body format and possible responses.
With the implementation of the PUT endpoint, users can now update existing blog posts. In the next step, you'll complete the CRUD functionality by implementing the DELETE endpoint to allow users to remove blog posts from the database.
Step 7 — Deleting blog posts
The final step in completing the CRUD functionality of your blog API is to implement the DELETE endpoint. This will allow users to remove blog posts from the database when they're no longer needed.
Update your PostController in src/controllers/post.py to add the DELETE method:
The delete_post method handles DELETE requests for blog posts. It retrieves the post by ID, raises a 404 error if the post isn't found, and then removes it from the database.
Notice that this endpoint returns None, which Litestar will interpret as a 204 No Content response—a standard HTTP status code indicating that the request was successful but there's no content to return.
Now, test the DELETE endpoint. First, make sure you have a post to delete. You can create a new one specifically for testing:
You should receive a response with the newly created post, including its ID:
Now, delete this post using its ID (replace with the actual ID from your response):
The -v (verbose) flag will show the response headers, including the status code:
The HTTP status code 204 confirms that the post was successfully deleted.
To verify that the post was deleted, try to retrieve it:
You should receive a 404 Not Found response, confirming that the post has been removed from the database:
Check the API documentation at http://127.0.0.1:8000/schema/swagger to see the new DELETE endpoint added to your API:
With that, you have successfully implemented a fully functional blog API using Litestar and SQLAlchemy.
Final thoughts
Congratulations! You've successfully built a fully functional RESTful API for a blog using Litestar and SQLAlchemy, supporting all CRUD operations, including creating, retrieving, updating, and deleting posts.
To enhance your blog API further, consider adding authentication, pagination, search functionality, model relationships, logging, and automated testing. Litestar’s performance, type safety, and developer-friendly design make it an excellent choice for modern Python APIs.
For further learning and advanced features, check out the Litestar documentation.