contact us

Flask is a lightweight Python web framework commonly used to build REST APIs and microservices. Its minimal core and flexible architecture allow developers to design APIs without being constrained by strict project structures.
In this guide, you will build a simple REST API using Flask, including:
By the end of this tutorial, you will understand how to design and structure a production-ready Flask API.
Flask is a lightweight Python web framework used to build APIs and web applications by defining HTTP routes that return JSON or HTML responses.
A framework is a library used by developers to build and maintain reliable and scalable web applications. There are several frameworks available for Python, such as Tornado, Pyramid, and of course, Django (which is often compared with Flask).
Flask is a Python microframework for web development. Despite being built with a small core and considered a very lightweight Web Server Gateway Interface (WSGI), Flask stands out for its easy-to-extend philosophy. It was designed to scale up to complex applications and to support an easy and quick start.
Moreover, another great advantage of Flask is its functionality. Even though it offers suggestions, Flask does not mandatorily require project layouts or other dependencies. Instead, it allows developers to choose the libraries and tools they wish to use and additionally has various extensions available, that are provided by the community.
API is an acronym of Application Programming Interface, which means it is basically how you communicate with an application. A REST (Representational State Transfer) API allows communication between systems using standard HTTP methods and corresponds to an architectural style that aims for stateless communications and separates client and server concerns. It typically exchanges data in JSON format and operates using endpoints that correspond to actions like Create (POST), Read (GET), Update (PUT or PATCH), and Delete (DELETE). REST APIs are widely used for their simplicity, scalability, and compatibility with a broad range of clients and platforms.
The REST API on this exercise will create a fake implementation of CRUD actions over an entity. An API with CRUD allows the Create, Read, Update and Delete operations over the application's elements.
This article will guide you through the first steps to create a REST API using Flask.
In this tutorial, you will build a simple REST API using Flask and Python that includes:
This example demonstrates the key components needed to create a production-ready backend API.
You must have Python installed on the current machine. The code presented will consider Python3. If you want to use Python2 and/or are following this procedure in a Windows machine, please follow the instructions presented in the Flask installation guide.
Let’s start by creating a directory to store the project. In the directory you want to have your project, run the following commands on the shell:
We’ve created the flask_demo directory and moved it inside. Before starting to install dependencies, let’s create a virtual environment by running the following command:
This will create a folder into your project with the name .venv. After that, we need to activate the respective environment by running:
This means we are now considering the venv virtual environment when running any Python code. It might be important to specify the environment you are considering when running it in your IDE.
Make sure you have the environment active before following the next steps. You can check if you are inside the environment by looking to the left side of the console. If there’s the virtual environment name inside parentheses, you’re good to go.
If you want to deactivate the environment, just run the following command:

