What is Flask?

Flask is a lightweight, flexible Python web framework that provides the essentials for building web applications without imposing a specific structure or dependencies. Created by Armin Ronacher in 2010, Flask follows the "micro" philosophy - it's minimal at its core but easily extensible.

Think of Flask as a minimalist apartment - you get the essentials (walls, floor, ceiling), and you choose which furniture to add. Unlike Django's fully-furnished mansion, Flask gives you the freedom to pick exactly what you need.

Why Flask? The "Micro" Philosophy

Flask's minimalist approach offers unique advantages:

  • Simplicity: Learn the core in an afternoon, minimal boilerplate code
  • Flexibility: Choose your own database, ORM, template engine, or auth system
  • Lightweight: Small codebase, minimal dependencies, easy to understand
  • Control: You decide the structure and components of your application
  • Perfect for APIs: Excellent for building RESTful APIs and microservices
  • Easy Testing: Simple to test due to minimal magic and clear structure
  • Great Documentation: Clear, comprehensive, and beginner-friendly
  • Extensible: Rich ecosystem of extensions for any feature you need

Flask doesn't include an ORM, form validation, or admin panel by default - but you can add them with extensions when needed!

Flask vs Django: When to Choose Flask

Use Flask when:

  • Building APIs: Microservices, REST APIs, or GraphQL backends
  • Small to medium projects: Personal projects, prototypes, or MVPs
  • Learning web development: Simpler to understand than Django
  • You need flexibility: Want to choose your own tools and structure
  • Microservices architecture: Each service can be a small Flask app
  • Minimalist applications: Single-page apps, landing pages, or simple tools

Use Django when:

  • Complex applications: CMS, e-commerce, social networks
  • Need built-in admin: Django's admin panel saves weeks of development
  • Rapid development: Django's batteries-included approach is faster initially
  • Team with conventions: Django enforces structure (good for teams)

Many developers use both: Django for full-featured apps, Flask for APIs and microservices!

Getting Started: Hello Flask

# Install Flask
pip install flask

# Minimal Flask application (app.py)
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run(debug=True)

# Run the app
python app.py
# Visit http://127.0.0.1:5000/

# That's it! You have a working web application!

Routing: URL Patterns

Routes map URLs to Python functions. Flask makes routing elegant and intuitive.

from flask import Flask

app = Flask(__name__)

# Basic route
@app.route('/')
def home():
    return 'Home Page'

# Route with variable
@app.route('/user/')
def profile(username):
    return f'Profile page for {username}'

# Route with type converter
@app.route('/post/')
def show_post(post_id):
    return f'Post {post_id}'

# Multiple HTTP methods
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return 'Processing login...'
    return 'Login form'

# URL building (reverse routing)
from flask import url_for

@app.route('/user/')
def profile(username):
    return f'User: {username}'

# Elsewhere in your code
url = url_for('profile', username='alice')
# url = '/user/alice'

# Dynamic URLs with query parameters
@app.route('/search')
def search():
    query = request.args.get('q', '')
    return f'Searching for: {query}'
# URL: /search?q=flask

Templates: Jinja2 Templating

Flask uses Jinja2 for templates, allowing you to generate dynamic HTML.

# Flask view
from flask import render_template

@app.route('/user/')
def user(name):
    return render_template('user.html', username=name)

@app.route('/posts')
def posts():
    posts_list = [
        {'title': 'First Post', 'author': 'Alice'},
        {'title': 'Second Post', 'author': 'Bob'}
    ]
    return render_template('posts.html', posts=posts_list)
<!-- 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>
    <nav>
        <a href="{{ url_for('home') }}">Home</a>
        <a href="{{ url_for('posts') }}">Posts</a>
    </nav>

    <main>
        {% block content %}{% endblock %}
    </main>

    <footer>
        <p>&copy; 2024 My App</p>
    </footer>
</body>
</html>

<!-- templates/user.html -->
{% extends 'base.html' %}

{% block title %}{{ username }}'s Profile{% endblock %}

{% block content %}
    <h1>Welcome, {{ username }}!</h1>
    <p>This is your profile page.</p>
{% endblock %}

<!-- templates/posts.html -->
{% extends 'base.html' %}

{% block content %}
    <h1>Blog Posts</h1>

    {% for post in posts %}
        <article>
            <h2>{{ post.title }}</h2>
            <p>By {{ post.author }}</p>
        </article>
    {% else %}
        <p>No posts available.</p>
    {% endfor %}
{% endblock %}

Request and Response Handling

from flask import Flask, request, jsonify, redirect, url_for

app = Flask(__name__)

# Access request data
@app.route('/form', methods=['GET', 'POST'])
def form():
    if request.method == 'POST':
        # Form data
        username = request.form.get('username')
        email = request.form.get('email')

        # Query parameters (?key=value)
        page = request.args.get('page', 1, type=int)

        # JSON data
        data = request.get_json()

        # Headers
        user_agent = request.headers.get('User-Agent')

        # Files
        file = request.files.get('photo')
        if file:
            file.save(f'uploads/{file.filename}')

        return 'Form processed!'

    return '''
        <form method="post">
            <input name="username">
            <input name="email">
            <button>Submit</button>
        </form>
    '''

# Return JSON (perfect for APIs)
@app.route('/api/users')
def api_users():
    users = [
        {'id': 1, 'name': 'Alice'},
        {'id': 2, 'name': 'Bob'}
    ]
    return jsonify(users)

# Redirects
@app.route('/old-page')
def old_page():
    return redirect(url_for('home'))

# Custom status codes
@app.route('/not-found')
def not_found():
    return 'This page does not exist', 404

# Set cookies
from flask import make_response

