Belajar Python Membuat REST API Flask

| | Python
Belajar Python Membuat REST API Flask

flask adalah framework yang saya pilih ketika ingin membuat API kecil dengan cepat, satu file sudah cukup untuk menjalankan server. untuk install library python, pastikan sudah paham pip dan virtual environment dulu. panduan ini adalah alur yang selalu saya pakai untuk memulai: setup, route pertama, JSON response, lalu koneksi database. semuanya bisa diikuti tanpa perlu memahami framework besar dulu.

1. apa itu flask?

flask adalah micro-framework Python untuk web. dikategorikan “micro” karena:

  • inti kecil, banyak fitur harus ditambah via ekstensi.
  • tidak ada ORM bawaan, pakai SQLAlchemy via Flask-SQLAlchemy.
  • tidak ada admin panel bawaan, beda dengan Django.
  • tidak ada auth bawaan, pakai Flask-Login, Flask-JWT, dll.

keuntungan:

  • simpel : pemula bisa paham dalam 1 jam.
  • fleksibel : pilih sendiri library yang mau dipakai.
  • banyak ekstensi : hampir semua kebutuhan ada ekstensi Flask.

kekurangan:

  • harus pilih banyak library manual : decision fatigue.
  • kurang cocok untuk app besar : Django atau FastAPI lebih terstruktur.

2. install flask

pip install flask flask-sqlalchemy

library yang di-install:

  • flask : framework utama.
  • flask-sqlalchemy : ORM SQLAlchemy integrasi Flask.

opsional tapi berguna:

  • flask-cors : handle CORS untuk frontend.
  • flask-jwt-extended : JWT auth.
  • flask-marshmallow : serialization & validation.
  • flask-migrate : database migration seperti Django.

3. app minimal: hello world

from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/api/hello")
def hello():
    return jsonify({"message": "Hello World!"})

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

simpan sebagai app.py, jalankan:

python app.py

output:

 * Running on http://127.0.0.1:5000

buka browser ke http://127.0.0.1:5000/api/hello atau test dengan curl:

curl http://127.0.0.1:5000/api/hello

response:

{"message":"Hello World!"}

penjelasan per baris:

  • from flask import Flask, jsonify : import class Flask dan fungsi jsonify.
  • app = Flask(__name__) : buat instance Flask. __name__ memberi tahu Flask di mana mencari template/static.
  • @app.route("/api/hello") : decorator yang daftarkan fungsi hello ke URL /api/hello.
  • def hello(): : fungsi yang dijalankan saat URL diakses.
  • return jsonify({...}) : return response JSON. jsonify set Content-Type ke application/json dan serialize dict ke JSON string.
  • if __name__ == "__main__": : hanya jalan kalau file dijalankan langsung (bukan di-import).
  • app.run(debug=True) : start development server. debug=True auto-reload saat file berubah.

4. CRUD lengkap dengan SQLAlchemy

contoh API CRUD (Create, Read, Update, Delete) untuk resource items:

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

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

class Item(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)
    price = db.Column(db.Float, nullable=False)
    
    def to_dict(self):
        return {"id": self.id, "name": self.name, "price": self.price}

with app.app_context():
    db.create_all()

@app.route("/api/items", methods=["GET"])
def get_items():
    items = Item.query.all()
    return jsonify([item.to_dict() for item in items])

@app.route("/api/items/<int:id>", methods=["GET"])
def get_item(id):
    item = Item.query.get_or_404(id)
    return jsonify(item.to_dict())

@app.route("/api/items", methods=["POST"])
def create_item():
    data = request.get_json()
    item = Item(name=data["name"], price=data["price"])
    db.session.add(item)
    db.session.commit()
    return jsonify(item.to_dict()), 201

@app.route("/api/items/<int:id>", methods=["PUT"])
def update_item(id):
    item = Item.query.get_or_404(id)
    data = request.get_json()
    item.name = data.get("name", item.name)
    item.price = data.get("price", item.price)
    db.session.commit()
    return jsonify(item.to_dict())

@app.route("/api/items/<int:id>", methods=["DELETE"])
def delete_item(id):
    item = Item.query.get_or_404(id)
    db.session.delete(item)
    db.session.commit()
    return "", 204

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

penjelasan per bagian:

  • app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///data.db" : set URL database. sqlite:///data.db artinya file data.db di folder project.
  • db = SQLAlchemy(app) : inisialisasi SQLAlchemy dengan Flask app.
  • class Item(db.Model): : model untuk tabel items. inherit dari db.Model.
  • db.Column(db.Integer, primary_key=True) : kolom id integer, primary key.
  • nullable=False : kolom wajib diisi, tidak boleh NULL.
  • def to_dict(self): : method untuk konversi object ke dict (untuk JSON response).
  • with app.app_context(): db.create_all() : buat tabel di database kalau belum ada. perlu app_context karena Flask pakai app context untuk operasi DB.
  • methods=["GET"] : endpoint hanya untuk method GET. default hanya GET.
  • Item.query.all() : ambil semua item dari tabel.
  • Item.query.get_or_404(id) : ambil item by id, return 404 kalau tidak ada.
  • request.get_json() : parse body request sebagai JSON dict.
  • data["name"] : akses key, raise KeyError kalau tidak ada.
  • data.get("name", item.name) : akses key dengan default value kalau tidak ada.
  • db.session.add(item) : tambahkan object ke session.
  • db.session.commit() : simpan perubahan ke database.
  • return jsonify(...), 201 : return JSON dengan status code 201 (Created).
  • return "", 204 : return kosong dengan status code 204 (No Content, success delete).

5. testing API dengan curl

# GET semua items
curl http://localhost:5000/api/items

# POST item baru
curl -X POST http://localhost:5000/api/items \
  -H "Content-Type: application/json" \
  -d '{"name":"Laptop","price":15000000}'

# PUT update item id=1
curl -X PUT http://localhost:5000/api/items/1 \
  -H "Content-Type: application/json" \
  -d '{"price":14000000}'

# DELETE item id=1
curl -X DELETE http://localhost:5000/api/items/1

penjelasan per perintah:

  • curl -X POST : kirim request dengan method POST.
  • -H "Content-Type: application/json" : set header Content-Type. server butuh ini untuk parse body sebagai JSON.
  • -d '{...}' : data body. JSON harus string valid.

alternatif: pakai Postman (GUI) atau Insomnia untuk test yang lebih nyaman.

6. validasi input dengan marshmallow

input user harus divalidasi. flask-marshmallow kasih cara yang rapi:

pip install flask-marshmallow marshmallow-sqlalchemy
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow import validate

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///data.db"
db = SQLAlchemy(app)
ma = Marshmallow(app)

class Item(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)
    price = db.Column(db.Float, nullable=False)

class ItemSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = Item
        load_instance = True
    
    # Tambah validasi custom
    name = ma.auto_field(required=True, validate=validate.Length(min=1, max=80))
    price = ma.auto_field(required=True, validate=validate.Range(min=0))

item_schema = ItemSchema()
items_schema = ItemSchema(many=True)

@app.route("/api/items", methods=["POST"])
def create_item():
    try:
        item = item_schema.load(request.get_json())
    except ValidationError as err:
        return jsonify({"errors": err.messages}), 400
    
    db.session.add(item)
    db.session.commit()
    return jsonify(item_schema.dump(item)), 201

item_schema.load(data) otomatis validasi tipe data dan aturan. kalau gagal, return 400 dengan pesan error.

7. error handling yang konsisten

return JSON yang konsisten untuk setiap error, bukan HTML default Flask:

from flask import Flask, jsonify
from werkzeug.exceptions import HTTPException

app = Flask(__name__)

@app.errorhandler(404)
def not_found(e):
    return jsonify({"error": "Resource not found", "status": 404}), 404

@app.errorhandler(400)
def bad_request(e):
    return jsonify({"error": "Bad request", "status": 400}), 400

@app.errorhandler(500)
def internal_error(e):
    # Log error untuk debug
    app.logger.exception("Internal server error")
    return jsonify({"error": "Internal server error", "status": 500}), 500

# Handle semua HTTPException lainnya
@app.errorhandler(HTTPException)
def handle_http_error(e):
    return jsonify({"error": e.description, "status": e.code}), e.code

8. auth dengan JWT

untuk proteksi endpoint, pakai JWT (JSON Web Token):

pip install flask-jwt-extended
from flask import Flask, jsonify, request
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity

app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "ganti-dengan-random-secret-key-yang-panjang"
jwt = JWTManager(app)

@app.route("/api/login", methods=["POST"])
def login():
    data = request.get_json()
    username = data.get("username")
    password = data.get("password")
    
    # Validasi user (contoh, jangan hardcode di production)
    if username == "admin" and password == "rahasia123":
        token = create_access_token(identity=username)
        return jsonify({"token": token}), 200
    
    return jsonify({"error": "Username atau password salah"}), 401

@app.route("/api/profile", methods=["GET"])
@jwt_required()
def profile():
    current_user = get_jwt_identity()
    return jsonify({"user": current_user})

test dengan curl:

# Login
curl -X POST http://localhost:5000/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"rahasia123"}'
# Response: {"token":"eyJhbGc..."}

# Akses endpoint protected
curl -H "Authorization: Bearer eyJhbGc..." \
  http://localhost:5000/api/profile

JWT_SECRET_KEY wajib ganti di production. generate dengan python -c "import secrets; print(secrets.token_hex(32))".

9. CORS untuk frontend

kalau frontend (misal di localhost:3000) mau akses API Flask (di localhost:5000), browser akan blokir karena CORS policy. fix dengan flask-cors:

pip install flask-cors
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app)  # izinkan semua origin (dev only!)

# Atau spesifik origin untuk production
CORS(app, resources={r"/api/*": {"origins": ["https://myapp.com"]}})

10. struktur folder untuk project menengah

untuk project serius, jangan taruh semua kode di satu file. struktur yang umum:

flask-api/
├── app/
│   ├── __init__.py       # factory pattern
│   ├── models.py         # SQLAlchemy models
│   ├── routes/
│   │   ├── __init__.py
│   │   ├── items.py
│   │   └── auth.py
│   ├── schemas.py        # Marshmallow schemas
│   └── extensions.py     # db, ma, jwt, cors
├── config.py             # konfigurasi
├── run.py                # entry point
├── requirements.txt
└── .env                  # environment variables

app/__init__.py : factory pattern:

from flask import Flask
from .extensions import db, ma, jwt, cors

def create_app(config_class="config.Config"):
    app = Flask(__name__)
    app.config.from_object(config_class)
    
    # Init ekstensi
    db.init_app(app)
    ma.init_app(app)
    jwt.init_app(app)
    cors.init_app(app)
    
    # Register blueprint
    from .routes.items import items_bp
    from .routes.auth import auth_bp
    app.register_blueprint(items_bp, url_prefix="/api/items")
    app.register_blueprint(auth_bp, url_prefix="/api/auth")
    
    return app

blueprint memungkinkan split route ke beberapa file. lebih rapi untuk tim.

11. testing otomatis dengan pytest

test API otomatis pakai pytest:

pip install pytest
# test_app.py
import pytest
from app import create_app
from app.extensions import db

@pytest.fixture
def app():
    app = create_app("config.TestConfig")
    with app.app_context():
        db.create_all()
        yield app
        db.drop_all()

@pytest.fixture
def client(app):
    return app.test_client()

def test_get_items_empty(client):
    response = client.get("/api/items")
    assert response.status_code == 200
    assert response.json == []

def test_create_item(client):
    response = client.post("/api/items", json={
        "name": "Laptop",
        "price": 15000000
    })
    assert response.status_code == 201
    assert response.json["name"] == "Laptop"
    assert response.json["price"] == 15000000

def test_get_item_not_found(client):
    response = client.get("/api/items/999")
    assert response.status_code == 404

jalankan:

pytest

12. deployment production

jangan pakai flask run atau app.run() di production. pakai production-grade WSGI server.

pakai Gunicorn

pip install gunicorn

# Jalanin 4 worker, bind ke port 8000
gunicorn -w 4 -b 0.0.0.0:8000 "app:create_app()"

konfigurasi Nginx (/etc/nginx/sites-available/api):

server {
    listen 80;
    server_name api.example.com;
    
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

deploy ke platform

opsi simpel tanpa setup server:

  • Railway : railway up, otomatis detect Flask.
  • Render : connect GitHub repo, otomatis build & deploy.
  • Fly.io : fly launch dan fly deploy.
  • Vercel : support Flask via Serverless Functions.
  • Heroku : git push heroku main dengan Procfile.

13. pagination, filtering & config modern 2026

GET /api/items tanpa paginasi akan berat kalau data sudah ribuan. tambah paginasi + filter + config .env:

# config.py
import os
class Config:
    SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL", "sqlite:///data.db")
    SQLALCHEMY_TRACK_MODIFICATIONS = False  # wajib False, cegah memory leak
    SQLALCHEMY_ENGINE_OPTIONS = {"pool_pre_ping": True, "pool_recycle": 3600}

# routes/items.py
from flask import request, jsonify
@app.route("/api/items", methods=["GET"])
def get_items():
    page = request.args.get("page", 1, type=int)
    per_page = min(request.args.get("per_page", 20, type=int), 100)
    q = request.args.get("q", "")
    query = Item.query
    if q:
        query = query.filter(Item.name.ilike(f"%{q}%"))
    pagination = query.paginate(page=page, per_page=per_page, error_out=False)
    return jsonify({
        "items": [i.to_dict() for i in pagination.items],
        "total": pagination.total,
        "page": pagination.page,
        "per_page": pagination.per_page
    })

untuk SQLAlchemy 2.x + Flask-SQLAlchemy 3.1 (2024+), gunakan typed Mapped[] kalau mau type-check mypy/pyright:

from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase): pass
db = SQLAlchemy(model_class=Base)
class Item(db.Model):
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(db.String(80))