A typical Flask API development workflow includes:
This structure helps keep backend services organised and easier to maintain.
In the previous example, the API stored data in a Python list. While this is useful for demonstrating CRUD endpoints, it is not suitable for real applications because the data disappears whenever the server restarts.
In practice, APIs store data in a database so it persists between requests and application restarts. In this section, we will use SQLite and SQLAlchemy to build a simple database-backed API.
SQLite is a lightweight database that requires no separate server, making it ideal for tutorials and small applications.
First, install the required dependencies.
Run the following command:
pip install sqlalchemy flask-sqlalchemy
If you are managing dependencies with a requirements.txt file, add:
Flask
sqlalchemy
flask-sqlalchemy
In this setup:
Next, configure Flask to connect to the SQLite database.
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///items.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
Here we define the database connection string:
sqlite:///items.db
This creates a local SQLite database file named items.db in the project directory.
Next, create a model representing the structure of the database table.
class Item(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
price = db.Column(db.Float, nullable=False)
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"price": self.price
}
The Item model defines three fields:
The to_dict() method converts the database object into a JSON-serialisable dictionary.
Before running the API, create the database tables using SQLAlchemy.
@app.route("/items", methods=["GET"])
def get_items():
items = Item.query.all()
return jsonify([item.to_dict() for item in items])
This endpoint retrieves all items stored in the database.
@app.route("/items", methods=["POST"])
def create_item():
data = request.get_json()
if not data or "name" not in data or "price" not in data:
return jsonify({"error": "Name and price are required"}), 400
item = Item(name=data["name"], price=data["price"])
db.session.add(item)
db.session.commit()
return jsonify(item.to_dict()), 201
This endpoint:
@app.route("/items/<int:item_id>", methods=["GET"])
def get_item(item_id):
item = Item.query.get_or_404(item_id)
return jsonify(item.to_dict())
This endpoint retrieves a specific item by its ID.
If the item does not exist, Flask automatically returns a 404 error.
@app.route("/items/<int:item_id>", methods=["PUT"])
def update_item(item_id):
item = Item.query.get_or_404(item_id)
data = request.get_json()
if not data:
return jsonify({"error": "Request body is required"}), 400
item.name = data.get("name", item.name)
item.price = data.get("price", item.price)
db.session.commit()
return jsonify(item.to_dict())
This endpoint updates an existing item.
Only the provided fields are modified.
@app.route("/items/<int:item_id>", methods=["DELETE"])
def delete_item(item_id):
item = Item.query.get_or_404(item_id)
db.session.delete(item)
db.session.commit()
return jsonify({"message": "Item deleted successfully"})
This endpoint removes the specified item from the database.
Start the Flask development server:
python app.py
Your API will now be available locally.
Request:
{
"name": "Keyboard",
"price": 49.99
}
Response:
{
"id": 1,
"name": "Keyboard",
"price": 49.99
}
Response:
[
{
"id": 1,
"name": "Keyboard",
"price": 49.99
}
]
In-memory lists are useful for simple demonstrations but have several limitations:
Using a database such as SQLite allows the API to store and retrieve persistent data, making the application behave more like a real backend service.
For production systems, developers typically use databases such as PostgreSQL, MySQL, or MongoDB, but the overall API structure remains the same.
For small applications or tutorials, it is common to keep the entire API in just a few files. This approach keeps the setup straightforward and easy to understand.
A typical beginner Flask API structure might look like this:
flask-api/
├── app.py
├── requirements.txt
├── models.py
├── routes.py
├── schemas.py
└── items.db
In this structure:
This layout is easy to follow and works well for small APIs, prototypes, and learning projects.
However, as the application grows, this structure can become difficult to maintain.
To make the structure easier to understand, here is how each file might be used.
app.pyThis file creates the Flask application, configures the database, and registers the routes.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_smorest import Api
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///items.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["API_TITLE"] = "Flask API Demo"
app.config["API_VERSION"] = "v1"
app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/docs"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
db.init_app(app)
api = Api(app)
from routes import blp
api.register_blueprint(blp)
with app.app_context():
db.create_all()
return app
app = create_app()
if __name__ == "__main__":
app.run(debug=True)
models.pyThis file defines the database model.
from app import db
class Item(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
price = db.Column(db.Float, nullable=False)
routes.pyThis file contains the API endpoints.
from flask.views import MethodView
from flask_smorest import Blueprint
from models import Item
from schemas import ItemSchema
from app import db
blp = Blueprint(
"items",
"items",
url_prefix="/items",
description="Operations on items"
)
@blp.route("/")
class ItemsResource(MethodView):
@blp.response(200, ItemSchema(many=True))
def get(self):
return Item.query.all()
@blp.arguments(ItemSchema)
@blp.response(201, ItemSchema)
def post(self, new_item):
item = Item(**new_item)
db.session.add(item)
db.session.commit()
return item
Separating the application into files provides several benefits:
For example, if you later add authentication, users, or orders, you can expand the structure without rewriting the whole project.
Larger applications benefit from organising the code into packages. This separates different parts of the system, making the project easier to scale and maintain.
A more production-ready Flask API structure might look like this:
For larger projects, it is common to organise the code into folders rather than individual top-level files.
A more scalable structure might look like this:
flask-api/
├── app/
│ ├── __init__.py
│ ├── models/
│ │ └── item.py
│ ├── routes/
│ │ └── items.py
│ └── schemas/
│ └── item.py
├── requirements.txt
└── run.py
In this architecture:
This modular approach makes it easier to:
Best Practice
When building a Flask API, it is a good idea to start with a simple structure and expand it only when the application grows. For small projects, a few clearly named files are often enough. For larger APIs, moving to a package-based layout makes maintenance much easier.
A clean project structure helps other developers understand the code quickly and makes the application easier to extend in the future.
Before we present other Flask strengths, let’s talk about blueprints. A blueprint is an object very similar to a Flask application object, but instead of creating a new one, it allows the extension of the current application. This might be useful if you want to create multiple versions of an API or simply divide services within the same application.
We will use this class type to present different use case scenarios of a Flask application. Let’s convert the code above to be inserted into a blueprint and loaded into the main application.
Create a new folder named blueprints to start inserting blueprint modules as we progress in the blog post. Inside it, create a folder named basic_endpoints and then a file named __init__.py:
# blueprints/basic_endpoints/__init__.py
from flask import Blueprint, jsonify
basic_bp = Blueprint('basic_bp', __name__)
@basic_bp.route('/')
def hello():
return jsonify({'message': 'Hello from a Blueprint!'})
Now the main.py file just needs to load the created blueprint and register it to the application object:
# main.py
from flask import Flask
from blueprints.basic_endpoints import basic_bp
app = Flask(__name__)
app.register_blueprint(basic_bp)
if __name__ == '__main__':
app.run(debug=True)
Now you should have exactly the same endpoints but using the structure provided by Blueprints. This will make your application easier to manage and scale as it grows.
.webp)
When building APIs, it is important to validate incoming requests and return meaningful error responses when something goes wrong. Without validation, an API might accept incomplete or invalid data, which can lead to inconsistent records in the database or unexpected application behaviour.
A well-designed API should:
This improves reliability and makes the API easier for other developers to use.
When a client sends data to the API, the server should verify that the required fields are present and correctly formatted.
For example, when creating a new item, the request should include both a name and a price.
Here is a simple validation example:
@app.route("/items", methods=["POST"])
def create_item():
data = request.get_json()
if not data:
return jsonify({"error": "Request body must be JSON"}), 400
if "name" not in data or "price" not in data:
return jsonify({"error": "Both 'name' and 'price' fields are required"}), 400
item = Item(name=data["name"], price=data["price"])
db.session.add(item)
db.session.commit()
return jsonify(item.to_dict()), 201
In this example, the API checks whether:
If validation fails, the API returns a 400 Bad Request response.
HTTP status codes help clients understand whether a request succeeded or failed.
Some common codes used in REST APIs include:
Using the correct status code helps API consumers handle responses properly.
When a client requests a resource that does not exist, the API should return a 404 error.
Flask-SQLAlchemy provides a convenient helper for this:
item = Item.query.get_or_404(item_id)
If the item does not exist, Flask automatically returns a response like:
{
"message": "404 Not Found: The requested URL was not found on the server."
}
This prevents the API from returning empty or misleading responses.
In larger applications, it is useful to define global error handlers that return consistent responses for common errors.
For example:
@app.errorhandler(400)
def bad_request(error):
return jsonify({
"error": "Bad Request",
"message": "The server could not process the request."
}), 400
@app.errorhandler(404)
def not_found(error):
return jsonify({
"error": "Not Found",
"message": "The requested resource was not found."
}), 404
With these handlers, the API always returns structured JSON responses instead of default HTML error pages.
If a client tries to create an item without providing the required fields, the API might return:
{
"error": "Bad Request",
"message": "Both 'name' and 'price' fields are required"
}
Providing clear error messages helps developers quickly understand what went wrong and how to fix their request.
Request validation and proper error handling are essential for building reliable APIs. They ensure that:
These practices are considered standard in modern API development and should be included in any production-ready backend service.
Modern APIs should expose machine-readable documentation so developers can easily understand endpoints, request formats, and responses. The most widely adopted standard for this is OpenAPI, which describes HTTP APIs in a structured format that tools can automatically interpret.
In Flask applications, a convenient way to generate OpenAPI documentation is by using flask-smorest, a library that integrates Flask with OpenAPI 3, request validation, and automatic Swagger UI documentation.
First, install the required packages.
Run the following command:
pip install flask-smorest marshmallow
If you prefer to manage dependencies with a requirements.txt file, you can add the following:
Flask
flask-smorest
marshmallow
In this setup:
Together, these libraries allow Flask APIs to automatically produce interactive documentation and enforce data validation.
Before creating endpoints, the Flask application must be configured to generate OpenAPI documentation.
Add the following configuration to your Flask application:
from flask import Flask
from flask_smorest import Api
app = Flask(__name__)
app.config["API_TITLE"] = "Flask API Demo"
app.config["API_VERSION"] = "v1"
app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/docs"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
api = Api(app)This configuration defines the metadata used to generate the API documentation.
Key settings include:
Once the API is running, the documentation will be accessible at:
http://localhost:5000/docs
APIs should clearly define the structure of the data they accept and return.
With flask-smorest, this is done using Marshmallow schemas.
Create a schema to describe an item in the API:
from marshmallow import Schema, fields
class ItemSchema(Schema):
name = fields.String(required=True)
price = fields.Float(required=True)
Schemas serve several important purposes:
Using schemas ensures that API endpoints behave consistently and reject invalid data.
Next, create API endpoints using a Blueprint. Blueprints allow you to group related routes and organise larger APIs.
from flask.views import MethodView
from flask_smorest import Blueprint
blp = Blueprint(
"items",
"items",
url_prefix="/items",
description="Operations on items"
)
items = []
Each blueprint represents a group of related endpoints that will appear as a section in the generated documentation.
Now we can implement endpoints for creating and retrieving items.
@blp.route("/")
class ItemsResource(MethodView):
@blp.response(200, ItemSchema(many=True))
def get(self):
"""Return all items"""
return items
@blp.arguments(ItemSchema)
@blp.response(201, ItemSchema)
def post(self, new_item):
"""Create a new item"""
items.append(new_item)
return new_item
In this example:
@blp.arguments(ItemSchema) validates the incoming request body.@blp.response() documents the API response and serialises the output.MethodView allows grouping multiple HTTP methods under the same route.
These decorators automatically update the generated OpenAPI documentation.
After defining the endpoints, register the blueprint with the API instance:
api.register_blueprint(blp)
if __name__ == "__main__":
app.run(debug=True)
Registering the blueprint activates the endpoints and ensures they appear in the API documentation.
Once the application is running, open the following URL in your browser:
http://localhost:5000/docs
You will see an interactive Swagger UI interface generated from the OpenAPI specification.
From this interface you can:
This makes the API easier to understand and significantly simplifies integration with other systems.
Using OpenAPI-based documentation tools such as flask-smorest provides several benefits:
Because OpenAPI is widely adopted across the industry, many development tools can automatically generate client libraries, perform API testing, and validate requests using the same specification.
For these reasons, OpenAPI documentation has become a standard practice when building modern APIs.
Once your Flask API is ready, the next step is deploying it so it can be accessed by users or other services. While the built-in Flask server is useful for development, production deployments require a WSGI server and a web server to handle traffic reliably.
A common deployment setup includes:
For example, you can run a Flask application in production using Gunicorn:
gunicorn -w 4 app:app
In this command:
-w 4 starts four worker processesapp:app references the Flask application object
In modern environments, Flask APIs are often deployed using Docker containers, which simplifies scaling, environment management, and continuous deployment.
Most production APIs require authentication to ensure that only authorised users or services can access protected endpoints.
Common authentication approaches include:
A common approach in Flask APIs is to use JWT-based authentication. In this model:
Authorization header with each request.
Example header:
Authorization: Bearer <access_token>
Flask extensions such as Flask-JWT-Extended make it easier to implement token-based authentication and protect API routes.
Adding authentication ensures that:
Flask is one of the most widely used Python web frameworks for building APIs, but it is not the only option. In recent years, FastAPI has gained popularity as a modern framework designed specifically for building high-performance APIs.
Both frameworks are powerful, but they differ in philosophy and features.
Flask is often the best choice when you need a flexible framework that can adapt to different types of applications.
Flask is well suited for:
Because Flask is lightweight and unopinionated, developers can easily customise how the application is structured.
FastAPI is designed specifically for building APIs and includes many features out of the box.
FastAPI is a strong choice for:
FastAPI automatically generates interactive API documentation and performs request validation using Pydantic models, which reduces the amount of boilerplate code developers need to write.
There is no universal answer to this question. Both frameworks are widely used and capable of powering production applications.
For many teams, the choice depends on existing experience, project requirements, and preferred development style.
Flask remains a powerful and flexible choice for building Python APIs. Its lightweight design, modular architecture, and extensive ecosystem make it well suited for everything from simple services to scalable backend systems. In this guide, we explored how to build a Flask API step by step, including project setup, database integration, request validation, error handling, and OpenAPI documentation, all key practices that help create maintainable and production-ready APIs.
If you are planning a new API or modernising an existing backend, choosing the right architecture early can make a significant difference. Need help building or scaling a Flask API? Contact Imaginary Cloud to discuss your project.
A Flask API refers to a RESTful web service built using the Flask framework in Python. It exposes endpoints that clients can interact with over HTTP, typically returning data in JSON format.
Yes, Flask can be used as a backend framework to build APIs that serve data to frontend applications or third-party services.
Flask is a web framework, while REST is an architectural style for designing networked applications. You can use Flask to implement REST APIs.
Yes, Flask is an excellent choice for building APIs due to its simplicity, flexibility, and a wide range of extensions that support documentation, authentication, and deployment.
Flask is commonly used for building REST APIs, microservices, admin dashboards, prototyping applications, and integrating with machine learning models.
Yes, Flask can be used in production and powers many real-world web applications and APIs. While the built-in Flask development server is intended only for development, production deployments typically use a WSGI server such as Gunicorn or uWSGI behind a web server like Nginx or Apache.
With proper configuration, Flask applications can scale effectively and support production workloads, especially when combined with tools such as Docker, reverse proxies, and database services.
A Flask API is typically deployed using a WSGI server and a reverse proxy.
The basic deployment steps include:
For example, a common deployment command using Gunicorn is:
gunicorn -w 4 app:app
In modern infrastructure, Flask APIs are often deployed using Docker containers and cloud platforms such as AWS, Azure, or Google Cloud.
The main difference between Flask and Django REST Framework (DRF) is the level of structure and built-in functionality.
Flask is a lightweight and flexible microframework that allows developers to choose their own libraries and architecture. It provides minimal built-in features and is highly customisable.
Django REST Framework, on the other hand, is a full-featured framework built on top of Django that includes built-in tools for authentication, serialization, permissions, and API views.
In general:
.webp)

Associate developer working mostly with Backend technologies. An entrepreneur with Data Science interest. Love for sports, audiobooks, coffee and memes!

CEO @ Imaginary Cloud and co-author of the Product Design Process book. I enjoy food, wine, and Krav Maga (not necessarily in this order).

Alexandra Mendes is a Senior Growth Specialist at Imaginary Cloud with 3+ years of experience writing about software development, AI, and digital transformation. After completing a frontend development course, Alexandra picked up some hands-on coding skills and now works closely with technical teams. Passionate about how new technologies shape business and society, Alexandra enjoys turning complex topics into clear, helpful content for decision-makers.
People who read this post, also found these interesting: