Flask is a lightweight web framework for Python that simplifies the development of web applications and APIs. It provides an intuitive way to build APIs, making it an excellent choice if you're looking to create scalable and maintainable web services.
In this tutorial, you'll build a blog API using Flask and SQLite as the database backend. We'll cover essential Flask concepts while implementing CRUD operations: creating, reading, updating, and deleting blog posts.
Prerequisites
Before getting started, ensure you have:
- Python installed on your system (preferably the latest version, Python 3.13 or higher)
- A basic understanding of Python and web development
Step 1 — Setting up the Flask project
In this section, you'll create the directory structure and install the necessary dependencies for the Flask API project.
First, create a new directory for your project and navigate into it:
Next, create a virtual environment and activate it:
Install Flask and other dependencies:
Here’s a breakdown of these packages:
Flask– The core web framework.Flask-SQLAlchemy– A powerful database toolkit for managing database interactions.Flask-Smorest– A structured way to build REST APIs with OpenAPI support.Marshmallow– A library for serializing, deserializing, and validating data.
Create a new file called app.py and set up a basic Flask application:
This script defines a create_app() function that initializes and returns a Flask application. It registers a simple route at /, which responds with a JSON message. When executed directly, the script runs the application in debug mode.
Now, run the application:
You should see output similar to:
This means your Flask server is running. Open http://127.0.0.1:5000/ in a browser, or alternatively, use curl to test the API:
The output will be:
At this point, your Flask 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 Flask application set up, the next step is integrating a database to store and manage blog posts. Flask-SQLAlchemy simplifies database interactions, allowing you to define models and perform queries with Python instead of raw SQL. This section will guide you through configuring SQLite as the database backend.
To begin, update your app.py file to include SQLAlchemy configuration:
The updated code initializes SQLAlchemy, configures the database connection, and binds it to the Flask app. It checks for a DATABASE_URL environment variable, defaulting to SQLite if not found. db.init_app(app) sets up the database, and db.create_all() ensures tables exist within an application context. These changes integrate the database, preparing it for future operations.
If everything is set up correctly, the Flask server will start upon saving, and the database file blog.db will be created inside the instance/ directory.
You can confirm its existence and directory structure by listing the files:
The database is now integrated with your Flask application, but it's currently empty.
Step 3 — Creating the post model
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.
First, create a dedicated models directory to keep your code organized:
Now, create the Post model in the models/post.py file:
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 __repr__ method provides a readable string representation of the Post object, while the to_dict method converts a Post instance to a dictionary for easy JSON serialization.
Now, update your app.py file to use this model:
If everything is set up correctly, the Flask server will auto-restart and the posts table will be created in the database. You won't notice any visual changes yet, but the foundation for storing blog posts is now in place.
You can verify that the table was created by using the SQLite command-line tool:
With the Post model defined, you now have a structured way to interact with the database. This model will serve as the foundation for the CRUD operations you'll implement in the next steps.
The table structure includes all necessary fields for managing blog posts, and the UUIDs ensure each post has a unique identifier across the system.
Step 4 — Creating the post schema with Marshmallow
With our database model in place, you now need a way to validate incoming request data and serialize database objects to JSON responses. Marshmallow is a powerful library that provides this functionality, ensuring data consistency and proper formatting across our API.
First, create a schemas directory to organize your validation schemas:
Next, define the Post schema in the schemas/post.py file:
The PostSchema class defines the expected structure for blog post objects, enforcing validation rules to ensure data consistency:
id: A string field that's only included in responses, not in requeststitle: A required string between 3-100 characterscontent: A required string with a minimum length of 10 characterspublished: A boolean that defaults to True if not providedcreated_atandupdated_at: DateTime fields only included in responses
The PostQuerySchema class is used for filtering posts when querying the database. It includes an optional published field that allows users to filter posts based on their publication status.
Now, update app.py to integrate Flask-Smorest for our API documentation:
The updated code configures Flask-Smorest, which provides:
- Automatic request validation based on our schemas
- Serialization of response data
- Interactive API documentation through Swagger UI
- OpenAPI specification generation
When you save, the server will restart. You can now access the API documentation at http://127.0.0.1:5000/api/docs/swagger, although it won't show any endpoints yet since we haven't defined them.
The Marshmallow schemas you’ve created provide several advantages. They ensure that only properly formatted data is accepted through validation while offering clear error messages to help API users understand issues.
Automatic type conversion simplifies data transformations, and the schemas also contribute to generating API documentation seamlessly.
In the next step, you'll create the API resources and endpoints to handle CRUD operations for blog posts, starting with the create endpoint.
Step 5 — Implementing the POST endpoint for creating blog posts
Now that you have your database model and validation schema, it's time to create your first API endpoint for creating new blog posts. You'll use Flask-Smorest to handle request validation and response serialization.
First, create a directory for our API resources:
Now, create the post resource in the resources/post.py file with your first endpoint for creating posts:
This code defines a blueprint that groups blog post-related routes under "posts".
The PostList class, which inherits from MethodView, defines a POST endpoint for creating posts.
Incoming request data is validated using the PostSchema, ensuring the correct structure. The validated data is then used to create a new Post instance, which is added to the database.
If an error occurs during the commit process, the transaction is rolled back to prevent data corruption.
Next, update the app.py file to register your blueprint:
This ensures that the post-related routes are included in your Flask API.
When you save the file, the server should restart automatically. If it doesn’t, manually restart it to apply the changes:
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:
You can also explore the API documentation at http://127.0.0.1:5000/api/docs/swagger in the browser:
The interface displays the "Blog API v1" title at the top, with an expandable "posts" section showing available operations, including the POST endpoint at /api/post.
When expanded, the POST endpoint details the required request body format, expected response codes, and example request/response pairs:
Now test the validation by trying to create a post with an invalid title (too short):
You should receive a validation error:
This demonstrates how Marshmallow validates incoming data before reaching our endpoint function, ensuring data integrity.
The POST endpoint you've created follows REST principles:
- Using the appropriate HTTP method (POST) for resource creation
- Returning a 201 status code to indicate successful creation
- Including the complete resource in the response
- Providing clear validation errors when input is invalid
In the next step, you'll implement the endpoint for retrieving blog posts.
Step 6 — Implementing the GET endpoint for retrieving blog posts
Now that you can create blog posts, the next step is to implement functionality to retrieve them. In this section, you'll create an endpoint to list all posts with optional filtering by publication status.
First, update the resources/post.py file to add a GET method to the existing PostList class:
This method allows filtering posts based on query parameters and returns them in a structured format.
The @blp.response(200, PostSchema(many=True)) decorator ensures the response follows the PostSchema format, while @blp.arguments(PostQuerySchema, location="query") processes optional query parameters.
Inside the method, the database query starts by selecting all posts. If a published status filter is provided, the query applies a filter to return only matching posts.
Additionally, posts are sorted by creation date in descending order, ensuring the most recent posts appear first.
The method then returns the filtered and sorted posts as JSON. The existing POST method remains unchanged, continuing to handle blog post creation.
With that, save the new changes.
Now use curl to retrieve all posts:
You should see all the posts you've created so far:
Now let's create another post to see how filtering works:
After creating the draft post, 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:
You can also check the Swagger UI documentation at http://127.0.0.1:5000/api/docs/swagger, which now shows the GET endpoint with its parameters:
Now that you can list and filter all posts by publication status, you will implement the endpoint for retrieving a single post by its ID.
Add the following code to the resources/post.py file:
This code adds a new PostResource class with a GET method that:
- Takes a post ID as a URL parameter
- Uses SQLAlchemy's
get_or_404method to retrieve the post or return a 404 error - Returns the post serialized according to
PostSchema
Test this endpoint by retrieving one of your posts by its ID (replace with an actual ID from your previous GET response):
You should receive the specific post:
Try requesting a non-existent post to see the 404 error handling:
You've now successfully implemented two GET endpoints for your blog API.
In the next step, you'll implement an endpoint for updating the blog posts.
Step 7 — Implementing the PUT endpoint for updating blog posts
Now that you have the functionality to create and retrieve blog posts, the next step is to implement the ability to update existing posts. This will allow users to modify post content, title, or publication status after creation.
Update the PostResource class in your resources/post.py file to add a PUT method:
The put method in PostResource accepts validated post data along with the post ID from the URL.
It first retrieves the corresponding post using get_or_404, ensuring a 404 Not Found response if the post does not exist.
Once the post is found, it updates the title and content based on the request payload, and if the published status is included, it is also updated.
The changes are then committed to the database, with built-in error handling to rollback the transaction in case of an issue.
Finally, the updated post is returned, serialized using PostSchema, ensuring consistency in the API response structure.
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 the publication status of a post:
The response should show the post with its publication status changed to false:
To verify that validation is still working, try updating a post with an invalid title (too short):
You should receive a validation error:
Your API documentation is also automatically updated to include the new PUT endpoint. You can check this by visiting the Swagger UI at http://127.0.0.1:5000/api/docs/swagger:
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 8 — Implementing the DELETE endpoint for removing blog posts
To complete the CRUD functionality of your blog API, the final step is to implement the DELETE endpoint for removing blog posts from the database. This will allow users to delete posts they no longer need permanently.
Update the PostResource class in your resources/post.py file to add a DELETE method:
This delete() method lets users permanently remove a blog post from the database.
It takes the post ID from the URL and retrieves the corresponding post using get_or_404, ensuring that a 404 Not Found response is returned if the post does not exist.
Once the post is found, it is deleted from the database, and changes are committed. A rollback is triggered to maintain database integrity if an error occurs during the process.
The method then returns a 204 No Content status code, signifying that the deletion was successful and that no further response body is needed.
Now, test the DELETE endpoint. First, make sure we 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:
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.
Your API documentation is automatically updated to include the new DELETE endpoint. You can check this by visiting the Swagger UI at http://127.0.0.1:5000/api/docs/swagger.
Final thoughts
Congratulations! You've built a fully functional RESTful API for a blog using Flask, supporting all CRUD operations: creating, retrieving, updating, and deleting posts.
To take your Flask API skills further, explore the official documentation for Flask, Flask-SQLAlchemy, and Flask-Smorest.
These resources offer insights and best practices to help you build more advanced and scalable applications.