30. Web Development with Flask
π Master Flask web development! Learn routing, templates, forms, databases, and REST APIs. Build production-ready web applications! β¨
What we will learn in this post?
- π Introduction to Flask
- π Setting Up Flask and First Application
- π Routing and URL Building
- π Templates with Jinja2
- π Handling Forms and User Input
- π Flask and Databases
- π Flask REST APIs
Introduction to Flask π
Flask is a lightweight web framework for Python that makes building web applications easy and fun! It follows the micro-framework philosophy, meaning it provides the essentials without unnecessary features. This allows developers to add only what they need, keeping applications simple and efficient.
Flask vs. Django βοΈ
- Flask:
- Lightweight and flexible
- Great for small to medium projects
- Easy to learn and use
- Ideal for microservices
- Django:
- Full-featured framework
- Best for large applications
- Comes with built-in features like admin panels and ORM
When to Choose Flask π€
- You want quick development for small projects.
- You prefer flexibility and control over components.
- You need a simple API or microservice.
Flask is also highly extensible! You can easily add libraries and tools to enhance your application. Companies like Netflix, Reddit, and Lyft use Flask to power critical microservices handling millions of requests per day.
graph TD
A["π― Start with Flask"]:::style1 --> B{"π‘ Choose Components"}:::style2
B -- "Minimal" --> C["β‘ Core Flask Only"]:::style3
B -- "Extended" --> D["π§ Add Extensions"]:::style4
C --> E["π Build Your App"]:::style5
D --> E
classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style2 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style3 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style4 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style5 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
With Flask, you can create powerful web applications while keeping things simple and enjoyable! Happy coding! π
Getting Started with Flask! π
Installing Flask
To start, you need to install Flask. Open your terminal and run:
1
pip install Flask
Creating a Minimal Flask Application
Now, letβs create a simple βHello Worldβ app! Create a file named app.py and add the following code:
1
2
3
4
5
6
7
8
9
10
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello, World!'
if __name__ == '__main__':
app.run(debug=True)
Understanding the Code
Flask(__name__): This creates the app object.@app.route('/'): This defines a route for the homepage.hello(): This function returns βHello, World!β when you visit the homepage.app.run(debug=True): This runs the server in debug mode.
Running the Development Server
To run your app, execute:
1
python app.py
Now, open your browser and go to http://127.0.0.1:5000/ to see your βHello, World!β message! π In production, youβd use a WSGI server like Gunicorn or uWSGI instead of the development server.
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#ff4f81','primaryTextColor':'#fff','primaryBorderColor':'#c43e3e','lineColor':'#e67e22','secondaryColor':'#6b5bff','tertiaryColor':'#ffd700'}}}%%
sequenceDiagram
participant B as π Browser
participant F as βοΈ Flask App
participant R as π― Route Handler
Note over B,F: HTTP Request Lifecycle
B->>+F: GET / HTTP/1.1
F->>F: Match route decorator
F->>+R: Call hello()
R-->>-F: Return 'Hello, World!'
F-->>-B: HTTP 200 with HTML
Note left of B: Page rendered β
Flask Routing Made Easy! π
Flask is a fantastic web framework for Python, and one of its coolest features is routing! Letβs break it down.
What is Routing? π
Routing in Flask helps you connect URLs to functions. You use the @app.route() decorator to define a route. Hereβs a simple example:
1
2
3
@app.route('/')
def home():
return "Welcome to my website!"
Using URL Parameters π¦
You can also capture parts of the URL as parameters. For example:
1
2
3
@app.route('/user/<username>')
def show_user(username):
return f"Hello, {username}!"
Variable Rules π
You can define variable rules in your routes:
1
2
3
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f"Post ID: {post_id}"
HTTP Methods (GET, POST) π¬
Flask supports different HTTP methods. By default, routes respond to GET requests. To handle POST requests, you can specify it like this:
1
2
3
@app.route('/submit', methods=['POST'])
def submit():
return "Form submitted!"
Using url_for() π
The url_for() function helps you build URLs dynamically. For example:
1
url_for('show_user', username='Alice')
This generates the URL /user/Alice.
graph TD
A["π€ User Request"]:::style1 --> B{"π― HTTP Method"}:::style2
B -- "GET /" --> C["π Home Page"]:::style3
B -- "GET /user/<name>" --> D["π₯ User Profile"]:::style4
B -- "POST /submit" --> E["π Submit Form"]:::style5
B -- "GET /api/data" --> F["π JSON Response"]:::style6
classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style2 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style3 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style4 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style5 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style6 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
Real-World Example: RESTful User API π―
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
from flask import Flask, jsonify, request, abort
from functools import wraps
import jwt
import datetime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
# In-memory user database (use real DB in production)
users = {
1: {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'},
2: {'id': 2, 'name': 'Bob', 'email': 'bob@example.com'}
}
def token_required(f):
"""Decorator to protect routes with JWT authentication"""
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization')
if not token:
return jsonify({'message': 'Token is missing'}), 401
try:
token = token.split()[1] # Remove 'Bearer' prefix
data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
current_user_id = data['user_id']
except:
return jsonify({'message': 'Token is invalid'}), 401
return f(current_user_id, *args, **kwargs)
return decorated
@app.route('/api/login', methods=['POST'])
def login():
"""Generate JWT token for authentication"""
auth = request.get_json()
# Validate credentials (simplified for demo)
if auth and auth.get('email') == 'alice@example.com':
token = jwt.encode({
'user_id': 1,
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)
}, app.config['SECRET_KEY'], algorithm='HS256')
return jsonify({'token': token})
return jsonify({'message': 'Invalid credentials'}), 401
@app.route('/api/users', methods=['GET'])
@token_required
def get_users(current_user_id):
"""Get all users (protected route)"""
return jsonify({'users': list(users.values())})
@app.route('/api/users/<int:user_id>', methods=['GET'])
@token_required
def get_user(current_user_id, user_id):
"""Get specific user by ID"""
user = users.get(user_id)
if not user:
abort(404, description='User not found')
return jsonify(user)
@app.route('/api/users', methods=['POST'])
@token_required
def create_user(current_user_id):
"""Create new user"""
data = request.get_json()
if not data or 'name' not in data or 'email' not in data:
abort(400, description='Missing required fields')
new_id = max(users.keys()) + 1
users[new_id] = {
'id': new_id,
'name': data['name'],
'email': data['email']
}
return jsonify(users[new_id]), 201
@app.route('/api/users/<int:user_id>', methods=['PUT'])
@token_required
def update_user(current_user_id, user_id):
"""Update existing user"""
if user_id not in users:
abort(404, description='User not found')
data = request.get_json()
users[user_id].update(data)
return jsonify(users[user_id])
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
@token_required
def delete_user(current_user_id, user_id):
"""Delete user"""
if user_id not in users:
abort(404, description='User not found')
del users[user_id]
return '', 204
if __name__ == '__main__':
app.run(debug=True)
Why This Matters: This RESTful API pattern with JWT authentication is used by companies like Stripe, Twilio, and GitHub for their APIs. It provides secure, stateless authentication perfect for microservices architectures.
What HTTP method should you use to update an existing resource in a RESTful API?
PUT is the standard HTTP method for updating existing resources. POST creates new resources, GET retrieves them, and DELETE removes them.
Happy coding! π
Introduction to Jinja2 Templating Engine π
Jinja2 is a powerful templating engine for Python, widely used in web frameworks like Flask. It allows you to create dynamic web pages by separating HTML from Python code. Letβs explore its key features!
Template Syntax π
Jinja2 uses a simple syntax to embed Python-like expressions in HTML. Here are some basics:
- Variables: Use double curly braces with variable name to display variables (e.g.,
variable_nameinside double braces) - Filters: Modify variables with filters like
name|capitalizeinside double braces
Control Structures π
You can control the flow of your templates using:
- If Statements: Check conditions with
if userblocks that show different content based on conditions1 2 3 4
<!-- Example: if user exists, show "Hello, user!", else show "Hello, Guest!" --> <div> Hello, Alice! </div>
- For Loops: Iterate over lists to create repeated HTML elements
1 2 3 4 5 6
<!-- Example: loop through items and display each in a list --> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul>
Template Inheritance ποΈ
Jinja2 supports template inheritance, allowing you to create a base template and extend it:
- Create a base.html with common structure (header, footer)
- Child templates extend the base and fill in specific content blocks
- This avoids code duplication across pages
1
2
3
4
5
6
7
8
9
<!-- base.html -->
<html>
<head><title>My Site</title></head>
<body>
<header>Site Header</header>
<!-- Content goes here -->
<footer>Site Footer</footer>
</body>
</html>
Rendering Templates π
To render templates in Flask, use render_template():
1
2
3
4
5
from flask import render_template
@app.route('/')
def home():
return render_template('index.html', user='Alice')
Handling Form Data in Flask π
Flask makes it easy to handle form data! Letβs explore how to use request.form, request.args, and request.json for different types of data.
Getting Data from Forms π
When you submit a form, you can access the data in different ways:
request.form: Use this for form data sent via POST. Itβs great for handling user inputs like text fields.1 2 3 4 5 6
from flask import Flask, request @app.route('/submit', methods=['POST']) def submit(): name = request.form['name'] return f"Hello, {name}!"
request.args: This is for data sent via GET requests, like query parameters in the URL.request.json: Use this for JSON data sent in the body of a POST request.
Form Validation with Flask-WTF β
To ensure your forms are filled out correctly, you can use Flask-WTF. It simplifies form handling and validation.
Basic Steps to Use Flask-WTF:
- Install Flask-WTF:
1
pip install Flask-WTF Create a form class with validation rules.
- Use the form in your route to handle submissions.
graph TD
A["π User Submits Form"]:::style1 --> B["π Flask-WTF Validates"]:::style2
B --> C{"β
Is Valid?"}:::style3
C -- "Yes" --> D["πΎ Process Data"]:::style4
C -- "No" --> E["β Show Errors"]:::style5
D --> F["β¨ Success Response"]:::style6
E --> G["π Re-render Form"]:::style7
classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style2 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style3 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style4 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style5 fill:#c43e3e,stroke:#8b2e2e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style6 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style7 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
graph TD
A["π base.html"]:::style1 --> B["π¨ Header Block"]:::style2
A --> C["π¦ Content Block"]:::style3
A --> D["π» Footer Block"]:::style4
E["π index.html"]:::style5 --> F["π extends base.html"]:::style6
F --> G["βοΈ Override content"]:::style7
G --> C
classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style2 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style3 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style4 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style5 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style6 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style7 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
Which Flask object contains form data submitted via POST request?
request.form contains form data from POST requests (application/x-www-form-urlencoded). request.args is for URL query parameters, and request.json is for JSON payloads.
Integrating Databases with Flask using Flask-SQLAlchemy π
Flask-SQLAlchemy makes it easy to work with databases in your Flask app. Letβs dive into how to set it up and perform some basic CRUD operations!
Setting Up Flask-SQLAlchemy π οΈ
First, install Flask and Flask-SQLAlchemy:
1
pip install Flask Flask-SQLAlchemy
Defining Models π
Create a simple model for a User:
1
2
3
4
5
6
7
8
9
10
11
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
Creating Tables ποΈ
Run this in your Python shell to create the database:
1
db.create_all()
Performing CRUD Operations π
Create a User
1
2
3
4
5
6
@app.route('/add_user/<name>/<email>')
def add_user(name, email):
new_user = User(name=name, email=email)
db.session.add(new_user)
db.session.commit()
return f'User {name} added!'
Read Users
1
2
3
4
@app.route('/users')
def get_users():
users = User.query.all()
return {user.id: user.name for user in users}
Update a User
1
2
3
4
5
6
@app.route('/update_user/<int:id>/<name>')
def update_user(id, name):
user = User.query.get(id)
user.name = name
db.session.commit()
return f'User {id} updated!'
Delete a User
1
2
3
4
5
6
@app.route('/delete_user/<int:id>')
def delete_user(id):
user = User.query.get(id)
db.session.delete(user)
db.session.commit()
return f'User {id} deleted!'
graph TD
A["βοΈ Flask Application"]:::style1 --> B["π Flask-SQLAlchemy"]:::style2
B --> C["ποΈ SQLite/PostgreSQL/MySQL"]:::style3
D["π Model Classes"]:::style4 --> B
D --> E["π€ User Model"]:::style5
D --> F["π Post Model"]:::style6
D --> G["π¬ Comment Model"]:::style7
E -."1:N".-> F
F -."1:N".-> G
classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style2 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style3 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style4 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style5 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style6 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style7 fill:#9e9e9e,stroke:#616161,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
Which method is used to add a new record to the database in Flask-SQLAlchemy?
db.session.add() adds a new object to the session, and db.session.commit() persists it to the database. Flask-SQLAlchemy uses SQLAlchemy's session pattern for database operations.
Feel free to ask if you have any questions! π
Real-World Example: Blog System with Relationships π―
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# Models with relationships
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(200), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Relationships
posts = db.relationship('Post', backref='author', lazy='dynamic',
cascade='all, delete-orphan')
comments = db.relationship('Comment', backref='author', lazy='dynamic',
cascade='all, delete-orphan')
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def to_dict(self):
return {
'id': self.id,
'username': self.username,
'email': self.email,
'created_at': self.created_at.isoformat(),
'post_count': self.posts.count()
}
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow,
onupdate=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
# Relationships
comments = db.relationship('Comment', backref='post', lazy='dynamic',
cascade='all, delete-orphan')
def to_dict(self, include_comments=False):
data = {
'id': self.id,
'title': self.title,
'content': self.content,
'created_at': self.created_at.isoformat(),
'updated_at': self.updated_at.isoformat(),
'author': self.author.username,
'comment_count': self.comments.count()
}
if include_comments:
data['comments'] = [c.to_dict() for c in self.comments]
return data
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
def to_dict(self):
return {
'id': self.id,
'content': self.content,
'created_at': self.created_at.isoformat(),
'author': self.author.username
}
# API endpoints
@app.route('/api/posts', methods=['GET'])
def get_posts():
"""Get all posts with pagination"""
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
posts = Post.query.order_by(Post.created_at.desc()).paginate(
page=page, per_page=per_page, error_out=False
)
return jsonify({
'posts': [post.to_dict() for post in posts.items],
'total': posts.total,
'page': page,
'pages': posts.pages
})
@app.route('/api/posts/<int:post_id>', methods=['GET'])
def get_post(post_id):
"""Get single post with comments"""
post = Post.query.get_or_404(post_id)
return jsonify(post.to_dict(include_comments=True))
@app.route('/api/posts', methods=['POST'])
def create_post():
"""Create new post"""
data = request.get_json()
# Validate required fields
if not data or 'title' not in data or 'content' not in data:
return jsonify({'error': 'Missing required fields'}), 400
# Get or create user (simplified - use proper auth in production)
user = User.query.filter_by(username=data.get('author', 'anonymous')).first()
if not user:
user = User(username=data.get('author', 'anonymous'),
email=f"{data.get('author', 'anonymous')}@example.com")
user.set_password('password123')
db.session.add(user)
# Create post
post = Post(
title=data['title'],
content=data['content'],
author=user
)
db.session.add(post)
db.session.commit()
return jsonify(post.to_dict()), 201
@app.route('/api/posts/<int:post_id>/comments', methods=['POST'])
def add_comment(post_id):
"""Add comment to post"""
post = Post.query.get_or_404(post_id)
data = request.get_json()
if not data or 'content' not in data:
return jsonify({'error': 'Missing content'}), 400
# Get or create user
user = User.query.filter_by(username=data.get('author', 'anonymous')).first()
if not user:
user = User(username=data.get('author', 'anonymous'),
email=f"{data.get('author', 'anonymous')}@example.com")
user.set_password('password123')
db.session.add(user)
comment = Comment(
content=data['content'],
post=post,
author=user
)
db.session.add(comment)
db.session.commit()
return jsonify(comment.to_dict()), 201
@app.route('/api/users/<username>/posts', methods=['GET'])
def get_user_posts(username):
"""Get all posts by a user"""
user = User.query.filter_by(username=username).first_or_404()
posts = user.posts.order_by(Post.created_at.desc()).all()
return jsonify({
'user': user.to_dict(),
'posts': [post.to_dict() for post in posts]
})
if __name__ == '__main__':
with app.app_context():
db.create_all() # Create tables
app.run(debug=True)
Why This Matters: This blog system demonstrates SQLAlchemy relationships (one-to-many), cascade deletes, and pagination - essential patterns used by WordPress, Medium, and DEV.to. The relationship handling prevents orphaned records and maintains data integrity across millions of posts.
Creating RESTful APIs with Flask π
Flask is a lightweight web framework for Python that makes it easy to create RESTful APIs. Letβs break down how to do this in a friendly way!
Getting Started with Flask π
- Install Flask: Use pip to install Flask:
1
pip install Flask - Create a Simple API: Hereβs a basic example of a Flask app that returns JSON responses:
1 2 3 4 5 6 7 8 9 10
from flask import Flask, jsonify app = Flask(__name__) @app.route('/api/greet', methods=['GET']) def greet(): return jsonify(message="Hello, World!") if __name__ == '__main__': app.run(debug=True)
Handling Different HTTP Methods π
You can handle various HTTP methods like GET, POST, PUT, and DELETE. Hereβs how:
- GET: Retrieve data.
- POST: Create new data.
- PUT: Update existing data.
- DELETE: Remove data.
Example for a POST request:
1
2
3
4
@app.route('/api/items', methods=['POST'])
def create_item():
item = request.json
return jsonify(item), 201
Using Flask-RESTful π οΈ
Flask-RESTful is an extension that simplifies API creation. It provides tools to build REST APIs quickly.
Example with Flask-RESTful:
1
2
3
4
5
6
7
8
9
from flask_restful import Resource, Api
api = Api(app)
class Item(Resource):
def get(self):
return {'item': 'example'}
api.add_resource(Item, '/api/item')
Flowchart of API Request Handling
graph TD
A["π Client Request"]:::style1 --> B{"π― HTTP Method"}:::style2
B -- "GET" --> C["π Retrieve Data"]:::style3
B -- "POST" --> D["β¨ Create Data"]:::style4
B -- "PUT" --> E["βοΈ Update Data"]:::style5
B -- "DELETE" --> F["ποΈ Delete Data"]:::style6
C --> G["π¦ JSON Response 200"]:::style7
D --> H["π¦ JSON Response 201"]:::style8
E --> G
F --> I["π¦ Empty Response 204"]:::style9
classDef style1 fill:#ff4f81,stroke:#c43e3e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style2 fill:#ffd700,stroke:#d99120,color:#222,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style3 fill:#43e97b,stroke:#38f9d7,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style4 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style5 fill:#ff9800,stroke:#f57c00,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style6 fill:#c43e3e,stroke:#8b2e2e,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style7 fill:#00bfae,stroke:#005f99,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style8 fill:#6b5bff,stroke:#4a3f6b,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
classDef style9 fill:#9e9e9e,stroke:#616161,color:#fff,font-size:16px,stroke-width:3px,rx:14,shadow:6px;
linkStyle default stroke:#e67e22,stroke-width:3px;
π― Hands-On Assignment: Build a Task Management API with Authentication π
π Your Mission
Create a production-ready RESTful API for task management using Flask. Build a complete system with user authentication (JWT tokens), CRUD operations for tasks, SQLAlchemy database models with relationships, and proper error handling. This project combines routing, forms, databases, and REST API concepts covered in this post.π― Requirements
- Implement User authentication system:
POST /api/register- Register new user with password hashingPOST /api/login- Login and receive JWT token- Use
@token_requireddecorator to protect routes
- Create Task CRUD operations:
GET /api/tasks- List all tasks for authenticated userGET /api/tasks/</code> - Get single task details</li> POST /api/tasks- Create new taskPUT /api/tasks/</code> - Update task</li> DELETE /api/tasks/</code> - Delete task</li> </ul> </li> - Database models with Flask-SQLAlchemy:
Usermodel: id, username, email, password_hash, created_atTaskmodel: id, title, description, status, priority, due_date, user_id- Relationship: User has many Tasks (one-to-many)
- Add task filtering and pagination:
- Filter by status:
?status=completed - Filter by priority:
?priority=high - Pagination:
?page=1&per_page=10
- Implement proper error handling with appropriate HTTP status codes
- Write at least 3 test cases using pytest or unittest </ol>
π‘ Implementation Hints
- Install required packages:
pip install Flask Flask-SQLAlchemy PyJWT werkzeug - Use
werkzeug.security.generate_password_hash()for password hashing - Generate JWT with:
jwt.encode({'user_id': user.id, 'exp': ...}, SECRET_KEY) - Protect routes with decorator that checks JWT from
Authorizationheader - Use
db.relationship()withbackreffor User-Task relationship - For filtering:
Task.query.filter_by(status=status, user_id=user_id) - For pagination:
.paginate(page=page, per_page=per_page)
π Example Input/Output
# 1. Register a new user curl -X POST http://localhost:5000/api/register \ -H "Content-Type: application/json" \ -d '{"username": "alice", "email": "alice@example.com", "password": "secret123"}' # Response: {"message": "User created successfully", "user_id": 1} # 2. Login to get JWT token curl -X POST http://localhost:5000/api/login \ -H "Content-Type: application/json" \ -d '{"email": "alice@example.com", "password": "secret123"}' # Response: {"token": "eyJ0eXAiOiJKV1QiLCJhbGc..."} # 3. Create a task (requires token) curl -X POST http://localhost:5000/api/tasks \ -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc..." \ -H "Content-Type: application/json" \ -d '{"title": "Learn Flask", "description": "Complete Flask tutorial", "priority": "high", "status": "in_progress"}' # Response: {"id": 1, "title": "Learn Flask", "status": "in_progress", "created_at": "2026-01-05T10:30:00"} # 4. Get all tasks with filtering curl -X GET "http://localhost:5000/api/tasks?status=in_progress&page=1" \ -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc..." # Response: # { # "tasks": [ # {"id": 1, "title": "Learn Flask", "status": "in_progress", "priority": "high"}, # {"id": 2, "title": "Build API", "status": "in_progress", "priority": "medium"} # ], # "total": 2, # "page": 1, # "pages": 1 # } # 5. Update a task curl -X PUT http://localhost:5000/api/tasks/1 \ -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc..." \ -H "Content-Type: application/json" \ -d '{"status": "completed"}' # Response: {"id": 1, "title": "Learn Flask", "status": "completed", "updated_at": "2026-01-05T14:20:00"}π Bonus Challenges
- Level 2: Add task categories with many-to-many relationship (Task β Category)
- Level 3: Implement task sharing:
POST /api/tasks//share</code> to share with other users</li> - Level 4: Add file attachments to tasks using secure file upload
- Level 5: Create real-time notifications using Flask-SocketIO when tasks are updated
- Level 6: Deploy to Heroku with PostgreSQL database and document API with Swagger/OpenAPI </ul>
π Learning Goals
- Master Flask routing and RESTful API design principles π―
- Implement secure JWT-based authentication π
- Build database models with SQLAlchemy relationships ποΈ
- Apply proper HTTP methods and status codes β¨
- Handle errors gracefully with try-except blocks π‘οΈ
- Create production-ready APIs used by real companies π
π‘ Pro Tip: This task management API pattern is used by Trello, Asana, and Todoist! JWT authentication is the industry standard for stateless API authentication, perfect for microservices and mobile apps. Master this, and you can build any RESTful API!
Share Your Solution! π¬
Completed the project? Post your code in the comments below! Show us your Flask API mastery! πβ¨
</div> </details> --- # Conclusion: Master Flask Web Development π Flask empowers you to build production-ready web applications and APIs with minimal boilerplate while maintaining maximum flexibility. From routing and templates to databases and REST APIs, Flask provides everything needed to create scalable, maintainable web services used by companies worldwide!