Merancang aplikasi Flask modular: memecah aplikasi menjadi blueprints dengan url prefix serta template dan static files sendiri, merangkai semuanya lewat application factory create_app(), dan menyusun struktur proyek yang scalable untuk aplikasi produksi.

Aplikasi di episode-episode sebelumnya hidup dalam satu file app.py — nyaman untuk belajar, tapi tidak bertahan saat aplikasi tumbuh: puluhan route, model, dan helper dalam satu file sulit dibaca dan diuji. Episode 9 mengajarkan dua pola yang membuat aplikasi Flask scalable: blueprints untuk memecah fitur menjadi modul, dan application factory untuk merangkai semuanya dengan rapi. Ini adalah episode struktur — setelah ini, seluruh series memakai arsitektur yang sama. Kuasai baik-baik.
Blueprint adalah "aplikasi mini" yang bisa punya route, template, static files, dan error handler sendiri, lalu diregistrasikan ke aplikasi induk. Contoh struktur nyata:
app/
__init__.py # create_app() + registrasi blueprint
config.py # config classes (episode 8)
extensions.py # init objek extension (db, login, dll)
models/
__init__.py
user.py
routes/
__init__.py
auth.py # blueprint auth
main.py # blueprint main
templates/
base.html
auth/
login.html
main/
index.html
static/
css/
js/
run.py # entry point `flask --app run`Setiap modul fitur (auth, main, admin) menjadi blueprint:
from flask import Blueprint, render_template
auth_bp = Blueprint(
"auth",
__name__,
url_prefix="/auth",
template_folder="templates",
static_folder="static",
)
@auth_bp.route("/login", methods=["GET", "POST"])
def login():
return render_template("auth/login.html")Perhatikan tiga argumen kunci: url_prefix="/auth" membuat semua route otomatis di bawah /auth/...; template_folder dan static_folder memungkinkan blueprint memakai folder template/static sendiri — berguna untuk aplikasi dengan banyak modul terpisah.
Default-nya, template blueprint digabung dengan template aplikasi. Dengan template_folder="templates" relatif terhadap lokasi blueprint, Flask mencari file template di folder tersebut terlebih dahulu — misalnya app/routes/auth/auth_templates/login.html untuk auth_bp.
Pola penamaan yang disarankan: prefix folder template dengan nama blueprint (auth/login.html) untuk menghindari bentrok nama antar blueprint. Untuk static per blueprint, static_folder="static" diekspos di /auth/static/... dan dirujuk dengan url_for('auth.static', filename='...').
Semua blueprint diregistrasikan di create_app():
from flask import Flask
from app.config import DevelopmentConfig, ProductionConfig, TestingConfig
from app.routes.auth import auth_bp
from app.routes.main import main_bp
def create_app(config: str | None = None) -> Flask:
app = Flask(__name__)
app.config.from_object(config or DevelopmentConfig)
app.register_blueprint(auth_bp)
app.register_blueprint(main_bp)
return appVersi di atas sudah memakai config classes (episode 8) — pilih config dari parameter atau environment. Entry point-nya sederhana:
from app import create_app
app = create_app()
if __name__ == "__main__":
app.run()Jalankan dengan flask --app run run. Keuntungan factory sekarang terlihat: testing bisa membuat instance dengan TestingConfig tanpa menyentuh config global.
Pola yang harus dihindari: menginisialisasi extension (database, login manager) di module level tanpa aplikasi. Pola yang benar — extension object di extensions.py, init di factory:
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
db = SQLAlchemy()
login_manager = LoginManager()from app.extensions import db, login_manager
def create_app(config) -> Flask:
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
login_manager.init_app(app)
app.register_blueprint(auth_bp)
app.register_blueprint(main_bp)
return appExtension dibuat sekali di extensions.py (tanpa aplikasi), lalu di-init_app(app) di dalam factory. Inilah extension pattern resmi Flask — memungkinkan beberapa instance aplikasi (test, prod) memakai extension yang sama tanpa bentrok state. Kita pakai pola ini terus dari episode 10 sampai seterusnya.
Note
Kesalahan klasik: memanggil db = SQLAlchemy(app) di module level, lalu testing gagal karena instance pertama terkunci pada config pertama. Pola db = SQLAlchemy() + db.init_app(app) di factory menyelesaikan masalah ini untuk selamanya.
Blueprint bisa punya error handler sendiri untuk scope-nya:
@auth_bp.app_errorhandler(404)
def auth_not_found(e: Exception) -> tuple[dict, int]:
return {"error": "halaman auth tidak ditemukan"}, 404@blueprint.app_errorhandler hanya menangkap error yang terjadi di route blueprint tersebut — berbeda dengan @app.errorhandler yang global. Berguna untuk API blueprint yang ingin format error berbeda dari halaman web.
Jangan juga berlebihan: satu blueprint per route/file. Pedoman praktis:
| Gejala | Solusi |
|---|---|
app.py ribuan baris | Pecah per fitur (auth, main, admin) |
| Dua blueprint saling butuh data | Pecahkan di layer service/model, bukan blueprint |
| Blueprint hanya berisi 2 route | Gabung ke blueprint yang serumpun |
| Setiap route bikin file sendiri | Terlalu pecah — grup fitur, bukan route |
Blueprint adalah batas fitur, bukan batas file. Kelompokkan route berdasarkan domain (auth, items, admin, api) — bukan per halaman.
url_prefix: semua blueprint tanpa prefix akan bertabrakan route-nya.db.init_app dipanggil dua kali: crash; panggil sekali di factory.import app untuk mengakses current_app — pakai proxy current_app dari flask (episode 2).Pada episode 9 ini, kalian telah menguasai arsitektur modular Flask.
Inti yang harus dibawa pulang:
url_prefix, template, dan static sendiri.create_app() merangkai config, extension, dan blueprint.db = SQLAlchemy() + db.init_app(app) di factory.Di episode 10 selanjutnya, kita masuk dunia data: SQLAlchemy & database — Flask-SQLAlchemy 3.x, models dan relationships, session scope, hingga CRUD dengan SQLite/PostgreSQL. Pastikan lab kalian siap, karena mulai sekarang aplikasi kita punya database!