Blog · Adres verisi ve rehberler

Adres Seçici Bileşen ile Formu Hızla Kurun

Adres seçici bileşen ile il, ilçe, mahalle ve sokak seçimini hazır kurun; API anahtarını sunucu tarafı proxy'de tutup seçimi forma aktarın.

4 dk okuma

Teslimat ya da kayıt formuna il, ilçe, mahalle ve sokak seçimi eklemek ilk bakışta basit görünür. Dört seçim kutusu yapar, her seçimde bir istek atarsınız. Uygulamada ise yükleme durumları, uzun sokak listeleri, hata mesajları ve form gönderimi derken iş büyür. API Türkiye'nin adres seçici bileşeni bu kademeli seçim deneyimini hazır olarak sunar. Size kalan iş, küçük bir sunucu tarafı uç yazmak ve seçilen değeri formunuza bağlamaktır.

Bu yazıda bileşeni sayfaya ekleyeceğiz, API anahtarını tarayıcıya hiç göndermeyen bir proxy kuracağız, seçimi form gönderimine taşıyacağız ve görünümü sitenize uyduracağız. Canlı örnekleri hazır bileşen örnekleri sayfasında deneyebilirsiniz.

Adres seçici bileşen size ne sağlar?

<api-turkiye-address-picker>, herhangi bir framework'e bağlı olmayan, sıfır bağımlılıklı bir web component'tir. React, Vue, Svelte veya düz JavaScript kullanan bir sayfada aynı etiketle çalışır.

Kullanıcının gördüğü akış şöyledir:

  1. İller yüklenir. İl seçilince o ilin ilçeleri gelir.
  2. İlçe seçilince o ilçenin mahalleleri yüklenir.
  3. Mahalle seçildikten sonra kullanıcı sokak alanına yazdıkça, seçilen mahalle içinde arama yapılır. Her istekte en fazla 20 sonuç gelir.

Son adım önemlidir. Kalabalık bir mahallede sokak sayısı yüksek olabilir. Yüzlerce seçenekli bir liste yerine birkaç harf yazıp aramak, özellikle mobilde daha rahattır. Klasik bir il ilçe dropdown zincirinin en çok zorlandığı yer de burasıdır.

Bileşen Shadow DOM kullanır. Sitenizin genel CSS kuralları bileşenin içini bozmaz, bileşenin stilleri de sayfanıza taşmaz.

İş bölümü kabaca şöyledir:

İşBileşenSizin tarafınız
Seçim alanları ve sıralı yüklemeHazır—
Mahalle içinde sokak aramaHazır—
API anahtarıÜretimde tutmazProxy'de saklanır
Seçimin forma yazılmasıOlay yayınlarGizli alana yazılır
Kaydetmeden önce kontrol—Sunucuda yapılır

Arayüzün her ayrıntısını kendiniz kontrol etmek istiyorsanız aynı akışı sıfırdan kurabilirsiniz. Bunun için il, ilçe ve mahalle seçimli adres formu yazımıza bakabilirsiniz.

Bileşeni sayfaya eklemek

Paketin canlı ESM dosyasını bir script etiketiyle ekleyip bileşeni formunuza yerleştirin:

<script type="module" src="https://apiturkiye.com/components.js"></script>

<api-turkiye-address-picker
  base-url="/api/address-catalog"
  theme="auto">
</api-turkiye-address-picker>

base-url, bileşenin istek göndereceği kendi sunucunuzdaki adrestir. Bileşen bu adresin altındaki şu dört yolu kullanır:

  • /v1/provinces
  • /v1/provinces/{code}/districts
  • /v1/districts/{code}/neighborhoods
  • /v1/neighborhoods/{code}/streets

Bileşenin bir api-key özelliği de vardır. Bu özellik yalnız kısıtlı test anahtarları ve kontrollü ortamlar içindir. Üretim anahtarını HTML'e yazarsanız, sayfa kaynağını açan herkes o anahtarı alabilir. Anahtar güvenliğinin genel ilkeleri için API anahtarı nedir, nasıl güvenli kullanılır? yazısına göz atabilirsiniz.

Anahtar sunucuda kalır: proxy kurmak

Proxy, bileşenden gelen isteği alır, X-API-Key başlığını sunucu tarafında ekler ve isteği API Türkiye'ye iletir. İyi bir proxy şu kuralları uygular:

  • Yalnız GET isteklerini ve yalnız yukarıdaki dört yolu kabul eder.
  • Anahtarı ortam değişkeninden okur ve istemciden gelen başlıkları iletmez.
  • limit değerini üst sınırla kırpar ve dış isteğe zaman aşımı koyar.
  • API'nin döndürdüğü durum kodunu ve varsa Retry-After başlığını bileşene aynen aktarır.

