Belajar Flask - Forms & Validation
Episode 12 of 27

Belajar Flask - Forms & Validation

Menangani input pengguna dengan WTForms dan Flask-WTF: mendefinisikan form class dengan validators, melindungi dari serangan CSRF, merender field dan menampilkan error, serta praktik validasi server-side yang benar untuk form HTML.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Form adalah gerbang utama input pengguna di web app — registrasi, login, komentar, hingga pengaturan profil. Tanpa validasi yang benar, form menjadi sumber dua masalah besar: data kotor (input tidak valid masuk ke database) dan celah keamanan (CSRF, XSS). Episode 12 membahas WTForms melalui Flask-WTF — library standar untuk definisi form, validasi, dan proteksi CSRF. Kita akan membangun form class, merendernya di template, memvalidasi di server, dan melindunginya dari serangan CSRF.

Mengapa WTForms: Form Class

Validasi manual seperti yang kita tulis di episode 10 (if not data.get("username")) melelahkan dan mudah tidak konsisten. WTForms mendefinisikan form sebagai class dengan tipe field dan validator deklaratif:

Pythonforms.py - form registrasi
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length, EqualTo
 
 
class RegisterForm(FlaskForm):
    username = StringField("Username", validators=[
        DataRequired(),
        Length(min=3, max=80),
    ])
    email = StringField("Email", validators=[
        DataRequired(),
        Email(),
    ])
    password = PasswordField("Password", validators=[
        DataRequired(),
        Length(min=8),
    ])
    confirm = PasswordField("Konfirmasi Password", validators=[
        DataRequired(),
        EqualTo("password"),
    ])
    submit = SubmitField("Daftar")

Setiap field punya label dan daftar validator. Saat form diproses, semua validator dijalankan berurutan dan hasilnya tersimpan di form.errors.

Setup Flask-WTF dan CSRF

Flask-WTF menyediakan proteksi CSRF (Cross-Site Request Forgery) secara otomatis — asalkan SECRET_KEY ter-set (episode 8):

Install Flask-WTF
pip install flask-wtf
Pythonapp/__init__.py - init CSRF
from flask_wtf import CSRFProtect
 
csrf = CSRFProtect()
 
def create_app(config) -> Flask:
    app = Flask(__name__)
    app.config.from_object(config)
    csrf.init_app(app)
    # ... registrasi blueprint
    return app

Apa itu CSRF? Saat pengguna terautentikasi memegang cookie session, situs jahat bisa memaksa browser-nya mengirim request ke aplikasi kalian (misal lewat form atau image hidden) — seolah-olah dari pengguna yang sah. Token CSRF mencegah ini: setiap form wajib membawa token rahasia yang diverifikasi server.

HTMLTemplate form dengan CSRF token
<form method="POST">
    {{ form.hidden_tag() }}
    <div>
        {{ form.username.label }}
        {{ form.username(size=20) }}
    </div>
    <div>
        {{ form.email.label }}
        {{ form.email() }}
    </div>
    <div>
        {{ form.password.label }}
        {{ form.password() }}
    </div>
    <div>
        {{ form.confirm.label }}
        {{ form.confirm() }}
    </div>
    {{ form.submit() }}
</form>

form.hidden_tag() merender token CSRF sebagai hidden field. Tanpa ini, request POST akan ditolak 400 Bad Request oleh CSRFProtect.

Memproses dan Memvalidasi di Server

Di view, form diproses dengan satu idiom standar:

Pythonroutes/main.py - proses form
from app.forms import RegisterForm
 
 
@main_bp.route("/register", methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        # validasi lolos - data bersih tersedia di form.data
        username = form.username.data
        email = form.email.data
        # simpan ke database...
        return redirect(url_for("main.login"))
    return render_template("register.html", form=form)

validate_on_submit() melakukan dua hal sekaligus: memeriksa request POST dan menjalankan semua validator. Jika ada error, form.errors terisi dan template dirender ulang — error muncul di field yang bersangkutan. Untuk menampilkan error, loop di template:

HTMLMenampilkan error field
{% for error in form.username.errors %}
    <p class="error">{{ error }}</p>
{% endfor %}

Validators yang Sering Dipakai

WTForms menyediakan validator bawaan yang mencakup kebutuhan umum:

ValidatorFungsi
DataRequired()Field wajib diisi (bukan kosong/spasi)
Email()Format alamat email valid
Length(min, max)Panjang string dalam rentang
EqualTo(field)Harus sama dengan field lain (konfirmasi password)
NumberRange(min, max)Nilai numerik dalam rentang
Regexp(pattern)Cocok dengan pola regex
Optional()Boleh kosong (tetap divalidasi jika terisi)

Validator custom memakai decorator @validates atau class validator:

PythonValidator custom - cek username unik
from wtforms.validators import ValidationError
 
 
class RegisterForm(FlaskForm):
    # ...
 
    def validate_username(self, field):
        if User.query.filter_by(username=field.data).first():
            raise ValidationError("Username sudah dipakai")

Method validate_<fieldname> dijalankan otomatis oleh WTForms setelah validator bawaan. Validator custom adalah tempat yang tepat untuk cek unik, format bisnis, dan aturan domain lainnya.

Server-Side vs Client-Side Validation

Penting dipahami: validasi server-side tidak bisa digantikan validasi client-side (HTML required, JavaScript).

Client-sideServer-side
TujuanUX (cepat, tanpa reload)Keamanan & integritas data
Bisa dilewati?Ya — mudahTidak — wajib
Contohrequired, maxlength di HTMLWTForms validators

Jangan pernah mempercayai validasi client-side: penyerang bisa menulis request langsung tanpa browser. validate_on_submit() di server adalah garis pertahanan terakhir yang tak boleh absen.

Warning

Aturan emas: setiap input yang sampai ke server wajib divalidasi ulang di server, apa pun yang terjadi di browser. Form yang hanya andal pada atribut HTML required akan membiarkan data kotor masuk saat ada request manual — sumber utama data corrupt dan bug yang sulit dilacak.

Common Pitfalls Forms & Validation

  • Lupa CSRF: hidden_tag() hilang → POST ditolak 400. Selalu sertakan di semua form POST.
  • Validasi hanya client-side: dilewati request manual — server wajib memvalidasi.
  • validate_on_submit dipanggil di GET: idiom ini memeriksa method-nya sendiri; jangan paksa dengan validate() di GET.
  • Simpan data sebelum validasi: hanya simpan form.data setelah validate_on_submit() lolos.

Penutup

Pada episode 12 ini, kalian telah menguasai form dan validasi server-side.

Inti yang harus dibawa pulang:

  • Form = class (FlaskForm) dengan field dan validator deklaratif.
  • form.hidden_tag() wajib untuk proteksi CSRF — jangan pernah dihilangkan.
  • validate_on_submit() memeriksa POST sekaligus menjalankan semua validator.
  • Validasi server-side adalah garis pertahanan yang tak bisa digantikan.

Di episode 13 selanjutnya, kita mengamankan aplikasi: authentication & sessions — login berbasis session dengan Flask-Login, password hashing dengan Werkzeug, user model, dan protected routes. Pastikan model User dari episode 10 masih segar, karena episode 13 membangun di atasnya!