Backend — Flask
Flask Framework
The micro-framework that stays out of your way — minimal core, maximum flexibility. Perfect for microservices, REST APIs, and learning backend fundamentals.
Micro-FrameworkPython 3.10+REST APIs
~7kLines of Code
WSGIServer
Jinja2Templates
WerkzeugCore
✓Lightweight
The Microframework — Minimal Core, Maximum Control
Flask is "micro" because it keeps the core deliberately small. No ORM. No form validation. No admin panel.
You choose every component — which means you understand every part of your stack.
01
The Smallest Possible Web App
A complete Flask app can be 7 lines. That's not a toy — that's the philosophy: only add what you actually need.
python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello, World!'
# That's it. That's a web server.
02
Freedom Over Convention
Django has one right way. Flask has zero opinions. You pick your ORM, auth library, form validation, and project structure — which means you actually learn why each piece exists.
python
# Django forces this structure. Flask doesn't.
# You can choose:
# ORM → SQLAlchemy / Peewee / raw SQL
# Auth → Flask-Login / Flask-JWT / roll your own
# Forms → Flask-WTF / Marshmallow / plain dicts
# Templates → Jinja2 (built-in) or any other engine
# Structure → flat file / MVC / blueprints / you decide
03
Built on Werkzeug + Jinja2
Flask is a thin layer on top of two battle-tested libraries — Werkzeug handles HTTP (WSGI toolkit) and Jinja2 handles templating. Both are used in production by major companies.
Netflix, LinkedIn, Reddit, and Airbnb have all used Flask in production. Its simplicity scales surprisingly well when you know what you're doing.
2010
Year Released
Created by Armin Ronacher as an April Fool's joke that turned real
3.x
Current Version
Full async support added in 2.0+
BSD
License
Open source, maintained by Pallets Project
~7
Lines Min App
The smallest complete web server you can write
Quick Start — From Zero to Running App
Install Flask, create your first app, and understand the recommended project structure before building anything bigger.
01
Install in a Virtual Environment
Always use a virtual environment. Never install Flask globally.
bash
# Create and activate virtual environment
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# Install Flask + common extensions
pip install flask flask-sqlalchemy flask-login flask-wtf
02
Recommended Project Structure
For anything beyond a single file, use this layout. The Application Factory pattern (
create_app()) makes testing and configuration much easier.myapp/
├── app.py # entry point
├── .env # secrets (never commit)
├── config.py # app configuration
├── requirements.txt # dependencies
├── app/
│ ├── __init__.py # Application Factory (create_app)
│ ├── models.py # SQLAlchemy models
│ ├── auth/ # auth blueprint
│ │ ├── __init__.py
│ │ └── routes.py
│ ├── main/ # main blueprint
│ │ ├── __init__.py
│ │ └── routes.py
│ └── templates/
│ ├── base.html
│ ├── auth/
│ └── main/
03
Application Factory Pattern
Create the Flask app inside a function. This avoids circular imports and makes it easy to create multiple app instances for testing.
app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
db = SQLAlchemy()
login = LoginManager()
def create_app(config='config.DevelopmentConfig'):
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
login.init_app(app)
from .auth import auth as auth_bp
from .main import main as main_bp
app.register_blueprint(auth_bp, url_prefix='/auth')
app.register_blueprint(main_bp)
return app
04
Configuration
Separate dev and production configs. Load secrets from environment variables — never hardcode them.
config.py
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY', 'change-me')
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'
class ProductionConfig(Config):
DEBUG = False
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
Routes & Handling Requests
Routes map URLs to Python functions. Flask's decorator-based routing is clean and expressive. The
request object gives you everything the client sent.01
Route Decorators & URL Variables
Use angle brackets to capture URL segments. Converters (
int:, float:, path:, uuid:) validate and convert automatically.python
@app.route('/')
def index():
return 'Home page'
@app.route('/user/<username>')
def profile(username):
return f'Profile: {username}'
@app.route('/post/<int:post_id>') # int: converter
def post(post_id):
return f'Post #{post_id}'
@app.route('/files/<path:filename>') # path: allows slashes
def serve_file(filename):
return send_from_directory('uploads', filename)
02
HTTP Methods & the Request Object
Specify which HTTP methods a route accepts. The global
request object carries all incoming data.python
from flask import request, redirect, url_for
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username') # form data
password = request.form.get('password')
return redirect(url_for('dashboard'))
return render_template('login.html')
@app.route('/api/data', methods=['POST'])
def api_data():
data = request.get_json() # JSON body
page = request.args.get('page', 1, type=int) # query param
token = request.headers.get('Authorization') # header
return {'status': 'ok'}
03
Responses & JSON API
Return strings, dicts (auto-converted to JSON), Response objects, or use
jsonify(). Set status codes with a tuple.python
from flask import jsonify, make_response
# Dict → auto JSON (Flask 1.0+)
@app.route('/api/user/<int:uid>')
def get_user(uid):
user = User.query.get_or_404(uid)
return {'id': user.id, 'name': user.name} # 200 OK
# Custom status code
@app.route('/api/items', methods=['POST'])
def create_item():
item = Item(**request.get_json())
db.session.add(item)
db.session.commit()
return jsonify(item.to_dict()), 201 # 201 Created
# Error response
@app.errorhandler(404)
def not_found(e):
return {'error': 'Not found'}, 404
04
Before/After Request Hooks
Run code before every request (e.g. auth checks) or after every response (e.g. logging).
python
@app.before_request
def check_auth():
# Runs before every request
if '/api/' in request.path and not current_user.is_authenticated:
return {'error': 'Unauthorized'}, 401
@app.after_request
def add_headers(response):
# Runs after every response
response.headers['X-Frame-Options'] = 'DENY'
return response
Jinja2 Templates
Flask uses Jinja2 for HTML templating. Variables, loops, conditionals, filters, and template inheritance allow you to build clean, reusable UI without repeating markup.
01
Base Template with Inheritance
Define a base layout once. Child templates fill in the blocks. This is the DRY approach to HTML.
templates/base.html
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My App{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
{# Flash messages #}
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endwith %}
{% block content %}{% endblock %}
</body>
</html>
02
Child Templates, Variables & Loops
Extend the base with
{% extends %}. Use {{ var }} for output, {% for %} for loops, {% if %} for conditionals.templates/posts.html
{% extends 'base.html' %}
{% block title %}Posts — {{ current_user.name }}{% endblock %}
{% block content %}
<h1>Posts{% if posts %} ({{ posts|length }}){% endif %}</h1>
{% for post in posts %}
<article>
<h2>{{ post.title | title }}</h2> {# filter: title case #}
<p>{{ post.body | truncate(150) }}</p> {# filter: truncate #}
<a href="{{ url_for('main.post', id=post.id) }}">Read more</a>
</article>
{% else %}
<p>No posts yet.</p>
{% endfor %}
{% endblock %}
03
Passing Data from View to Template
Use
render_template() with keyword arguments. Everything you pass becomes a variable in the template.python
from flask import render_template
@app.route('/posts')
def post_list():
posts = Post.query.order_by(Post.created.desc()).all()
return render_template(
'posts.html',
posts=posts,
title='All Posts',
page_num=request.args.get('page', 1, type=int)
)
Database with Flask-SQLAlchemy
Flask doesn't have a built-in ORM, but Flask-SQLAlchemy integrates SQLAlchemy seamlessly. Define models as Python classes, let SQLAlchemy handle the SQL.
01
Defining Models
Each class is a database table. Column types map directly to SQL types.
models.py
from app import db
from datetime import datetime
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 = db.Column(db.String(256), nullable=False)
created = db.Column(db.DateTime, default=datetime.utcnow)
posts = db.relationship('Post', backref='author', lazy=True)
def __repr__(self):
return f'<User {self.username}>'
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
body = db.Column(db.Text, nullable=False)
created = db.Column(db.DateTime, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
02
CRUD Operations
Create, read, update, delete using SQLAlchemy's session and query API.
python
# CREATE
user = User(username='omer', email='omer@example.com', password=hashed)
db.session.add(user)
db.session.commit()
# READ
user = User.query.get(1) # by primary key
user = User.query.filter_by(username='omer').first()
users = User.query.order_by(User.created.desc()).all()
posts = Post.query.filter(Post.title.ilike('%flask%')).limit(10).all()
# UPDATE
user.email = 'newemail@example.com'
db.session.commit()
# DELETE
db.session.delete(post)
db.session.commit()
# get_or_404 — returns 404 if not found (use in views)
post = Post.query.get_or_404(post_id)
03
Migrations with Flask-Migrate
Use Flask-Migrate (Alembic) to version-control your schema changes. Never manually alter your production DB.
bash
pip install flask-migrate
# In __init__.py:
# from flask_migrate import Migrate
# migrate = Migrate(app, db)
# CLI commands:
flask db init # create migrations folder (once)
flask db migrate -m "add posts table" # generate migration
flask db upgrade # apply to database
flask db downgrade # roll back one migration
Authentication & Sessions
Flask doesn't have built-in auth. Flask-Login manages user sessions, and you implement the login logic. This gives you full control over how authentication works.
01
Flask-Login Setup
Add the required methods to your User model and configure the login manager.
models.py
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model, UserMixin): # UserMixin adds is_authenticated etc.
# ... columns ...
def set_password(self, password):
self.password = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password, password)
# In __init__.py / create_app:
# @login.user_loader
# def load_user(user_id):
# return User.query.get(int(user_id))
02
Login & Logout Views
Use
login_user() to start a session and logout_user() to end it. @login_required protects any view.auth/routes.py
from flask_login import login_user, logout_user, login_required, current_user
@auth.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
if request.method == 'POST':
user = User.query.filter_by(email=request.form['email']).first()
if user and user.check_password(request.form['password']):
login_user(user, remember=request.form.get('remember') == 'on')
return redirect(request.args.get('next') or url_for('main.index'))
flash('Invalid credentials', 'danger')
return render_template('auth/login.html')
@auth.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('auth.login'))
03
Flask Sessions
The
session object stores data across requests (server-side cookie, signed with SECRET_KEY).python
from flask import session
# Store data in session
session['cart'] = [1, 2, 3]
# Read from session
cart = session.get('cart', [])
# Mark session as modified (for mutable objects)
session['cart'].append(4)
session.modified = True
# Clear session
session.clear()
Always hash passwords with
werkzeug.security.generate_password_hash(). Never store plain-text passwords, even in development.
Blueprints — Organizing Large Apps
Blueprints are Flask's way of splitting a large app into modular, reusable components. Each blueprint has its own routes, templates, and static files.
01
Creating a Blueprint
Define a blueprint in a package. Routes inside a blueprint are prefixed with the blueprint's name for
url_for() calls.auth/__init__.py
from flask import Blueprint
# name, import_name, template folder
auth = Blueprint('auth', __name__, template_folder='templates')
from . import routes # import routes to register them
02
Defining Routes in a Blueprint
Use
@blueprint_name.route() instead of @app.route(). In templates, reference routes as blueprint_name.view_name.auth/routes.py
from . import auth
from flask import render_template, redirect, url_for
@auth.route('/login')
def login():
return render_template('auth/login.html')
@auth.route('/register')
def register():
return render_template('auth/register.html')
# In templates, use: url_for('auth.login') — not just url_for('login')
03
Registering Blueprints in the App Factory
Register all blueprints inside
create_app(). The url_prefix prepends all blueprint routes.app/__init__.py
def create_app():
app = Flask(__name__)
from .auth import auth as auth_bp
from .main import main as main_bp
from .api import api as api_bp
app.register_blueprint(main_bp) # no prefix → '/'
app.register_blueprint(auth_bp, url_prefix='/auth') # → '/auth/login'
app.register_blueprint(api_bp, url_prefix='/api') # → '/api/users'
return app
Key Flask Extensions
Flask's power comes from its ecosystem. These are the extensions you'll use in almost every production Flask project.
flask-sqlalchemy
Flask-SQLAlchemy
ORM / Database
Integrates SQLAlchemy into Flask. Define models as Python classes, query with Pythonic syntax. Supports PostgreSQL, MySQL, SQLite.
flask-migrate
Flask-Migrate
Database Migrations
Wraps Alembic to give Flask apps
flask db migrate and flask db upgrade. Schema version control for your database.
flask-login
Flask-Login
Session Auth
Manages user sessions. Provides
login_user(), logout_user(), current_user, and the @login_required decorator.
flask-wtf
Flask-WTF
Forms & Validation
Integrates WTForms with Flask. Form classes with built-in validation, CSRF protection, and Jinja2 rendering helpers.
flask-cors
Flask-CORS
Cross-Origin Requests
Adds CORS headers to Flask responses. Essential when your frontend runs on a different domain or port than your Flask API.
flask-mail
Flask-Mail
Email
Send emails from Flask via SMTP. Used for password reset flows, notifications, and user verification emails.
bash — install all at once
pip install flask-sqlalchemy flask-migrate flask-login flask-wtf flask-cors flask-mail
Flask Complete Cheatsheet
Everything from boilerplate to SQLAlchemy CRUD — all patterns in one place.
① Essential Imports
python
from flask import (
Flask, render_template, redirect, url_for,
request, jsonify, flash, session, abort, make_response
)
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from flask_migrate import Migrate
from werkzeug.security import generate_password_hash, check_password_hash
② Minimal App Boilerplate
app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return '<p>Hello, World!</p>'
if __name__ == '__main__':
app.run(debug=True) # dev
# app.run(host='0.0.0.0', port=5001) # custom host/port
③ Routes, Methods & Request Data
python
@app.route('/submit', methods=['GET', 'POST'])
def submit():
if request.method == 'POST':
name = request.form['username'] # POST form data
page = request.args.get('page') # GET query param
data = request.get_json() # JSON body
return redirect(url_for('home'))
return render_template('submit.html', name='Harry')
④ Flash Messages & Sessions
python
# Flash — one-time messages
app.secret_key = 'your-secret-key'
flash('Data saved successfully!', 'success')
# Sessions — persist data between requests
session['user'] = 'Harry'
print(session.get('user'))
session.pop('user', None)
# JSON response
return jsonify({'status': 'ok', 'data': [1, 2, 3]})
jinja2 — flash in template
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
<div class="{{ category }}">{{ message }}</div>
{% endfor %}
{% endwith %}
⑤ SQLAlchemy — Setup & CRUD
python — setup & model
from flask_sqlalchemy import SQLAlchemy
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), nullable=False)
email = db.Column(db.String(120), nullable=False)
def __repr__(self):
return f'<User {self.username}>'
# Create all tables
with app.app_context():
db.create_all()
python — CRUD operations
# CREATE
entry = User(username='Harry', email='harry@example.com')
db.session.add(entry)
db.session.commit()
# READ
all_users = User.query.all()
first_user = User.query.first()
filtered = User.query.filter_by(username='Harry').all()
# UPDATE
user = User.query.first()
user.username = 'Updated'
db.session.commit()
# DELETE
db.session.delete(user)
db.session.commit()
⑥ Error Handling
python
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
⑦ Quick Reference
| Pattern / Command | What It Does |
|---|---|
Flask(__name__) | Create the app instance |
@app.route('/path', methods=[...]) | Register a route with allowed methods |
render_template('file.html', key=val) | Render a Jinja2 template with context vars |
redirect(url_for('view_name')) | Redirect to another named route |
request.form['key'] | Get POST form field value |
request.args.get('key') | Get URL query string parameter |
request.get_json() | Parse JSON request body |
jsonify(dict) | Return a JSON HTTP response |
flash('msg', 'success') | Add a one-time flash message |
session['key'] = value | Store data in signed session cookie |
abort(404) | Return an HTTP error response immediately |
flask run | Start the development server |
flask shell | Open interactive shell with app context |
flask db migrate -m "msg" | Generate a new Flask-Migrate migration |
flask db upgrade | Apply pending migrations to database |
Test Your Flask Knowledge
5 questions on core Flask concepts. Click an option to check your answer instantly.
1What does
url_for('auth.login') mean in a Flask app using Blueprints?2What is the Application Factory pattern (
create_app()) primarily used for?3How do you access JSON data from an incoming POST request in Flask?
4Which extension handles database schema version control in Flask (equivalent to Django's
makemigrations)?5What does
@login_required do when applied to a Flask view?6What is a Flask Blueprint?
7Flask
g vs session — g is…8Flask vs Django — typical interview answer?
9Testing Flask apps commonly uses…
10Application factory pattern benefit?
—
Keep practicing!