Node.js (Express) örneği

Örnek, Node.js 18 veya üzerindeki yerleşik fetch ile çalışır:

import express from "express";

const app = express();
const API_BASE = "https://api.apiturkiye.com";
const API_KEY = process.env.APITURKIYE_KEY;

const ALLOWED = [
  /^\/v1\/provinces$/,
  /^\/v1\/provinces\/\d+\/districts$/,
  /^\/v1\/districts\/\d+\/neighborhoods$/,
  /^\/v1\/neighborhoods\/\d+\/streets$/,
];

app.use("/api/address-catalog", async (req, res) => {
  if (req.method !== "GET" || !ALLOWED.some((re) => re.test(req.path))) {
    return res.status(404).json({ error: { code: "proxy_not_allowed" } });
  }

  const url = new URL(API_BASE + req.path);
  for (const [key, value] of Object.entries(req.query)) {
    if (typeof value === "string") url.searchParams.set(key, value);
  }
  if (url.searchParams.has("limit")) {
    const limit = Math.min(Math.max(Number(url.searchParams.get("limit")) || 50, 1), 100);
    url.searchParams.set("limit", String(limit));
  }

  try {
    const upstream = await fetch(url, {
      headers: { "X-API-Key": API_KEY },
      signal: AbortSignal.timeout(8000),
    });
    const retryAfter = upstream.headers.get("retry-after");
    if (retryAfter) res.set("Retry-After", retryAfter);
    res.status(upstream.status).type("application/json").send(await upstream.text());
  } catch {
    res.status(504).json({ error: { code: "upstream_unavailable" } });
  }
});

app.listen(3000);

Python (Flask + requests) örneği

Aynı proxy Python ile şöyle yazılır:

import os
import re
import requests
from flask import Flask, Response, request

app = Flask(__name__)
API_BASE = "https://api.apiturkiye.com"
API_KEY = os.environ["APITURKIYE_KEY"]
ALLOWED = [
    re.compile(r"/v1/provinces"),
    re.compile(r"/v1/provinces/\d+/districts"),
    re.compile(r"/v1/districts/\d+/neighborhoods"),
    re.compile(r"/v1/neighborhoods/\d+/streets"),
]

@app.get("/api/address-catalog/<path:subpath>")
def address_catalog(subpath):
    path = "/" + subpath
    if not any(p.fullmatch(path) for p in ALLOWED):
        return {"error": {"code": "proxy_not_allowed"}}, 404

    params = request.args.to_dict()
    if "limit" in params:
        try:
            limit = int(params["limit"])
        except ValueError:
            limit = 50
        params["limit"] = str(min(max(limit, 1), 100))

    try:
        upstream = requests.get(API_BASE + path, params=params,
                                headers={"X-API-Key": API_KEY}, timeout=8)
    except requests.RequestException:
        return {"error": {"code": "upstream_unavailable"}}, 504

    headers = {}
    if "Retry-After" in upstream.headers:
        headers["Retry-After"] = upstream.headers["Retry-After"]
    return Response(upstream.content, status=upstream.status_code,
                    content_type="application/json; charset=utf-8", headers=headers)

Bu örnekler sorgu dizesini olduğu gibi aktarır. Bileşenin hangi parametreleri gönderdiğini tarayıcının ağ sekmesinde görebilirsiniz. Ardından izin verilen parametreleri bir listeyle daraltabilirsiniz.

Proxy'yi curl ile test etmek

# İzin verilen yol: anahtar göndermeden yanıt almalısınız
curl "http://localhost:3000/api/address-catalog/v1/provinces/34/districts"

# İzin listesinde olmayan yol: 404 dönmeli
curl -i "http://localhost:3000/api/address-catalog/v1/meta"

Proxy'niz herkese açık bir uçtur. Başka biri onu doğrudan çağırıp kotanızı tüketebilir. Bunu önlemek için IP veya oturum bazlı kendi hız sınırınızı ekleyin. Formun yalnız giriş yapmış kullanıcılara açık olduğu sayfalarda oturum kontrolü de yapın.

İl, ilçe ve mahalle listeleri sık değişmez. Bu yanıtlarda kısa süreli bir önbellek, gereksiz istekleri azaltır. API 401 ya da 403 dönüyorsa sorun anahtarda, pakette veya yetkidedir. Bu durumu sunucu logunda kaydedin ama isteği otomatik olarak tekrarlamayın.

Seçilen değerleri forma aktarmak

Bileşen, seçim her değiştiğinde api-turkiye-address-change olayını yayınlar. Olayın detail nesnesindeki complete alanı, seçimin tamamlanıp tamamlanmadığını gösterir.