pakai python-dotenv untuk load .env otomatis di dev: pip install python-dotenv lalu from dotenv import load_dotenv; load_dotenv().

14. rate limiting, OpenAPI & security

pip install Flask-Limiter flask-smorest
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(get_remote_address, app=app, default_limits=["100/hour"])
@app.route("/api/items", methods=["POST"])
@limiter.limit("10/minute")
def create_item(): ...

# OpenAPI Swagger otomatis (Flask-SMorest)
from flask_smorest import Api
api = Api(app)
api.spec.title = "My API"
# buka /swagger-ui untuk docs interaktif

security checklist 2026: CORS(app, resources={r"/api/*": {"origins": ["https://myapp.com"]}}) jangan * di production, validasi input, X-RateLimit headers, dan generate SECRET_KEY via secrets.

15. tips produksi

  • set debug=False di production. debug=True kasih info stack trace yang bisa bocor ke user. Flask 3.1 butuh Python 3.9+ (3.8 sudah drop).
  • secret key di environment variable, jangan hardcode.
  • HTTPS wajib untuk production, pakai Let’s Encrypt gratis.
  • rate limiting pakai Flask-Limiter untuk cegah abuse (lihat bab 14).
  • logging ke file atau service (Sentry, DataDog).
  • monitoring dengan Prometheus + Grafana.
  • database connection pool di SQLAlchemy untuk handle concurrent request (pool_pre_ping=True atasi MySQL has gone away).
  • migrasi pakai Flask-Migrate (Alembic): flask db migrate -m "add table" && flask db upgrade — jalankan upgrade sekali di deploy, bukan per server.

Kesimpulan

  • Flask micro-framework Python untuk API, ringan dan simpel
  • flask run hanya untuk development, production pakai Gunicorn + Nginx
  • SQLAlchemy untuk ORM, flask-marshmallow untuk validasi & serialization
  • JWT untuk auth, flask-cors untuk izinkan akses dari frontend
  • struktur folder blueprint pattern untuk project menengah-besar
  • test API otomatis dengan pytest dan test_client()
  • deploy ke Railway/Render/Fly.io untuk simpel, atau setup Gunicorn + Nginx untuk control penuh

kalau API mulai kompleks dan butuh async, pertimbangkan FastAPI : performance-nya lebih cepet untuk high-concurrency. tapi untuk kebanyakan project, Flask cukup.

Baca Juga Mengenai :


Pertanyaan yang Sering Diajukan

Apa itu Flask dan kenapa pakai Flask untuk API?

flask adalah micro-framework Python untuk web. ringan, simpel, dan fleksibel. cocok untuk REST API kecil-menengah karena tidak perlu setup banyak. untuk API besar, pertimbangkan FastAPI atau Django.

Bedanya Flask dan Django untuk API?

flask micro-framework, banyak hal harus ditambah manual (ORM, auth, admin). django full-featured, sudah include ORM, admin, auth bawaan. flask cocok untuk API sederhana, django untuk aplikasi besar dengan banyak fitur.

Bagaimana cara test API Flask?

pakai curl di terminal, Postman (GUI), atau pytest untuk automated test. flask punya test_client() bawaan untuk unit test API endpoint tanpa harus start server.

Bagaimana cara handle error di Flask API?

pakai @app.errorhandler(404) atau abort() untuk trigger error. custom error handler return JSON, bukan HTML default Flask. selalu log error untuk debug production.

Apakah Flask support async?

flask 2.0+ punya dukungan async/await, tapi untuk performa high-concurrency lebih baik pakai FastAPI atau Starlette. flask cocok untuk request sync tradisional.

Bagaimana deploy Flask API ke production?

jangan pakai flask run di production. pakai Gunicorn atau uWSGI sebagai WSGI server, di belakang Nginx reverse proxy. untuk deployment simpel, Railway, Render, atau Fly.io support Flask out of the box.

Cara validasi input JSON di Flask?

bisa manual dengan if-else, atau pakai library marshmallow atau pydantic. flask punya ekstensi flask-pydantic atau flask-marshmallow untuk validasi berbasis schema.

Bagaimana cara implementasi JWT auth di Flask?

pakai library flask-jwt-extended. simpan secret key di environment variable, generate token saat login, dan pakai @jwt_required() di endpoint yang butuh autentikasi.

Sigit Nurhanafi avatar
Full-stack developer & technical writer. Berpengalaman di PHP, Laravel, NodeJS, MySQL, dan Python. Aktif menulis tutorial pemrograman dan maintaining open-source projects di PemburuKode sejak 2021.