@app.route('/set-cookie')
def set_cookie():
    response = make_response('Cookie set!')
    response.set_cookie('username', 'alice')
    return response

@app.route('/get-cookie')
def get_cookie():
    username = request.cookies.get('username')
    return f'Username: {username}'

Flask Extensions: Adding Features

Flask's extension ecosystem allows you to add features as needed:

# Install extensions
pip install flask-sqlalchemy flask-login flask-wtf flask-migrate

# Flask-SQLAlchemy (Database ORM)
from flask_sqlalchemy import SQLAlchemy

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)
    email = db.Column(db.String(120), unique=True)

    def __repr__(self):
        return f'<User {self.username}>'

# Create tables
with app.app_context():
    db.create_all()

# Flask-WTF (Forms with validation)
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, Email

class LoginForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        # Process login
        return redirect(url_for('home'))
    return render_template('login.html', form=form)

# Flask-Login (User session management)
from flask_login import LoginManager, login_user, logout_user, login_required

login_manager = LoginManager(app)

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

@app.route('/dashboard')
@login_required  # Must be logged in to access
def dashboard():
    return 'Dashboard'

# Flask-Migrate (Database migrations)
from flask_migrate import Migrate

migrate = Migrate(app, db)

# Commands:
# flask db init      (initialize migrations)
# flask db migrate   (create migration)
# flask db upgrade   (apply migration)

Building a REST API with Flask

from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///books.db'
db = SQLAlchemy(app)

class Book(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    author = db.Column(db.String(100), nullable=False)
    year = db.Column(db.Integer)

    def to_dict(self):
        return {
            'id': self.id,
            'title': self.title,
            'author': self.author,
            'year': self.year
        }

# GET all books
@app.route('/api/books', methods=['GET'])
def get_books():
    books = Book.query.all()
    return jsonify([book.to_dict() for book in books])

# GET single book
@app.route('/api/books/', methods=['GET'])
def get_book(id):
    book = Book.query.get_or_404(id)
    return jsonify(book.to_dict())

# POST create book
@app.route('/api/books', methods=['POST'])
def create_book():
    data = request.get_json()

    book = Book(
        title=data['title'],
        author=data['author'],
        year=data.get('year')
    )
    db.session.add(book)
    db.session.commit()

    return jsonify(book.to_dict()), 201

# PUT update book
@app.route('/api/books/', methods=['PUT'])
def update_book(id):
    book = Book.query.get_or_404(id)
    data = request.get_json()

    book.title = data.get('title', book.title)
    book.author = data.get('author', book.author)
    book.year = data.get('year', book.year)

    db.session.commit()
    return jsonify(book.to_dict())

# DELETE book
@app.route('/api/books/', methods=['DELETE'])
def delete_book(id):
    book = Book.query.get_or_404(id)
    db.session.delete(book)
    db.session.commit()
    return '', 204

# Error handling
@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': 'Not found'}), 404

@app.errorhandler(500)
def internal_error(error):
    return jsonify({'error': 'Internal server error'}), 500

Application Structure

For larger applications, organize code using Flask blueprints:

myapp/
├── app/
│   ├── __init__.py         # Application factory
│   ├── models.py           # Database models
│   ├── auth/               # Authentication blueprint
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   └── forms.py
│   ├── blog/               # Blog blueprint
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   └── forms.py
│   ├── api/                # API blueprint
│   │   ├── __init__.py
│   │   └── routes.py
│   ├── templates/
│   │   ├── base.html
│   │   ├── auth/
│   │   └── blog/
│   └── static/
│       ├── css/
│       └── js/
├── config.py               # Configuration
├── requirements.txt        # Dependencies
└── run.py                  # Entry point

# app/__init__.py (Application Factory)
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    app.config.from_object('config.Config')

    db.init_app(app)

    from app.auth import auth_bp
    from app.blog import blog_bp
    from app.api import api_bp

    app.register_blueprint(auth_bp, url_prefix='/auth')
    app.register_blueprint(blog_bp, url_prefix='/blog')
    app.register_blueprint(api_bp, url_prefix='/api')

    return app

# app/blog/__init__.py
from flask import Blueprint

blog_bp = Blueprint('blog', __name__)

from app.blog import routes

# app/blog/routes.py
from app.blog import blog_bp
from flask import render_template

@blog_bp.route('/')
def index():
    return render_template('blog/index.html')

# run.py
from app import create_app

app = create_app()

if __name__ == '__main__':
    app.run(debug=True)

Configuration and Environment Variables

# config.py
import os

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
        'sqlite:///app.db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False

class DevelopmentConfig(Config):
    DEBUG = True

class ProductionConfig(Config):
    DEBUG = False

# .env file
SECRET_KEY=your-secret-key
DATABASE_URL=postgresql://user:pass@localhost/dbname

# Load environment variables
from dotenv import load_dotenv
load_dotenv()

app.config.from_object('config.DevelopmentConfig')

Best Practices

  • Use application factory: Create app in a function for flexibility
  • Use blueprints: Organize code into modules for larger apps
  • Environment variables: Never hardcode secrets or config
  • Use Flask-SQLAlchemy: Better than raw SQLAlchemy with Flask
  • Error handling: Use @app.errorhandler for custom error pages
  • Use extensions: Don't reinvent wheels (auth, forms, migrations)
  • Debug mode only in development: Never in production!
  • Use HTTPS in production: Always secure your application
  • Write tests: Flask has excellent testing support
  • Use a production server: Gunicorn, uWSGI, not the dev server

Master Flask with Expert Mentorship

Our Full Stack Python program covers Flask alongside Django, teaching you when to use each framework. Build APIs, microservices, and lightweight web applications with personalized guidance.

Explore Full Stack Python Program

Related Articles