Merancang API Warna: REST Endpoint untuk Data Warna
Embed This Widget
Add the script tag and a data attribute to embed this widget.
Embed via iframe for maximum compatibility.
<iframe src="https://colorfyi.com/iframe/entity//" width="420" height="400" frameborder="0" style="border:0;border-radius:10px;max-width:100%" loading="lazy"></iframe>
Paste this URL in WordPress, Medium, or any oEmbed-compatible platform.
https://colorfyi.com/entity//
Add a dynamic SVG badge to your README or docs.
[](https://colorfyi.com/entity//)
Use the native HTML custom element.
API warna terdengar sangat spesifik hingga Anda memikirkan berapa banyak alat yang mengonsumsi data warna: alat desain yang membutuhkan konversi, pemeriksa aksesibilitas yang memverifikasi kontras, komponen pemilih warna yang memerlukan pencarian warna bernama, dan generator Palet yang menghasilkan harmoni. Jika Anda sedang membangun salah satu dari ini, atau menyediakan data warna kepada developer lain, Anda membutuhkan REST API yang dirancang dengan baik.
Panduan ini mencakup seluruh permukaan desain: keputusan skema, struktur endpoint, format respons, penanganan error, dan pembatasan laju — dari perspektif seseorang yang telah membangun endpoint yang mendukung Color Converter dan Contrast Checker di ColorFYI.
Desain Skema API untuk Data Warna
Objek Warna Utama
Sebuah warna memiliki beberapa representasi — hex, RGB, HSL, CMYK, OKLCH — dan beberapa bidang metadata. Objek respons kanonik harus mencakup semuanya, memungkinkan klien menggunakan format mana pun yang mereka butuhkan tanpa membuat permintaan tambahan:
{
"hex": "#FF5733",
"rgb": {
"r": 255,
"g": 87,
"b": 51,
"css": "rgb(255, 87, 51)"
},
"hsl": {
"h": 11,
"s": 100,
"l": 60,
"css": "hsl(11, 100%, 60%)"
},
"hsv": {
"h": 11,
"s": 80,
"v": 100
},
"cmyk": {
"c": 0,
"m": 66,
"y": 80,
"k": 0
},
"oklch": {
"l": 0.63,
"c": 0.19,
"h": 27.5,
"css": "oklch(0.63 0.19 27.5)"
},
"luminance": 0.215,
"is_dark": false,
"name": "Coral Red",
"name_source": "color_name_database",
"name_distance": 0.04
}
Menyertakan is_dark (yang berasal dari luminansi) menghemat klien dari penerapan perhitungan luminansi WCAG sendiri. Menyertakan name dan name_distance memungkinkan klien menampilkan warna bernama terdekat tanpa panggilan API terpisah.
Konvensi Penamaan
Gunakan snake_case untuk kunci JSON (konsisten dengan sebagian besar REST API). Kelompokkan bidang terkait ke dalam objek (rgb, hsl, cmyk) daripada meratakannya (rgb_r, rgb_g, rgb_b). Objek bersarang lebih mudah dibaca, lebih mudah didestrukturisasi di JavaScript, dan dipetakan dengan bersih ke antarmuka bertipe:
interface ColorResponse {
hex: string;
rgb: { r: number; g: number; b: number; css: string };
hsl: { h: number; s: number; l: number; css: string };
oklch: { l: number; c: number; h: number; css: string };
luminance: number;
is_dark: boolean;
name: string | null;
name_source: string | null;
name_distance: number | null;
}
Normalisasi Hex
Terima warna hex dalam berbagai format — dengan atau tanpa #, 3-digit atau 6-digit, huruf besar atau kecil — dan normalisasikan di lapisan API sebelum pemrosesan apapun:
# Contoh Python/Django
import re
def normalize_hex(raw: str) -> str:
"""Normalisasi input hex ke 6-digit huruf besar tanpa #."""
clean = raw.strip().lstrip('#').upper()
if not re.match(r'^[0-9A-F]{3}$|^[0-9A-F]{6}$', clean):
raise ValueError(f"Warna hex tidak valid: {raw!r}")
# Perluas 3-digit ke 6-digit
if len(clean) == 3:
clean = ''.join(c * 2 for c in clean)
return clean
Kembalikan bentuk yang dinormalisasi dalam respons — ini menghilangkan ambiguitas dan membuat cache downstream lebih dapat diprediksi.
Endpoint Konversi Hex/RGB/HSL
GET /api/color/{hex}
Endpoint utama mengembalikan objek warna lengkap untuk kode hex yang diberikan. Inilah yang digunakan Color Converter untuk mengisi semua bidang format sekaligus:
GET /api/color/FF5733
GET /api/color/%23FF5733 (# yang diencode URL)
GET /api/color/f57 (3-digit, huruf kecil)
Ketiganya harus mengembalikan respons yang sama untuk #FF5733.
Respons:
{
"hex": "#FF5733",
"rgb": { "r": 255, "g": 87, "b": 51, "css": "rgb(255, 87, 51)" },
"hsl": { "h": 11, "s": 100, "l": 60, "css": "hsl(11, 100%, 60%)" },
"cmyk": { "c": 0, "m": 66, "y": 80, "k": 0 },
"oklch": { "l": 0.63, "c": 0.19, "h": 27.5, "css": "oklch(0.63 0.19 27.5)" },
"luminance": 0.215,
"is_dark": false,
"name": "Coral Red",
"name_source": "css_extended",
"name_distance": 0.08
}
POST /api/convert
Untuk mengonversi input arbitrer yang mungkin bukan hex — nama warna CSS, tripel RGB, nilai HSL:
POST /api/convert
Content-Type: application/json
{
"input": "rgb(255, 87, 51)",
"from": "rgb"
}
Respons: Objek warna lengkap yang sama.
Terima nilai from: hex, rgb, hsl, cmyk, oklch, name. Validasi format input sesuai dengan tipe from yang dinyatakan dan kembalikan 422 dengan pesan error yang jelas jika tidak cocok.
Contoh Django ViewSet
# apps/api/views.py
from django.http import JsonResponse
from django.views import View
from apps.colors.engine import ColorEngine
class ColorDetailView(View):
def get(self, request, hex_code: str):
try:
normalized = normalize_hex(hex_code)
except ValueError as e:
return JsonResponse(
{"error": "invalid_hex", "message": str(e)},
status=422
)
engine = ColorEngine(normalized)
data = {
"hex": f"#{normalized}",
"rgb": engine.to_rgb_dict(),
"hsl": engine.to_hsl_dict(),
"cmyk": engine.to_cmyk_dict(),
"oklch": engine.to_oklch_dict(),
"luminance": engine.relative_luminance(),
"is_dark": engine.is_dark(),
"name": engine.nearest_name(),
"name_source": engine.nearest_name_source(),
"name_distance": engine.nearest_name_distance(),
}
response = JsonResponse(data)
response["Cache-Control"] = "public, max-age=86400, stale-while-revalidate=604800"
return response
Cache data warna secara agresif — kode hex tertentu selalu menghasilkan output yang sama, sehingga cache bisa berumur sangat panjang.
API Pencarian dan Pelengkapan Otomatis Warna
GET /api/search
Pencarian warna bernama mendukung pelengkapan otomatis dalam pemilih warna dan kolom pencarian. Parameter kueri adalah nama warna parsial:
GET /api/search?q=coral
GET /api/search?q=teal&limit=5
GET /api/search?q=%230d&source=css // Cari berdasarkan awalan hex
Respons:
{
"results": [
{
"hex": "#FF7F50",
"name": "Coral",
"source": "css_named",
"score": 1.0
},
{
"hex": "#FF6B6B",
"name": "Light Coral",
"source": "css_extended",
"score": 0.85
},
{
"hex": "#E8735A",
"name": "Terra Cotta",
"source": "crayola",
"score": 0.62
}
],
"total": 3,
"query": "coral"
}
Pertimbangan Implementasi Pencarian
Untuk database warna bernama kecil (~2.000 entri), pencarian awalan dalam memori dengan pencocokan fuzzy sudah cukup cepat:
from difflib import SequenceMatcher
def search_colors(query: str, limit: int = 10) -> list[dict]:
q = query.lower().strip()
results = []
for color in NAMED_COLORS: # Daftar yang sudah dimuat
name = color["name"].lower()
if q in name:
# Pencocokan substring tepat: skor berdasarkan posisi
pos = name.index(q)
score = 1.0 - (pos / len(name)) * 0.3
else:
# Pencocokan fuzzy
score = SequenceMatcher(None, q, name).ratio()
if score < 0.5:
continue
results.append({**color, "score": round(score, 3)})
results.sort(key=lambda x: x["score"], reverse=True)
return results[:limit]
Untuk database yang lebih dari 10.000 entri, gunakan ekstensi pg_trgm PostgreSQL dengan pengindeksan trigram untuk pencarian fuzzy sub-milidetik:
-- Aktifkan ekstensi
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- Tambah indeks
CREATE INDEX idx_color_name_trgm ON named_colors USING gin(name gin_trgm_ops);
-- Kueri
SELECT hex, name, source, similarity(name, 'coral') AS score
FROM named_colors
WHERE name % 'coral'
ORDER BY score DESC
LIMIT 10;
Optimisasi Pelengkapan Otomatis
Untuk pelengkapan otomatis pencarian langsung saat mengetik, optimalkan untuk latensi rendah:
- Debounce di klien — minimum 150ms sebelum mengirimkan permintaan.
- Tetapkan panjang kueri minimum — tolak kueri yang lebih pendek dari 2 karakter (kembalikan hasil kosong secara langsung).
- Cache hasil berdasarkan kueri — kueri yang sama dari pengguna berbeda menghasilkan hasil yang sama. Gunakan
Cache-Control: public, max-age=3600. - Respons cepat jika tidak ada hasil — respons segera dengan
{"results": [], "total": 0}daripada menjalankan pemindaian fuzzy penuh.
Endpoint Pengecekan Kontras
GET /api/contrast
API kontras adalah mesin di balik Contrast Checker. Ia menerima dua kode hex dan mengembalikan rasio kontras WCAG dengan status lulus/gagal untuk setiap level:
GET /api/contrast?fg=FF5733&bg=FFFFFF
GET /api/contrast?fg=000000&bg=FFFFFF
Respons:
{
"ratio": 3.02,
"ratio_formatted": "3.02:1",
"foreground": {
"hex": "#FF5733",
"luminance": 0.215
},
"background": {
"hex": "#FFFFFF",
"luminance": 1.0
},
"wcag": {
"aa_normal": false,
"aa_large": true,
"aaa_normal": false,
"aaa_large": false
},
"level": "AA Large",
"passes": true
}
Bidang level mengembalikan level WCAG tertinggi yang dicapai pasangan tersebut ("AAA", "AA", "AA Large", atau "Fail"). Bidang passes adalah true jika level WCAG mana pun terpenuhi — berguna untuk indikator hijau/merah sederhana.
Perhitungan Kontras
def relative_luminance(hex_code: str) -> float:
r, g, b = hex_to_rgb(hex_code)
def linearize(v: int) -> float:
s = v / 255
return s / 12.92 if s <= 0.04045 else ((s + 0.055) / 1.055) ** 2.4
return 0.2126 * linearize(r) + 0.7152 * linearize(g) + 0.0722 * linearize(b)
def contrast_ratio(hex1: str, hex2: str) -> float:
L1 = relative_luminance(hex1)
L2 = relative_luminance(hex2)
lighter = max(L1, L2)
darker = min(L1, L2)
return round((lighter + 0.05) / (darker + 0.05), 2)
def wcag_result(ratio: float) -> dict:
return {
"aa_normal": ratio >= 4.5,
"aa_large": ratio >= 3.0,
"aaa_normal": ratio >= 7.0,
"aaa_large": ratio >= 4.5,
}
Menyarankan Alternatif Aksesibel
Ekstensi yang berguna: diberikan warna depan yang gagal kontras terhadap latar belakang, sarankan penyesuaian kecerahan minimum yang mencapai WCAG AA:
def suggest_accessible_foreground(fg_hex: str, bg_hex: str, target_ratio: float = 4.5) -> str:
"""Kembalikan versi fg_hex yang disesuaikan kecerahannya yang lulus target_ratio."""
hsl = rgb_to_hsl(*hex_to_rgb(fg_hex))
bg_lum = relative_luminance(bg_hex)
is_dark_bg = bg_lum < 0.179
# Coba sesuaikan kecerahan ke arah yang meningkatkan kontras
step = -2 if is_dark_bg else 2 # Gelap pada bg terang, cerah pada bg gelap
current_l = hsl[2]
for _ in range(50):
current_l = max(0, min(100, current_l + step))
adjusted_hex = hsl_to_hex(hsl[0], hsl[1], current_l)
ratio = contrast_ratio(adjusted_hex, bg_hex)
if ratio >= target_ratio:
return adjusted_hex
# Kembalikan kontras maksimum jika target tidak dapat dicapai
return '#000000' if not is_dark_bg else '#FFFFFF'
Pembatasan Laju untuk API Warna
Mengapa API Warna Memerlukan Pembatasan Laju
API warna mungkin tampak berisiko rendah, tetapi bisa menjadi target: - Serangan enumerasi: Mengulang semua 16,7 juta kode hex untuk memanen database nama warna lengkap. - Scraping: Menggunakan logika konversi Anda sebagai sumber daya komputasi gratis. - Penyalahgunaan: Permintaan volume tinggi dari satu klien yang menurunkan layanan bagi yang lain.
Strategi Implementasi
Untuk API Django, django-ratelimit menyediakan pembatasan laju berbasis dekorator dengan backend Redis:
# settings.py
RATELIMIT_USE_CACHE = 'default' # Menggunakan backend cache Django (Redis direkomendasikan)
# views.py
from django_ratelimit.decorators import ratelimit
@ratelimit(key='ip', rate='100/m', method='GET', block=True)
def color_detail_view(request, hex_code: str):
...
Format Respons Pembatasan Laju
Saat batas laju terlampaui, kembalikan 429 Too Many Requests dengan isi respons yang jelas dan header Retry-After:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1708012345
Content-Type: application/json
{
"error": "rate_limit_exceeded",
"message": "Anda telah melampaui batas laju 100 permintaan per menit.",
"retry_after": 60
}
Pembatasan Laju Berjenjang
Struktur berjenjang yang masuk akal:
| Tingkat | Batas | Kunci |
|---|---|---|
| Anonim | 60 permintaan/menit | Alamat IP |
| Kunci API (gratis) | 300 permintaan/menit | Kunci API |
| Kunci API (berbayar) | 3000 permintaan/menit | Kunci API |
Kunci API dikirim sebagai Authorization: Bearer {key} atau X-API-Key: {key}. Gunakan kunci API sebagai kunci pembatasan laju untuk permintaan terotentikasi:
@ratelimit(key='header:x-api-key', rate='300/m', method='GET')
@ratelimit(key='ip', rate='60/m', method='GET')
def color_detail_view(request, hex_code: str):
# django-ratelimit memeriksa keduanya; batas yang lebih longgar menang untuk permintaan terotentikasi
...
Praktik Terbaik Format Respons
Skema Error yang Konsisten
Setiap respons error — kegagalan validasi, batas laju, tidak ditemukan — harus mengikuti skema yang sama:
{
"error": "invalid_hex",
"message": "Nilai 'gggggg' bukan warna hex yang valid. Diperlukan string hex 3 atau 6 digit.",
"field": "hex_code",
"value": "gggggg"
}
| Bidang | Tujuan |
|---|---|
error |
Kode error yang dapat dibaca mesin (snake_case) |
message |
Penjelasan yang dapat dibaca manusia |
field |
Bidang input mana yang menyebabkan error (untuk error validasi) |
value |
Nilai yang tidak valid (membantu debugging) |
Paginasi untuk Endpoint Koleksi
Endpoint pencarian dengan limit dan offset:
{
"results": [...],
"pagination": {
"total": 47,
"limit": 10,
"offset": 0,
"next": "/api/search?q=blue&limit=10&offset=10",
"prev": null
}
}
Selalu sertakan total agar klien dapat merender kontrol paginasi tanpa mengetahui set hasil lengkap.
Strategi Versioning
Versi API Anda di jalur URL, bukan di header. Versioning URL bersifat eksplisit, dapat di-cache oleh CDN, dan tidak mengharuskan klien menetapkan header kustom:
/api/v1/color/FF5733 # Versi 1
/api/v2/color/FF5733 # Versi 2 (saat perubahan yang memutus diperlukan)
Pertahankan versi sebelumnya setidaknya satu tahun setelah rilis versi utama. Gunakan header deprecation pada versi lama:
Deprecation: Sun, 01 Jan 2026 00:00:00 GMT
Sunset: Sun, 01 Jan 2027 00:00:00 GMT
Link: </api/v2/color/FF5733>; rel="successor-version"
Konfigurasi CORS
Untuk API publik yang dikonsumsi oleh klien browser:
# settings.py (menggunakan django-cors-headers)
CORS_ALLOWED_ORIGINS = [
"https://yoursite.com",
"https://app.yoursite.com",
]
# Atau untuk API yang sepenuhnya publik:
CORS_ALLOW_ALL_ORIGINS = True
# Selalu batasi metode dan header
CORS_ALLOW_METHODS = ['GET', 'POST', 'OPTIONS']
CORS_ALLOW_HEADERS = ['Content-Type', 'Authorization', 'X-API-Key']
Arsitektur Caching
Data warna sangat mudah di-cache. Objek warna untuk #FF5733 tidak akan pernah berubah — matematika konversinya deterministik. Susun strategi cache Anda sesuai:
| Endpoint | Cache-Control | CDN TTL |
|---|---|---|
/api/color/{hex} |
public, max-age=86400, stale-while-revalidate=604800 |
1 hari |
/api/search?q={q} |
public, max-age=3600 |
1 jam |
/api/contrast?fg=X&bg=Y |
public, max-age=86400 |
1 hari |
/api/palette/{hex} |
public, max-age=86400 |
1 hari |
Tambahkan header ETag berdasarkan kode hex untuk permintaan kondisional:
response["ETag"] = f'"{hex_code}"'
response["Last-Modified"] = "Thu, 01 Jan 2026 00:00:00 GMT" # Statis — konten tidak pernah berubah
Klien yang mengirim If-None-Match: "FF5733" mendapatkan respons 304 Not Modified tanpa isi — menghemat bandwidth pada permintaan berulang.
Poin Utama
- Objek warna kanonik harus mencakup semua representasi format (hex, RGB, HSL, CMYK, OKLCH) dalam satu respons — jangan pernah membuat klien membuat beberapa permintaan untuk mendapatkan format yang berbeda.
- Normalisasi input hex secara agresif: hapus
#, perluas 3-digit, ubah ke huruf besar. Validasi dengan regex sebelum pemrosesan apapun. - Endpoint kontras adalah inti dari semua alat aksesibilitas — kembalikan rasio WCAG, lulus/gagal per level, dan level lulus tertinggi dalam satu respons.
- Pencarian warna bernama bekerja andal dengan pencarian awalan dalam memori untuk database kecil; gunakan PostgreSQL
pg_trgmuntuk database besar. - Batasi laju berdasarkan IP untuk permintaan tidak terotentikasi (60/menit adalah nilai yang wajar); gunakan kunci API untuk tingkat yang lebih tinggi.
- Setiap respons error harus mengikuti skema yang sama dengan
error(dapat dibaca mesin) danmessage(dapat dibaca manusia) minimal. - Data warna sangat mudah di-cache — cache CDN 24 jam sesuai untuk endpoint konversi; gunakan
ETagdanstale-while-revalidateuntuk invalidasi cache yang efisien. - Coba Color Converter dan Contrast Checker untuk melihat endpoint API ini beraksi.