1. Pengenalan Flask
Flask adalah micro web framework Python yang dikembangkan oleh Armin Ronacher dan pertama kali dirilis pada tahun 2010. Disebut "micro" karena Flask memiliki inti yang minimalis β hanya menyediakan routing, request handling, dan templating. Fitur lain seperti database, autentikasi, dan form validation ditambahkan melalui extension.
Flask sangat cocok untuk proyek kecil-menengah, API, prototype, dan bagi developer yang menginginkan fleksibilitas penuh dalam memilih komponen yang digunakan.
Flask vs Django
| Aspek | Flask | Django |
|---|---|---|
| Tipe | Micro framework | Full-stack framework |
| Filosofi | Minimalis, fleksibel | Batteries included |
| Database | Flask-SQLAlchemy (extension) | Django ORM (built-in) |
| Admin Panel | Flask-Admin (extension) | Built-in |
| Template | Jinja2 | Django Template Language |
| URL Routing | Decorator-based | urls.py configuration |
| Cocok untuk | API, Microservice, Prototype | Web app besar, CMS |
| Learning Curve | π’ Mudah | π‘ Sedang |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β FLASK APPLICATION β β β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β Flask Core β β β β β’ Werkzeug (WSGI toolkit) β β β β β’ Jinja2 (Template engine) β β β β β’ URL Routing β β β β β’ Request/Response handling β β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β ββββββββββββ ββββββββββββ ββββββββββββββββββββ β β β Extensionβ β Extensionβ β Extension β β β β Flask- β β Flask- β β Flask- β β β β SQLAlchemyβ β Login β β WTF (Forms) β β β ββββββββββββ ββββββββββββ ββββββββββββββββββββ β β β β ββββββββββββ ββββββββββββ ββββββββββββββββββββ β β β Extensionβ β Extensionβ β Extension β β β β Flask- β β Flask- β β Flask- β β β β Admin β β Mail β β Migrate β β β ββββββββββββ ββββββββββββ ββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2. Instalasi dan Hello World
Setup Virtual Environment
# Buat folder proyek mkdir belajar-flask && cd belajar-flask # Buat virtual environment python -m venv venv source venv/bin/activate # macOS/Linux # venv\Scripts\activate # Windows # Instal Flask pip install flask # Verifikasi python -c "import flask; print(flask.__version__)" # Output: 3.0.3
Flask App Pertama
# app.py β Aplikasi Flask pertama
from flask import Flask
# Buat instance Flask
app = Flask(__name__)
# Route utama
@app.route('/')
def beranda():
return '<h1>Halo, selamat datang di Flask!</h1>'
# Route dengan parameter
@app.route('/sapa/<nama>')
def sapa(nama):
return f'<h1>Halo, {nama}! Selamat datang!</h1>'
# Jalankan development server
if __name__ == '__main__':
app.run(debug=True, port=5000)
# Jalankan dari terminal:
# python app.py
# Output:
# * Running on http://127.0.0.1:5000
# * Debug mode: on
Struktur Proyek Flask
belajar-flask/ βββ venv/ β Virtual environment βββ app/ β βββ __init__.py β Flask app factory β βββ routes.py β URL routes β βββ models.py β Database models β βββ forms.py β Form definitions β βββ config.py β Konfigurasi β βββ static/ β CSS, JS, images β β βββ css/ β β βββ js/ β β βββ images/ β βββ templates/ β HTML templates β βββ base.html β βββ beranda.html β βββ tentang.html βββ tests/ β Unit tests βββ requirements.txt β Dependencies βββ .env β Environment variables βββ run.py β Entry point
App Factory Pattern
# app/__init__.py β App Factory Pattern
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
def create_app(config_name='development'):
app = Flask(__name__)
# Load konfigurasi
app.config.from_object(f'app.config.{config_name.capitalize()}Config')
# Inisialisasi extensions
db.init_app(app)
migrate.init_app(app, db)
# Register blueprints
from app.routes import main_bp
app.register_blueprint(main_bp)
from app.api import api_bp
app.register_blueprint(api_bp, url_prefix='/api')
return app
Gunakan App Factory Pattern (create_app()) untuk proyek yang lebih besar. Pola ini memudahkan testing, konfigurasi multiple environments, dan organisasi kode yang lebih baik.
3. Routing dan URL
Routing di Flask menggunakan decorator @app.route() yang memetakan URL ke fungsi Python. Flask mendukung URL variables, HTTP methods, dan URL building.
from flask import Blueprint, request, redirect, url_for, abort, jsonify
main_bp = Blueprint('main', __name__)
# Route dasar
@main_bp.route('/')
def beranda():
return 'Halaman Beranda'
# Route dengan HTTP methods
@main_bp.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
# Proses login...
return f'Login berhasil: {username}'
return 'Halaman Login'
# URL variables β tipe data
@main_bp.route('/artikel/<int:id>')
def artikel_by_id(id):
return f'Artikel ID: {id}'
@main_bp.route('/user/<username>')
def profil(username):
return f'Profil: {username}'
@main_bp.route('/file/<path:filepath>')
def file_path(filepath):
return f'File: {filepath}'
# Multiple URL untuk satu fungsi
@main_bp.route('/beranda')
@main_bp.route('/home')
def home():
return redirect(url_for('main.beranda'))
# URL building β generate URL dari fungsi
@main_bp.route('/link-demo')
def link_demo():
url1 = url_for('main.beranda') # /
url2 = url_for('main.artikel_by_id', id=5) # /artikel/5
url3 = url_for('main.profil', username='budi') # /user/budi
return f'URL1: {url1}, URL2: {url2}, URL3: {url3}'
# Error handlers
@main_bp.errorhandler(404)
def not_found(error):
return '<h1>404 β Halaman Tidak Ditemukan</h1>', 404
@main_bp.errorhandler(500)
def server_error(error):
return '<h1>500 β Server Error</h1>', 500
# Blueprint dengan sub-bp
from flask import Blueprint
api_bp = Blueprint('api', __name__)
@api_bp.route('/artikel')
def get_artikel():
return jsonify({'artikel': []})
4. Templates dengan Jinja2
Flask menggunakan Jinja2 sebagai template engine. Jinja2 memungkinkan Anda menulis HTML yang dinamis dengan sintaks yang mirip Python.
Template Dasar
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Flask App{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
{% block extra_css %}{% endblock %}
</head>
<body>
<nav class="navbar">
<a href="{{ url_for('main.beranda') }}">FlaskApp</a>
<ul>
<li><a href="{{ url_for('main.beranda') }}">Beranda</a></li>
<li><a href="{{ url_for('main.tentang') }}">Tentang</a></li>
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('main.profil', username=current_user.username) }}">Profil</a></li>
{% else %}
<li><a href="{{ url_for('main.login') }}">Login</a></li>
{% endif %}
</ul>
</nav>
<!-- Flash messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<main class="container">
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2026 FlaskApp</p>
</footer>
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
Template Anak
{% extends 'base.html' %}
{% block title %}Beranda{% endblock %}
{% block content %}
<h1>{{ judul }}</h1>
<p>{{ deskripsi }}</p>
<div class="artikel-grid">
{% for artikel in artikel_list %}
<article class="card">
<h2><a href="{{ url_for('main.detail', id=artikel.id) }}">{{ artikel.judul }}</a></h2>
<p class="meta">{{ artikel.tanggal|format_date }} oleh {{ artikel.penulis }}</p>
<p>{{ artikel.konten|truncate(200) }}</p>
<div class="tags">
{% for tag in artikel.tags %}
<span class="badge">{{ tag }}</span>
{% endfor %}
</div>
</article>
{% else %}
<p class="empty">Belum ada artikel.</p>
{% endfor %}
</div>
<!-- Macro untuk reusable component -->
{% macro card(judul, isi) %}
<div class="card">
<h3>{{ judul }}</h3>
<p>{{ isi }}</p>
</div>
{% endmacro %}
{{ card("Tips Flask", "Selalu gunakan virtual environment!") }}
{{ card("Tips Jinja2", "Gunakan macro untuk komponen yang sering dipakai") }}
{% endblock %}
Render Template dari View
from flask import render_template, flash, redirect, url_for
@main_bp.route('/')
def beranda():
artikel_list = Artikel.query.filter_by(status='published').all()
return render_template('beranda.html',
judul='Selamat Datang',
deskripsi='Blog sederhana dengan Flask',
artikel_list=artikel_list
)
# Flash message
@main_bp.route('/submit', methods=['POST'])
def submit():
# Proses data...
flash('Data berhasil disimpan!', 'success')
return redirect(url_for('main.beranda'))
5. Forms dan Input
Flask menggunakan extension Flask-WTF untuk menangani form, validasi, dan proteksi CSRF. Flask-WTF dibangun di atas library WTForms.
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SelectField, SubmitField, PasswordField
from wtforms.validators import DataRequired, Email, Length, EqualTo, URL
class LoginForm(FlaskForm):
username = StringField('Username', validators=[
DataRequired(message="Username wajib diisi"),
Length(min=3, max=50)
])
password = PasswordField('Password', validators=[
DataRequired(message="Password wajib diisi"),
Length(min=6)
])
submit = SubmitField('Login')
class ArtikelForm(FlaskForm):
judul = StringField('Judul', validators=[
DataRequired(),
Length(min=10, max=200, message="Judul 10-200 karakter")
])
konten = TextAreaField('Konten', validators=[
DataRequired(),
Length(min=50, message="Konten minimal 50 karakter")
])
kategori = SelectField('Kategori', coerce=int, validators=[DataRequired()])
gambar_url = StringField('URL Gambar', validators=[URL()])
submit = SubmitField('Simpan Artikel')
class KomentarForm(FlaskForm):
nama = StringField('Nama', validators=[DataRequired(), Length(max=100)])
email = StringField('Email', validators=[DataRequired(), Email()])
isi = TextAreaField('Komentar', validators=[DataRequired(), Length(min=5)])
submit = SubmitField('Kirim Komentar')
{% extends 'base.html' %}
{% block title %}Buat Artikel{% endblock %}
{% block content %}
<h1>Buat Artikel Baru</h1>
<form method="POST" enctype="multipart/form-data" novalidate>
{{ form.hidden_tag() }}
{% for field in [form.judul, form.konten, form.kategori, form.gambar_url] %}
<div class="form-group">
{{ field.label(class='form-label') }}
{{ field(class='form-control') }}
{% for error in field.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
</div>
{% endfor %}
{{ form.submit(class='btn btn-primary') }}
</form>
{% endblock %}
@main_bp.route('/artikel/baru', methods=['GET', 'POST'])
@login_required
def buat_artikel():
form = ArtikelForm()
form.kategori.choices = [(k.id, k.nama) for k in Kategori.query.all()]
if form.validate_on_submit():
artikel = Artikel(
judul=form.judul.data,
konten=form.konten.data,
kategori_id=form.kategori.data,
gambar_url=form.gambar_url.data,
penulis=current_user
)
db.session.add(artikel)
db.session.commit()
flash('Artikel berhasil dibuat!', 'success')
return redirect(url_for('main.detail', id=artikel.id))
return render_template('form_artikel.html', form=form)
6. Database dengan SQLAlchemy
Flask menggunakan Flask-SQLAlchemy sebagai ORM untuk berinteraksi dengan database. SQLAlchemy mendukung SQLite, PostgreSQL, MySQL, dan lainnya.
from app import db
from datetime import datetime
from flask_login import UserMixin
class User(UserMixin, db.Model):
__tablename__ = 'users'
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(256), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Relationship
artikel = db.relationship('Artikel', backref='penulis', lazy='dynamic')
def __repr__(self):
return f'<User {self.username}>'
class Kategori(db.Model):
__tablename__ = 'kategori'
id = db.Column(db.Integer, primary_key=True)
nama = db.Column(db.String(100), unique=True, nullable=False)
slug = db.Column(db.String(100), unique=True, nullable=False)
def __repr__(self):
return f'<Kategori {self.nama}>'
class Artikel(db.Model):
__tablename__ = 'artikel'
id = db.Column(db.Integer, primary_key=True)
judul = db.Column(db.String(200), nullable=False)
slug = db.Column(db.String(200), unique=True, nullable=False)
konten = db.Column(db.Text, nullable=False)
status = db.Column(db.String(20), default='draft')
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Foreign Keys
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
kategori_id = db.Column(db.Integer, db.ForeignKey('kategori.id'))
# Relationship
komentar = db.relationship('Komentar', backref='artikel', lazy='dynamic',
cascade='all, delete-orphan')
def __repr__(self):
return f'<Artikel {self.judul}>'
class Komentar(db.Model):
__tablename__ = 'komentar'
id = db.Column(db.Integer, primary_key=True)
nama = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(120), nullable=False)
isi = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
artikel_id = db.Column(db.Integer, db.ForeignKey('artikel.id'), nullable=False)
CRUD Operations
from app import db
from app.models import Artikel, Kategori, User
# CREATE β Tambah data baru
kategori = Kategori(nama='Python', slug='python')
db.session.add(kategori)
db.session.commit()
artikel = Artikel(
judul='Tutorial Flask',
slug='tutorial-flask',
konten='Ini konten tutorial...',
user_id=1,
kategori_id=1
)
db.session.add(artikel)
db.session.commit()
# READ β Query data
semua = Artikel.query.all() # Semua data
artikel = Artikel.query.get(1) # By ID
artikel = Artikel.query.filter_by(status='published').first() # Filter
artikel = Artikel.query.filter(Artikel.judul.contains('Flask')) # Search
# Order, limit, paginate
terbaru = Artikel.query.order_by(Artikel.created_at.desc()).limit(10).all()
page = Artikel.query.paginate(page=1, per_page=10, error_out=False)
# Join
artikel_kategori = Artikel.query.join(Kategori).filter(Kategori.nama == 'Python').all()
# UPDATE β Ubah data
artikel = Artikel.query.get(1)
artikel.judul = 'Judul Baru'
artikel.status = 'published'
db.session.commit()
# DELETE β Hapus data
artikel = Artikel.query.get(1)
db.session.delete(artikel)
db.session.commit()
7. Membuat REST API
Flask sangat cocok untuk membuat REST API karena sifatnya yang minimalis. Berikut contoh membuat API lengkap dengan CRUD.
from flask import Blueprint, request, jsonify
from app.models import Artikel, db
from app import db
api_bp = Blueprint('api', __name__)
# GET semua artikel
@api_bp.route('/artikel', methods=['GET'])
def get_artikel():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
status = request.args.get('status', 'published')
query = Artikel.query.filter_by(status=status)
pagination = query.paginate(page=page, per_page=per_page)
return jsonify({
'artikel': [{
'id': a.id,
'judul': a.judul,
'slug': a.slug,
'konten': a.konten[:200] + '...',
'penulis': a.penulis.username,
'created_at': a.created_at.isoformat()
} for a in pagination.items],
'total': pagination.total,
'pages': pagination.pages,
'current_page': pagination.page
})
# GET satu artikel
@api_bp.route('/artikel/<int:id>', methods=['GET'])
def get_artikel_by_id(id):
artikel = Artikel.query.get_or_404(id)
return jsonify({
'id': artikel.id,
'judul': artikel.judul,
'slug': artikel.slug,
'konten': artikel.konten,
'penulis': artikel.penulis.username,
'kategori': artikel.kategori.nama if artikel.kategori else None,
'komentar': [{
'nama': k.nama,
'isi': k.isi,
'tanggal': k.created_at.isoformat()
} for k in artikel.komentar.all()],
'created_at': artikel.created_at.isoformat()
})
# POST β buat artikel baru
@api_bp.route('/artikel', methods=['POST'])
def create_artikel():
data = request.get_json()
if not data or not data.get('judul') or not data.get('konten'):
return jsonify({'error': 'judul dan konten wajib diisi'}), 400
artikel = Artikel(
judul=data['judul'],
slug=data['judul'].lower().replace(' ', '-'),
konten=data['konten'],
user_id=data.get('user_id', 1),
status=data.get('status', 'draft')
)
db.session.add(artikel)
db.session.commit()
return jsonify({
'message': 'Artikel berhasil dibuat',
'id': artikel.id
}), 201
# PUT β update artikel
@api_bp.route('/artikel/<int:id>', methods=['PUT'])
def update_artikel(id):
artikel = Artikel.query.get_or_404(id)
data = request.get_json()
if data.get('judul'):
artikel.judul = data['judul']
if data.get('konten'):
artikel.konten = data['konten']
if data.get('status'):
artikel.status = data['status']
db.session.commit()
return jsonify({'message': 'Artikel berhasil diupdate'})
# DELETE β hapus artikel
@api_bp.route('/artikel/<int:id>', methods=['DELETE'])
def delete_artikel(id):
artikel = Artikel.query.get_or_404(id)
db.session.delete(artikel)
db.session.commit()
return jsonify({'message': 'Artikel berhasil dihapus'})
# Error handler untuk API
@api_bp.errorhandler(404)
def not_found(error):
return jsonify({'error': 'Resource tidak ditemukan'}), 404
@api_bp.errorhandler(500)
def server_error(error):
return jsonify({'error': 'Internal server error'}), 500
8. Deployment
Setelah aplikasi selesai, saatnya deploy ke server produksi. Berikut beberapa opsi deployment Flask.
Konfigurasi untuk Produksi
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'rahasia-produksi-ganti-ini'
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') or \
'postgresql://user:pass@localhost/flask_prod'
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///test.db'
Docker Deployment
# Dockerfile FROM python:3.12-slim WORKDIR /app # Instal dependencies COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy source code COPY . . # Expose port EXPOSE 5000 # Jalankan dengan Gunicorn CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "run:app"]
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "5000:5000"
environment:
- FLASK_ENV=production
- DATABASE_URL=postgresql://postgres:password@db:5432/flask_app
- SECRET_KEY=your-secret-key-here
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_DB=flask_app
- POSTGRES_PASSWORD=password
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Deployment Platforms
| Platform | Gratis? | Keunggulan |
|---|---|---|
| Render | β Ya | Mudah setup, auto-deploy dari Git |
| Railway | β Ya (trial) | Deploy cepat, DB managed |
| Vercel | β Ya | CDN global, serverless functions |
| PythonAnywhere | β Ya | Spesialis Python, mudah setup |
| AWS / GCP / Azure | π° Bayar | Skalabilitas enterprise |
9. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Flask: