Welcome, aspiring web developers! Have you ever wondered how apps talk to each other, or how websites store all that information you see? The answer often involves an API and a database. In this guide, we’re going to build a simple but powerful web API using Flask, a popular Python framework, and connect it to a database to store our information.
Don’t worry if these terms sound a bit daunting. We’ll break everything down into easy-to-understand steps, perfect for beginners!
What Are We Building Today?
Today, we’ll create a basic “To-Do List” API. This API will allow us to:
* Create new to-do items.
* Read all existing to-do items.
* Read a single specific to-do item.
* Update an existing to-do item.
* Delete a to-do item.
This covers the fundamental “CRUD” operations (Create, Read, Update, Delete) that are the backbone of many applications.
Let’s Define Some Key Terms
Before we dive into the code, let’s quickly clarify some important concepts:
- API (Application Programming Interface): Think of an API as a waiter in a restaurant. You (the client) tell the waiter (the API) what you want (e.g., “get me all to-do items”), and the waiter goes to the kitchen (the server/database) to get it for you and brings it back. It’s a set of rules and tools that allow different software applications to communicate with each other.
- Flask: Flask is a “micro web framework” for Python. This means it provides the essential tools to build web applications without getting in your way with too many rigid rules. It’s lightweight, flexible, and very popular for building APIs.
- Database: A database is an organized collection of data, stored and accessed electronically. It’s like a highly organized digital filing cabinet where our to-do items will live permanently, even after our Flask application stops running. For this tutorial, we’ll use SQLite, which is a simple, file-based database perfect for small projects and learning.
- ORM (Object-Relational Mapper): This is a fancy term for a tool that helps us interact with our database using Python objects instead of raw SQL queries. We’ll use SQLAlchemy (and
Flask-SQLAlchemy, an extension for Flask) to make database operations much easier and more Pythonic. - JSON (JavaScript Object Notation): This is a lightweight format for storing and transporting data. It’s commonly used when data is sent from a server to a web page or, in our case, between our API and its clients. It looks very similar to Python dictionaries.
Prerequisites
To follow along, you’ll need:
1. Python 3: Make sure you have Python 3 installed on your system. You can download it from python.org.
2. pip: This is Python’s package installer, usually included with Python 3. We’ll use it to install Flask and other libraries.
3. A text editor or IDE (like VS Code, PyCharm, Atom, or Sublime Text).
Setting Up Your Project
It’s good practice to create a virtual environment for your Python projects. This keeps the dependencies (libraries) for each project separate, preventing conflicts.
-
Create a Project Folder:
First, create a new folder for our project. You can name itflask_todo_api.bash
mkdir flask_todo_api
cd flask_todo_api -
Create a Virtual Environment:
Inside your project folder, run this command to create a virtual environment namedvenv:bash
python -m venv venv -
Activate the Virtual Environment:
You need to activate this environment so that any packages you install only go into this project’s environment.- On macOS/Linux:
bash
source venv/bin/activate - On Windows (Command Prompt):
bash
venv\Scripts\activate.bat - On Windows (PowerShell):
bash
venv\Scripts\Activate.ps1
You’ll notice(venv)appearing at the beginning of your command prompt, indicating that the virtual environment is active.
- On macOS/Linux:
-
Install Required Packages:
Now, with the virtual environment active, install Flask and Flask-SQLAlchemy:bash
pip install Flask Flask-SQLAlchemyFlask-SQLAlchemyis an extension that simplifies using SQLAlchemy (our ORM) with Flask.
Building Our Flask Application
Now that our environment is set up, let’s start coding! Create a file named app.py in your flask_todo_api folder.
1. Initialize Flask and Database
First, we’ll import necessary libraries, initialize our Flask app, and set up our database connection.
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todo.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # This disables a warning
db = SQLAlchemy(app)
Flask: The main class for our web application.request: Used to access incoming request data (like data sent in POST or PUT requests).jsonify: A helper function to turn Python dictionaries into JSON responses.SQLAlchemy(app): Connects our Flask app to the database via SQLAlchemy.
2. Define the Database Model
Next, we need to tell SQLAlchemy what our Todo items should look like in the database. This is our “model.”
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(200), nullable=True)
completed = db.Column(db.Boolean, default=False)
def __repr__(self):
return f'<Todo {self.id}: {self.title}>'
# A method to easily convert our Todo object to a dictionary (for JSON response)
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'description': self.description,
'completed': self.completed
}
db.Model: This tells SQLAlchemy thatTodois a database model.db.Column: Defines a column in our database table.db.Integer,db.String,db.Boolean: These are the data types for our columns.primary_key=True:idwill be unique and automatically generated, acting as the main identifier for each todo item.nullable=False: Means this field cannot be empty.default=False: Sets a default value if none is provided.to_dict(): This is a custom method we added to make it easy to convert aTodoobject into a Python dictionary, whichjsonifycan then turn into JSON.
3. Create Database Tables
Before we can interact with the database, we need to create the tables based on our model. We’ll do this once when the application starts or when we run a specific command. For simplicity, we can do it inside our app.py file, but in larger apps, you might use a separate script or Flask CLI commands.
with app.app_context():
db.create_all()
app.app_context(): Flask needs to know which application instance it’s dealing with to perform certain operations, especially those related to extensions like Flask-SQLAlchemy. app_context provides that context. db.create_all() then creates the tables.
4. Implement API Endpoints (Routes)
Now let’s define the “routes” or “endpoints” that our API will respond to. Each route will correspond to an HTTP method (GET, POST, PUT, DELETE) and a URL path.
@app.route('/todos', methods=['GET', 'POST'])
def handle_todos():
if request.method == 'POST':
# Create a new Todo item
data = request.json # Get JSON data from the request body
if not data or not data.get('title'):
return jsonify({'message': 'Title is required'}), 400
new_todo = Todo(
title=data['title'],
description=data.get('description'), # Use .get() to safely access optional fields
completed=data.get('completed', False) # Default to False if not provided
)
db.session.add(new_todo) # Add the new todo object to the database session
db.session.commit() # Commit the transaction to save it to the database
return jsonify(new_todo.to_dict()), 201 # Return the created item with 201 Created status
else: # GET request
# Get all Todo items
all_todos = Todo.query.all() # Query all Todo objects from the database
return jsonify([todo.to_dict() for todo in all_todos]) # Return a list of dictionaries as JSON
@app.route('/todos/<int:todo_id>', methods=['GET', 'PUT', 'DELETE'])
def handle_single_todo(todo_id):
todo = Todo.query.get_or_404(todo_id) # Find todo by ID, or return 404 if not found
if request.method == 'GET':
# Get a specific Todo item
return jsonify(todo.to_dict())
elif request.method == 'PUT':
# Update a specific Todo item
data = request.json
if not data:
return jsonify({'message': 'No data provided for update'}), 400
# Update fields if they are provided in the request
if 'title' in data:
todo.title = data['title']
if 'description' in data:
todo.description = data['description']
if 'completed' in data:
todo.completed = data['completed']
db.session.commit() # Commit changes to the database
return jsonify(todo.to_dict())
else: # DELETE request
# Delete a specific Todo item
db.session.delete(todo) # Mark the object for deletion
db.session.commit() # Commit the deletion
return jsonify({'message': 'Todo item deleted successfully'}), 204 # 204 No Content status
Let’s break down what’s happening in these routes:
* @app.route(...): This is a “decorator” that registers the function handle_todos or handle_single_todo to be called when a request matches the specified URL path and HTTP method.
* methods=['GET', 'POST']: Specifies which HTTP methods this route should handle.
* request.json: This automatically parses incoming JSON data from the request body into a Python dictionary.
* db.session.add(new_todo): Stages the new Todo object to be added to the database.
* db.session.commit(): Saves all staged changes (additions, updates, deletions) to the database permanently.
* Todo.query.all(): Fetches all Todo objects from the database.
* Todo.query.get_or_404(todo_id): Fetches a single Todo object by its id. If no Todo with that ID is found, it automatically sends a 404 Not Found error.
* jsonify(some_dict): Converts a Python dictionary (some_dict) into a JSON formatted string and sets the correct HTTP Content-Type header.
* return jsonify(new_todo.to_dict()), 201: We return the JSON representation of the created item along with an HTTP status code 201 (Created), which is good practice. Other codes like 200 (OK), 400 (Bad Request), 204 (No Content) are also used.
5. Run the Application
Finally, add the standard block to run your Flask application:
if __name__ == '__main__':
app.run(debug=True) # Run the development server
if __name__ == '__main__':: This ensures the code inside this block only runs when you executeapp.pydirectly (not when it’s imported as a module).app.run(debug=True): Starts the Flask development server.debug=Trueprovides helpful error messages and automatically reloads the server when you make code changes. Never usedebug=Truein a production environment!
The Complete app.py File
Here’s the full code for your app.py file:
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
import os # We'll use this for the database path
app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'todo.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # This disables a warning
db = SQLAlchemy(app)
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(200), nullable=True)
completed = db.Column(db.Boolean, default=False)
def __repr__(self):
return f'<Todo {self.id}: {self.title}>'
# A method to easily convert our Todo object to a dictionary (for JSON response)
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'description': self.description,
'completed': self.completed
}
with app.app_context():
db.create_all()
@app.route('/todos', methods=['GET', 'POST'])
def handle_todos():
if request.method == 'POST':
# Create a new Todo item
data = request.json # Get JSON data from the request body
if not data or not data.get('title'):
return jsonify({'message': 'Title is required'}), 400
new_todo = Todo(
title=data['title'],
description=data.get('description'),
completed=data.get('completed', False)
)
db.session.add(new_todo)
db.session.commit()
return jsonify(new_todo.to_dict()), 201
else: # GET request
# Get all Todo items
all_todos = Todo.query.all()
return jsonify([todo.to_dict() for todo in all_todos])
@app.route('/todos/<int:todo_id>', methods=['GET', 'PUT', 'DELETE'])
def handle_single_todo(todo_id):
todo = Todo.query.get_or_404(todo_id)
if request.method == 'GET':
# Get a specific Todo item
return jsonify(todo.to_dict())
elif request.method == 'PUT':
# Update a specific Todo item
data = request.json
if not data:
return jsonify({'message': 'No data provided for update'}), 400
if 'title' in data:
todo.title = data['title']
if 'description' in data:
todo.description = data['description']
if 'completed' in data:
todo.completed = data['completed']
db.session.commit()
return jsonify(todo.to_dict())
else: # DELETE request
# Delete a specific Todo item
db.session.delete(todo)
db.session.commit()
return jsonify({'message': 'Todo item deleted successfully'}), 204
if __name__ == '__main__':
app.run(debug=True)
Running and Testing Your API
-
Run the Flask app:
Make sure your virtual environment is active, then run:bash
python app.py
You should see output similar to this, indicating the server is running:
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: XXX-XXX-XXX
Your API is now live athttp://127.0.0.1:5000(which islocalhost:5000). -
Test with
curl(or Postman/Insomnia):
curlis a command-line tool for making HTTP requests. Open a new terminal window (keep the server running in the first one), and make sure your virtual environment is not active in this new terminal if you are usingcurlfrom outside thevenv.-
Create a To-Do item (POST):
bash
curl -X POST -H "Content-Type: application/json" -d '{"title": "Learn Flask API", "description": "Finish the Flask API tutorial"}' http://127.0.0.1:5000/todos
You should get a response like:
json
{"completed": false, "description": "Finish the Flask API tutorial", "id": 1, "title": "Learn Flask API"}
Theidmight be different if you’ve already created other items. -
Create another To-Do item:
bash
curl -X POST -H "Content-Type: application/json" -d '{"title": "Buy groceries", "completed": true}' http://127.0.0.1:5000/todos -
Get all To-Do items (GET):
bash
curl http://127.0.0.1:5000/todos
You should see a list of all items you created:
json
[{"completed": false, "description": "Finish the Flask API tutorial", "id": 1, "title": "Learn Flask API"}, {"completed": true, "description": null, "id": 2, "title": "Buy groceries"}] -
Get a specific To-Do item (GET) (replace
1with the ID of an item):
bash
curl http://127.0.0.1:5000/todos/1
Response:
json
{"completed": false, "description": "Finish the Flask API tutorial", "id": 1, "title": "Learn Flask API"} -
Update a To-Do item (PUT) (let’s mark the first item as completed):
bash
curl -X PUT -H "Content-Type: application/json" -d '{"completed": true}' http://127.0.0.1:5000/todos/1
Response:
json
{"completed": true, "description": "Finish the Flask API tutorial", "id": 1, "title": "Learn Flask API"} -
Delete a To-Do item (DELETE):
bash
curl -X DELETE http://127.0.0.1:5000/todos/2
Response:
json
{"message": "Todo item deleted successfully"}
If you now try toGETall todos, you’ll see only the first one remains.
-
Congratulations! You’ve successfully built and tested your first Flask API connected to a database!
Conclusion
You’ve taken a significant step in your web development journey. You learned what APIs and databases are, how to set up a Flask project, define a database model with Flask-SQLAlchemy, and create API endpoints to perform CRUD operations.
This simple to-do list API forms the foundation for countless other web services. From here, you can explore adding authentication, handling more complex data relationships, deploying your API, or even building a front-end application to interact with it. Keep experimenting and building!
Leave a Reply
You must be logged in to post a comment.