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 / CommandWhat 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'] = valueStore data in signed session cookie
abort(404)Return an HTTP error response immediately
flask runStart the development server
flask shellOpen interactive shell with app context
flask db migrate -m "msg"Generate a new Flask-Migrate migration
flask db upgradeApply 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 sessiong is…
8Flask vs Django — typical interview answer?
9Testing Flask apps commonly uses…
10Application factory pattern benefit?
Keep practicing!