Bileşen Shadow DOM içinde çalıştığı için değerleri form gönderimine bir gizli alan üzerinden taşımak en sade yoldur:

<form id="checkout" method="post" action="/siparis">
  <api-turkiye-address-picker base-url="/api/address-catalog" theme="auto">
  </api-turkiye-address-picker>

  <label>
    Bina no, daire
    <input name="doorAndUnit" required>
  </label>

  <input type="hidden" name="addressSelection">
  <button type="submit" disabled>Devam et</button>
</form>

<script type="module">
  const form = document.getElementById("checkout");
  const picker = form.querySelector("api-turkiye-address-picker");
  const hidden = form.elements.addressSelection;
  const submit = form.querySelector('button[type="submit"]');

  picker.addEventListener("api-turkiye-address-change", (event) => {
    const { complete } = event.detail;
    hidden.value = complete ? JSON.stringify(event.detail) : "";
    submit.disabled = !complete;
  });
</script>

Geliştirme sırasında console.log(event.detail) ile nesnenin tamamını inceleyin. İhtiyacınız olan alanları gerekirse ayrı gizli alanlara bölebilirsiniz. React'ta aynı dinleyiciyi bir ref üzerinden addEventListener ile ekleyebilir, değeri form durumunuza yazabilirsiniz.

Sunucu tarafında üç noktaya dikkat edin:

  • Gelen değere güvenmeyin. Gizli alan tarayıcıdan gelir ve kullanıcı tarafından değiştirilebilir. JSON'u ayrıştırın ve beklediğiniz alanların varlığını kontrol edin.
  • Referans olarak stableId saklayın. Katalog kayıtlarındaki stableId sürümden bağımsızdır. Adları yalnız görüntüleme için tutun. Aynı mahalle adı farklı yerleşimlerde bulunabilir, bu yüzden kayıtları adlarına göre eşleştirmeyin.
  • Kapı numarasını ayrıca doğrulayın. Bileşen sokağa kadar seçim yaptırır. Kapı numarasını da katalogda kontrol etmek isterseniz Pro ve üzeri paketlerde POST /v1/addresses/validate ucunu kullanabilirsiniz. Bu uç il, ilçe, mahalle, sokak ve kapı zincirini kontrol eder. Ayrıntılar API dokümantasyonunda yer alır.

Görünümü sitenize uydurmak

Bileşenin dış kutusunu, yani elementin kendisini, normal CSS ile form düzeninize yerleştirebilirsiniz. İç görünüm Shadow DOM içinde kalır. Bu yüzden renk ve köşe yuvarlaklığı gibi ayarları CSS özel değişkenleriyle, doğrudan element üzerinde yaparsınız:

api-turkiye-address-picker {
  display: block;
  max-width: 32rem;
  margin-block: 1rem;
  --apt-color: #0f766e;
  --apt-radius: 6px;
}

--apt-color değerini marka renginizle, --apt-radius değerini sitenizdeki diğer alanların köşe yuvarlaklığıyla eşleştirin. Böylece bileşen formun geri kalanından ayrışmaz. Değişkenlerin bileşenin hangi parçalarını etkilediğini bileşen örnekleri sayfasında deneyerek görebilirsiniz.

theme="auto" sistem temasını izler, yani kullanıcının cihazı koyu moddaysa bileşen de koyu görünür. Siteniz tek bir temada sabitse açık veya koyu temayı doğrudan seçebilirsiniz.

Aynı paketteki <api-turkiye-autocomplete> bileşeni, adım adım seçim yerine tek bir alanda il, ilçe, mahalle ve sokak araması isteyen formlar içindir. Bu bileşen de endpoint özelliğiyle aynı-origin bir proxy üzerinden çalışır.

Yayına almadan önce kısa kontrol listesi

  • Üretim anahtarı yalnız sunucuda, ortam değişkeninde duruyor. HTML'de, JavaScript'te ve Git deposunda yok.
  • Proxy yalnız GET isteklerini ve dört katalog yolunu kabul ediyor. Diğer yollar 404 dönüyor.
  • Proxy'nin kendi hız sınırı ve dış istek için zaman aşımı var.
  • Gönder düğmesi, complete değeri gelene kadar kapalı.
  • Sunucu, gelen seçimi ayrıştırıp kontrol ediyor ve stableId değerlerini saklıyor.
  • 429 yanıtında Retry-After süresine uyuluyor. 401 ve 403 yanıtları otomatik olarak tekrarlanmıyor.

Bileşeni denemek için hazır adres seçici örneklerine göz atabilirsiniz. Uçların ayrıntılarını API dokümantasyonunda bulabilir, proxy'niz için anahtarı ücretsiz kayıt ile alabilirsiniz.