Flask is een van de meest populaire Python web frameworks, en dat is niet zonder reden. Als "micro-framework" biedt Flask de essentiële tools voor web development zonder je te overweldigen met complexiteit. In mijn 10 jaar ervaring als full-stack developer bij Nederlandse startups en scale-ups, heb ik gezien hoe Flask teams in staat stelt om snel van idee naar werkende applicatie te gaan.
In dit artikel nemen we je mee van je eerste Flask route tot het bouwen van complexe, schaalbare web applicaties. We behandelen real-world voorbeelden en best practices die ik dagelijks gebruik in mijn werk.
Waarom Flask?
Flexibiliteit en Simpliciteit
Flask volgt de filosofie "micro but mighty". Het framework geeft je de vrijheid om je eigen architecturale keuzes te maken, zonder je te dwingen tot een specifieke structuur zoals Django doet.
Ideaal voor Nederlandse Startups
In Nederland's snelle startup ecosystem is Flask populair omdat:
- Snelle prototyping: Van MVP tot product in weken
- Lage learning curve: Developers kunnen snel productief zijn
- Schaalbaar: Van prototype tot miljoenen gebruikers
- API-first: Perfect voor moderne, microservice architecturen
Je Eerste Flask Applicatie
Installatie en Setup
# Virtual environment aanmaken
python -m venv flask_env
source flask_env/bin/activate # Linux/Mac
# flask_env\Scripts\activate # Windows
# Flask installeren
pip install Flask
# Optionele packages voor deze tutorial
pip install Flask-SQLAlchemy Flask-Login Flask-WTF
Hello World - De Basis
# app.py
from flask import Flask
# Flask app initialiseren
app = Flask(__name__)
# Eerste route
@app.route('/')
def hello_world():
return '<h1>Hallo, Flask wereld!</h1>'
# Route met parameter
@app.route('/user/<name>')
def show_user_profile(name):
return f'<h1>Welkom, {name}!</h1>'
# HTTP methoden
@app.route('/submit', methods=['GET', 'POST'])
def submit_data():
if request.method == 'POST':
return 'Data ontvangen via POST'
else:
return 'Stuur data via POST'
if __name__ == '__main__':
app.run(debug=True)
Start je app met python app.py
en bezoek http://localhost:5000
.
Templates en Static Files
Jinja2 Templates
Flask gebruikt Jinja2 voor templating. Maak een templates/
map aan:
# templates/base.html
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Mijn Flask App{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{{ url_for('index') }}">Mijn App</a>
</div>
</nav>
<div class="container mt-4">
{% block content %}{% endblock %}
</div>
</body>
</html>
# templates/index.html
{% extends "base.html" %}
{% block title %}Home - Mijn Flask App{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8">
<h1>Welkom bij onze Flask App</h1>
<p>Dit is een voorbeeld van een moderne Flask applicatie.</p>
{% if users %}
<h3>Geregistreerde gebruikers:</h3>
<ul class="list-group">
{% for user in users %}
<li class="list-group-item">{{ user.name }} - {{ user.email }}</li>
{% endfor %}
</ul>
{% else %}
<p>Nog geen gebruikers geregistreerd.</p>
{% endif %}
</div>
</div>
{% endblock %}
Updated Flask App met Templates
from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)
# Mock data (later vervangen door database)
users = [
{'name': 'Jan Jansen', 'email': '[email protected]'},
{'name': 'Marie van der Berg', 'email': '[email protected]'}
]
@app.route('/')
def index():
return render_template('index.html', users=users)
@app.route('/add_user', methods=['GET', 'POST'])
def add_user():
if request.method == 'POST':
name = request.form['name']
email = request.form['email']
users.append({'name': name, 'email': email})
return redirect(url_for('index'))
return render_template('add_user.html')
if __name__ == '__main__':
app.run(debug=True)
Database Integratie met SQLAlchemy
Model Definitie
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'jouw-geheime-sleutel-hier'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# User model
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def __repr__(self):
return f'<User {self.name}>'
# Post model (voor blog functionality)
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)
author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
author = db.relationship('User', backref=db.backref('posts', lazy=True))
# Database aanmaken
with app.app_context():
db.create_all()
CRUD Operaties
@app.route('/')
def index():
users = User.query.all()
posts = Post.query.order_by(Post.created_at.desc()).limit(5).all()
return render_template('index.html', users=users, posts=posts)
@app.route('/add_user', methods=['GET', 'POST'])
def add_user():
if request.method == 'POST':
name = request.form['name']
email = request.form['email']
# Check if email already exists
existing_user = User.query.filter_by(email=email).first()
if existing_user:
return render_template('add_user.html', error='Email bestaat al!')
# Create new user
user = User(name=name, email=email)
db.session.add(user)
db.session.commit()
return redirect(url_for('index'))
return render_template('add_user.html')
@app.route('/user/<int:user_id>')
def user_profile(user_id):
user = User.query.get_or_404(user_id)
return render_template('user_profile.html', user=user)
@app.route('/add_post', methods=['GET', 'POST'])
def add_post():
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
author_id = request.form['author_id']
post = Post(title=title, content=content, author_id=author_id)
db.session.add(post)
db.session.commit()
return redirect(url_for('index'))
users = User.query.all()
return render_template('add_post.html', users=users)
RESTful API Ontwikkeling
JSON API Endpoints
from flask import jsonify
# API Routes
@app.route('/api/users', methods=['GET'])
def api_get_users():
users = User.query.all()
return jsonify([{
'id': user.id,
'name': user.name,
'email': user.email,
'created_at': user.created_at.isoformat()
} for user in users])
@app.route('/api/users', methods=['POST'])
def api_create_user():
data = request.get_json()
# Validation
if not data or not data.get('name') or not data.get('email'):
return jsonify({'error': 'Name and email required'}), 400
# Check if email exists
if User.query.filter_by(email=data['email']).first():
return jsonify({'error': 'Email already exists'}), 400
# Create user
user = User(name=data['name'], email=data['email'])
db.session.add(user)
db.session.commit()
return jsonify({
'id': user.id,
'name': user.name,
'email': user.email,
'created_at': user.created_at.isoformat()
}), 201
@app.route('/api/users/<int:user_id>', methods=['GET'])
def api_get_user(user_id):
user = User.query.get_or_404(user_id)
return jsonify({
'id': user.id,
'name': user.name,
'email': user.email,
'created_at': user.created_at.isoformat(),
'posts': [{
'id': post.id,
'title': post.title,
'content': post.content,
'created_at': post.created_at.isoformat()
} for post in user.posts]
})
@app.route('/api/users/<int:user_id>', methods=['PUT'])
def api_update_user(user_id):
user = User.query.get_or_404(user_id)
data = request.get_json()
if 'name' in data:
user.name = data['name']
if 'email' in data:
# Check if new email already exists (excluding current user)
existing = User.query.filter(User.email == data['email'], User.id != user_id).first()
if existing:
return jsonify({'error': 'Email already exists'}), 400
user.email = data['email']
db.session.commit()
return jsonify({
'id': user.id,
'name': user.name,
'email': user.email,
'created_at': user.created_at.isoformat()
})
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
def api_delete_user(user_id):
user = User.query.get_or_404(user_id)
db.session.delete(user)
db.session.commit()
return '', 204
Authenticatie en Beveiliging
User Authentication
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash, check_password_hash
# Login manager setup
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# Update User model voor authentication
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
created_at = db.Column(db.DateTime, default=datetime.utcnow)
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)
# Flask-Login required methods
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return str(self.id)
# Authentication routes
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
name = request.form['name']
email = request.form['email']
password = request.form['password']
# Check if user exists
if User.query.filter_by(email=email).first():
return render_template('register.html', error='Email bestaat al!')
# Create user
user = User(name=name, email=email)
user.set_password(password)
db.session.add(user)
db.session.commit()
login_user(user)
return redirect(url_for('index'))
return render_template('register.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
email = request.form['email']
password = request.form['password']
user = User.query.filter_by(email=email).first()
if user and user.check_password(password):
login_user(user)
return redirect(url_for('index'))
else:
return render_template('login.html', error='Ongeldige inloggegevens!')
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('index'))
# Protected route example
@app.route('/dashboard')
@login_required
def dashboard():
return render_template('dashboard.html', user=current_user)
Real-World Example: Nederlandse Webshop API
Laten we een praktisch voorbeeld bouwen: een API voor een Nederlandse webshop.
# models.py
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
naam = db.Column(db.String(200), nullable=False)
beschrijving = db.Column(db.Text)
prijs = db.Column(db.Numeric(10, 2), nullable=False)
voorraad = db.Column(db.Integer, default=0)
categorie_id = db.Column(db.Integer, db.ForeignKey('categorie.id'))
created_at = db.Column(db.DateTime, default=datetime.utcnow)
categorie = db.relationship('Categorie', backref='producten')
class Categorie(db.Model):
id = db.Column(db.Integer, primary_key=True)
naam = db.Column(db.String(100), nullable=False, unique=True)
beschrijving = db.Column(db.Text)
class Bestelling(db.Model):
id = db.Column(db.Integer, primary_key=True)
klant_email = db.Column(db.String(120), nullable=False)
totaal_bedrag = db.Column(db.Numeric(10, 2), nullable=False)
status = db.Column(db.String(50), default='nieuw')
created_at = db.Column(db.DateTime, default=datetime.utcnow)
class BestellingItem(db.Model):
id = db.Column(db.Integer, primary_key=True)
bestelling_id = db.Column(db.Integer, db.ForeignKey('bestelling.id'))
product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
aantal = db.Column(db.Integer, nullable=False)
prijs = db.Column(db.Numeric(10, 2), nullable=False)
bestelling = db.relationship('Bestelling', backref='items')
product = db.relationship('Product')
# webshop_api.py
from flask import Flask, request, jsonify
from models import db, Product, Categorie, Bestelling, BestellingItem
from decimal import Decimal
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///webshop.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
# Products API
@app.route('/api/producten', methods=['GET'])
def get_producten():
categorie_id = request.args.get('categorie_id')
zoekterm = request.args.get('q')
query = Product.query
if categorie_id:
query = query.filter_by(categorie_id=categorie_id)
if zoekterm:
query = query.filter(Product.naam.contains(zoekterm))
producten = query.all()
return jsonify([{
'id': p.id,
'naam': p.naam,
'beschrijving': p.beschrijving,
'prijs': float(p.prijs),
'voorraad': p.voorraad,
'categorie': p.categorie.naam if p.categorie else None
} for p in producten])
@app.route('/api/producten/<int:product_id>', methods=['GET'])
def get_product(product_id):
product = Product.query.get_or_404(product_id)
return jsonify({
'id': product.id,
'naam': product.naam,
'beschrijving': product.beschrijving,
'prijs': float(product.prijs),
'voorraad': product.voorraad,
'categorie': {
'id': product.categorie.id,
'naam': product.categorie.naam
} if product.categorie else None
})
# Bestellingen API
@app.route('/api/bestellingen', methods=['POST'])
def create_bestelling():
data = request.get_json()
# Validation
if not data.get('klant_email') or not data.get('items'):
return jsonify({'error': 'Klant email en items zijn verplicht'}), 400
# Calculate total and check stock
totaal_bedrag = Decimal('0')
for item_data in data['items']:
product = Product.query.get(item_data['product_id'])
if not product:
return jsonify({'error': f'Product {item_data["product_id"]} niet gevonden'}), 400
if product.voorraad < item_data['aantal']:
return jsonify({'error': f'Onvoldoende voorraad voor {product.naam}'}), 400
totaal_bedrag += product.prijs * item_data['aantal']
# Create order
bestelling = Bestelling(
klant_email=data['klant_email'],
totaal_bedrag=totaal_bedrag
)
db.session.add(bestelling)
db.session.flush() # Get the ID
# Add items and update stock
for item_data in data['items']:
product = Product.query.get(item_data['product_id'])
item = BestellingItem(
bestelling_id=bestelling.id,
product_id=product.id,
aantal=item_data['aantal'],
prijs=product.prijs
)
db.session.add(item)
# Update stock
product.voorraad -= item_data['aantal']
db.session.commit()
return jsonify({
'id': bestelling.id,
'totaal_bedrag': float(bestelling.totaal_bedrag),
'status': bestelling.status
}), 201
@app.route('/api/bestellingen/<int:bestelling_id>', methods=['GET'])
def get_bestelling(bestelling_id):
bestelling = Bestelling.query.get_or_404(bestelling_id)
return jsonify({
'id': bestelling.id,
'klant_email': bestelling.klant_email,
'totaal_bedrag': float(bestelling.totaal_bedrag),
'status': bestelling.status,
'created_at': bestelling.created_at.isoformat(),
'items': [{
'product': {
'id': item.product.id,
'naam': item.product.naam
},
'aantal': item.aantal,
'prijs': float(item.prijs)
} for item in bestelling.items]
})
if __name__ == '__main__':
with app.app_context():
db.create_all()
# Sample data
if not Categorie.query.first():
tech = Categorie(naam='Technologie', beschrijving='Elektronische apparaten')
books = Categorie(naam='Boeken', beschrijving='Fysieke en digitale boeken')
db.session.add_all([tech, books])
db.session.commit()
laptop = Product(
naam='MacBook Pro 16"',
beschrijving='Krachtige laptop voor professionals',
prijs=Decimal('2499.99'),
voorraad=10,
categorie_id=tech.id
)
python_book = Product(
naam='Python voor Data Science',
beschrijving='Complete gids voor data science met Python',
prijs=Decimal('39.99'),
voorraad=50,
categorie_id=books.id
)
db.session.add_all([laptop, python_book])
db.session.commit()
app.run(debug=True)
Deployment en Production
Configuratie Management
# config.py
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///app_dev.db'
class ProductionConfig(Config):
DEBUG = False
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///app.db'
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig,
'default': DevelopmentConfig
}
Application Factory Pattern
# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from config import config
db = SQLAlchemy()
login_manager = LoginManager()
def create_app(config_name='default'):
app = Flask(__name__)
app.config.from_object(config[config_name])
# Initialize extensions
db.init_app(app)
login_manager.init_app(app)
login_manager.login_view = 'auth.login'
# Register blueprints
from app.main import bp as main_bp
app.register_blueprint(main_bp)
from app.auth import bp as auth_bp
app.register_blueprint(auth_bp, url_prefix='/auth')
from app.api import bp as api_bp
app.register_blueprint(api_bp, url_prefix='/api')
return app
Best Practices en Tips
1. Error Handling
@app.errorhandler(404)
def not_found(error):
if request.path.startswith('/api/'):
return jsonify({'error': 'Resource not found'}), 404
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
if request.path.startswith('/api/'):
return jsonify({'error': 'Internal server error'}), 500
return render_template('500.html'), 500
2. Input Validation
from marshmallow import Schema, fields, validate
class UserSchema(Schema):
name = fields.Str(required=True, validate=validate.Length(min=2, max=100))
email = fields.Email(required=True)
age = fields.Int(validate=validate.Range(min=0, max=120))
user_schema = UserSchema()
@app.route('/api/users', methods=['POST'])
def create_user():
try:
data = user_schema.load(request.json)
except ValidationError as err:
return jsonify({'errors': err.messages}), 400
# Process valid data...
3. Database Migrations
# Flask-Migrate installeren
pip install Flask-Migrate
# In je app
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
Conclusie
Flask biedt de perfecte balans tussen simpliciteit en kracht voor moderne web development. Met zijn flexibele architectuur en uitgebreide ecosystem kun je alles bouwen, van eenvoudige prototypes tot complexe enterprise applicaties.
De key takeaways uit dit artikel:
- Start simpel: Begin met basic routes en bouw geleidelijk uit
- Gebruik blueprints: Voor betere code organisatie
- Security first: Implementeer authentication en input validation
- API-driven: Bouw moderne, schaalbare architecturen
- Test alles: Gebruik pytest voor comprehensive testing
Ready to master Flask? Bij ImmenArchl leren onze studenten niet alleen de syntax, maar ook industry best practices en real-world patterns die je direct kunt toepassen in je werk.
Pro Tip van een Senior Developer
"Flask's kracht ligt in zijn flexibiliteit, maar dit kan ook overweldigend zijn voor beginners. Mijn advies: begin met een duidelijke project structuur en voeg functionaliteit stap voor stap toe. Gebruik blueprints vanaf dag één, ook voor kleine projecten. Je toekomstige zelf zal je dankbaar zijn!"
- Marcus Janssen, Full-Stack Developer & Instructeur