การออกแบบ Color API: REST Endpoint สำหรับข้อมูลสี
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.
Color API อาจดูเฉพาะทางมาก จนกว่าคุณจะนึกถึงว่ามีเครื่องมือกี่ชิ้นที่ใช้ข้อมูลสี: เครื่องมือออกแบบที่ต้องการการแปลง ตัวตรวจสอบการเข้าถึงที่ตรวจสอบคอนทราสต์ คอมโพเนนต์ตัวเลือกสีที่ต้องการการค้นหาสีที่มีชื่อ และตัวสร้างจานสีที่ผลิตความสอดคล้องกัน หากคุณกำลังสร้างสิ่งใดสิ่งหนึ่งเหล่านี้ หรือจัดหาข้อมูลสีให้กับนักพัฒนาคนอื่น คุณต้องการ REST API ที่ออกแบบมาอย่างดี
คู่มือนี้ครอบคลุมพื้นที่การออกแบบทั้งหมด: การตัดสินใจเกี่ยวกับ schema โครงสร้าง endpoint รูปแบบการตอบสนอง การจัดการข้อผิดพลาด และการจำกัดอัตรา — จากมุมมองของผู้ที่สร้าง endpoint ที่ขับเคลื่อน Color Converter และ Contrast Checker ที่ ColorFYI
การออกแบบ Schema API สำหรับข้อมูลสี
Object สีหลัก
สีมีหลายการแสดงผล — hex, RGB, HSL, CMYK, OKLCH — และหลายฟิลด์ metadata Object การตอบสนองแบบ canonical ควรรวมทั้งหมด ช่วยให้ client ใช้รูปแบบใดก็ได้ที่ต้องการโดยไม่ต้องส่งคำขอเพิ่มเติม:
{
"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
}
การรวม is_dark (ที่มาจากความสว่าง) ช่วยให้ client ไม่ต้องนำการคำนวณความสว่าง WCAG มาใช้เอง การรวม name และ name_distance ช่วยให้ client แสดงสีที่มีชื่อใกล้เคียงที่สุดโดยไม่ต้องเรียก API แยกต่างหาก
แบบแผนการตั้งชื่อ
ใช้ snake_case สำหรับ JSON key (สอดคล้องกับ REST API ส่วนใหญ่) จัดกลุ่มฟิลด์ที่เกี่ยวข้องเป็น object (rgb, hsl, cmyk) แทนที่จะแผ่ออก (rgb_r, rgb_g, rgb_b) Object ที่ซ้อนกันอ่านง่ายกว่า แยกโครงสร้างใน JavaScript ได้ง่ายกว่า และ mapping ไปยัง typed interface ได้อย่างชัดเจน:
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;
}
การ Normalize Hex
รับสีใน hex หลายรูปแบบ — มีหรือไม่มี #, 3-หลักหรือ 6-หลัก, ตัวพิมพ์ใหญ่หรือเล็ก — และ normalize ใน API layer ก่อนการประมวลผลใดๆ:
# ตัวอย่าง Python/Django
import re
def normalize_hex(raw: str) -> str:
"""Normalize อินพุต hex เป็น 6-หลักตัวพิมพ์ใหญ่โดยไม่มี #"""
clean = raw.strip().lstrip('#').upper()
if not re.match(r'^[0-9A-F]{3}$|^[0-9A-F]{6}$', clean):
raise ValueError(f"สีในรูปแบบ hex ไม่ถูกต้อง: {raw!r}")
# ขยาย 3-หลักเป็น 6-หลัก
if len(clean) == 3:
clean = ''.join(c * 2 for c in clean)
return clean
คืนค่ารูปแบบที่ normalize แล้วในการตอบสนอง — สิ่งนี้ขจัดความคลุมเครือและทำให้การ cache ที่ปลายทางคาดเดาได้มากขึ้น
Endpoint การแปลง Hex/RGB/HSL
GET /api/color/{hex}
Endpoint หลักคืนค่า object สีเต็มรูปแบบสำหรับ hex code ที่กำหนด นี่คือสิ่งที่ Color Converter ใช้เพื่อเติมฟิลด์รูปแบบทั้งหมดพร้อมกัน:
GET /api/color/FF5733
GET /api/color/%23FF5733 (# ที่ encode URL)
GET /api/color/f57 (3-หลัก, ตัวพิมพ์เล็ก)
ทั้งสามควรคืนค่าการตอบสนองเดียวกันสำหรับ #FF5733
การตอบสนอง:
{
"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
สำหรับการแปลงอินพุตตามอำเภอใจที่อาจไม่ใช่ hex — ชื่อสี CSS, ชุด RGB, ค่า HSL:
POST /api/convert
Content-Type: application/json
{
"input": "rgb(255, 87, 51)",
"from": "rgb"
}
การตอบสนอง: Object สีเต็มรูปแบบเดียวกัน
รับค่า from: hex, rgb, hsl, cmyk, oklch, name ตรวจสอบรูปแบบอินพุตให้ตรงกับประเภท from ที่ประกาศ และคืนค่า 422 พร้อมข้อความแสดงข้อผิดพลาดที่ชัดเจนหากไม่ตรงกัน
ตัวอย่าง 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 ข้อมูลสีอย่างจริงจัง — hex code ที่กำหนดจะให้ผลลัพธ์เดิมเสมอ ดังนั้น cache จึงมีอายุยาวนานมากได้
API การค้นหาสีและการเติมอัตโนมัติ
GET /api/search
การค้นหาสีที่มีชื่อรองรับการเติมอัตโนมัติในตัวเลือกสีและช่องค้นหา พารามิเตอร์ query คือชื่อสีบางส่วน:
GET /api/search?q=coral
GET /api/search?q=teal&limit=5
GET /api/search?q=%230d&source=css // ค้นหาตามคำนำหน้า hex
การตอบสนอง:
{
"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"
}
ข้อพิจารณาในการพัฒนาการค้นหา
สำหรับฐานข้อมูลสีที่มีชื่อขนาดเล็ก (~2,000 รายการ) การค้นหาคำนำหน้าใน memory พร้อม fuzzy matching มีความเร็วเพียงพอ:
from difflib import SequenceMatcher
def search_colors(query: str, limit: int = 10) -> list[dict]:
q = query.lower().strip()
results = []
for color in NAMED_COLORS: # รายการที่โหลดไว้ล่วงหน้า
name = color["name"].lower()
if q in name:
# การจับคู่ substring ตรง: คะแนนตามตำแหน่ง
pos = name.index(q)
score = 1.0 - (pos / len(name)) * 0.3
else:
# Fuzzy match
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]
สำหรับฐานข้อมูลที่มีมากกว่า 10,000 รายการ ใช้ส่วนขยาย pg_trgm ของ PostgreSQL พร้อม trigram indexing สำหรับ fuzzy search ระดับ sub-millisecond:
-- เปิดใช้ส่วนขยาย
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- เพิ่ม index
CREATE INDEX idx_color_name_trgm ON named_colors USING gin(name gin_trgm_ops);
-- Query
SELECT hex, name, source, similarity(name, 'coral') AS score
FROM named_colors
WHERE name % 'coral'
ORDER BY score DESC
LIMIT 10;
การปรับปรุงการเติมอัตโนมัติ
สำหรับการเติมอัตโนมัติแบบ search-as-you-type ปรับให้เหมาะสมสำหรับ latency ต่ำ:
- Debounce ที่ client — ขั้นต่ำ 150ms ก่อนส่งคำขอ
- กำหนดความยาว query ขั้นต่ำ — ปฏิเสธ query ที่สั้นกว่า 2 ตัวอักษร (คืนค่าผลลัพธ์ว่างทันที)
- Cache ผลลัพธ์ตาม query — query เดียวกันจากผู้ใช้ที่ต่างกันได้ผลลัพธ์เดิม ใช้
Cache-Control: public, max-age=3600 - ตอบสนองเร็วเมื่อไม่มีผลลัพธ์ — ตอบสนองทันทีด้วย
{"results": [], "total": 0}แทนการรัน fuzzy scan เต็มรูปแบบ
Endpoint การตรวจสอบคอนทราสต์
GET /api/contrast
API คอนทราสต์คือเครื่องยนต์เบื้องหลัง Contrast Checker รับ hex code สองรหัสและคืนค่าอัตราส่วนคอนทราสต์ WCAG พร้อมสถานะผ่าน/ล้มเหลวสำหรับแต่ละระดับ:
GET /api/contrast?fg=FF5733&bg=FFFFFF
GET /api/contrast?fg=000000&bg=FFFFFF
การตอบสนอง:
{
"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
}
ฟิลด์ level คืนค่าระดับ WCAG สูงสุดที่คู่สีนั้นบรรลุ ("AAA", "AA", "AA Large", หรือ "Fail") ฟิลด์ passes เป็น true หากมีระดับ WCAG ใดระดับหนึ่งที่ผ่าน — มีประโยชน์สำหรับตัวบ่งชี้สีเขียว/แดงอย่างง่าย
การคำนวณคอนทราสต์
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,
}
การแนะนำทางเลือกที่เข้าถึงได้
ส่วนขยายที่มีประโยชน์: กำหนดสีหน้าที่ล้มเหลวในการตรวจสอบคอนทราสต์กับพื้นหลัง แนะนำการปรับ lightness ขั้นต่ำที่บรรลุ WCAG AA:
def suggest_accessible_foreground(fg_hex: str, bg_hex: str, target_ratio: float = 4.5) -> str:
"""คืนค่าเวอร์ชันของ fg_hex ที่ปรับ lightness แล้วซึ่งผ่าน target_ratio"""
hsl = rgb_to_hsl(*hex_to_rgb(fg_hex))
bg_lum = relative_luminance(bg_hex)
is_dark_bg = bg_lum < 0.179
# ลองปรับ lightness ในทิศทางที่เพิ่มคอนทราสต์
step = -2 if is_dark_bg else 2 # ทำให้มืดบนพื้นหลังสว่าง, สว่างบนพื้นหลังมืด
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
# คืนค่าคอนทราสต์สูงสุดหากเป้าหมายไม่สามารถบรรลุได้
return '#000000' if not is_dark_bg else '#FFFFFF'
การจำกัดอัตราสำหรับ Color API
ทำไม Color API จึงต้องการการจำกัดอัตรา
Color API อาจดูเสี่ยงต่ำ แต่อาจเป็นเป้าหมายของ: - การโจมตีแบบ Enumeration: วนซ้ำผ่าน hex code ทั้ง 16.7 ล้านรหัสเพื่อเก็บเกี่ยวฐานข้อมูลชื่อสีทั้งหมด - Scraping: ใช้ logic การแปลงของคุณเป็นทรัพยากรการคำนวณฟรี - การละเมิด: คำขอปริมาณสูงจาก client เดียวที่ลดคุณภาพบริการสำหรับผู้อื่น
กลยุทธ์การพัฒนา
สำหรับ Django API, django-ratelimit ให้การจำกัดอัตราแบบ decorator พร้อม Redis backend:
# settings.py
RATELIMIT_USE_CACHE = 'default' # ใช้ cache backend ของ Django (แนะนำ Redis)
# 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):
...
รูปแบบการตอบสนองการจำกัดอัตรา
เมื่อเกินขีดจำกัดอัตรา คืนค่า 429 Too Many Requests พร้อมเนื้อหาการตอบสนองที่ชัดเจนและ 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": "คุณเกินขีดจำกัดอัตรา 100 คำขอต่อนาทีแล้ว",
"retry_after": 60
}
ขีดจำกัดอัตราแบบแบ่งระดับ
โครงสร้างแบบแบ่งระดับที่สมเหตุสมผล:
| ระดับ | ขีดจำกัด | Key |
|---|---|---|
| ไม่ระบุตัวตน | 60 คำขอ/นาที | ที่อยู่ IP |
| API key (ฟรี) | 300 คำขอ/นาที | API key |
| API key (เสียเงิน) | 3000 คำขอ/นาที | API key |
API key ส่งผ่าน Authorization: Bearer {key} หรือ X-API-Key: {key} ใช้ API key เป็น key การจำกัดอัตราสำหรับคำขอที่ผ่านการตรวจสอบสิทธิ์:
@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 ตรวจสอบทั้งคู่; ขีดจำกัดที่หลวมกว่าจะชนะสำหรับคำขอที่ผ่านการตรวจสอบ
...
แนวทางปฏิบัติที่ดีที่สุดสำหรับรูปแบบการตอบสนอง
Schema ข้อผิดพลาดที่สอดคล้องกัน
ทุกการตอบสนองข้อผิดพลาด — ความล้มเหลวในการตรวจสอบ, การจำกัดอัตรา, ไม่พบ — ควรปฏิบัติตาม schema เดียวกัน:
{
"error": "invalid_hex",
"message": "ค่า 'gggggg' ไม่ใช่สีในรูปแบบ hex ที่ถูกต้อง คาดว่าเป็น hex string 3 หรือ 6 หลัก",
"field": "hex_code",
"value": "gggggg"
}
| ฟิลด์ | วัตถุประสงค์ |
|---|---|
error |
รหัสข้อผิดพลาดที่เครื่องอ่านได้ (snake_case) |
message |
คำอธิบายที่มนุษย์อ่านได้ |
field |
ฟิลด์อินพุตใดที่ทำให้เกิดข้อผิดพลาด (สำหรับข้อผิดพลาดการตรวจสอบ) |
value |
ค่าที่ไม่ถูกต้อง (ช่วยในการ debug) |
การแบ่งหน้าสำหรับ Collection Endpoint
Endpoint การค้นหาพร้อม limit และ offset:
{
"results": [...],
"pagination": {
"total": 47,
"limit": 10,
"offset": 0,
"next": "/api/search?q=blue&limit=10&offset=10",
"prev": null
}
}
รวม total เสมอเพื่อให้ client สามารถแสดงตัวควบคุมการแบ่งหน้าได้โดยไม่ต้องรู้ชุดผลลัพธ์ทั้งหมด
กลยุทธ์การกำหนดเวอร์ชัน
กำหนดเวอร์ชัน API ของคุณในเส้นทาง URL ไม่ใช่ใน header การกำหนดเวอร์ชัน URL นั้นชัดเจน CDN สามารถ cache ได้ และไม่ต้องให้ client ตั้ง header แบบกำหนดเอง:
/api/v1/color/FF5733 # เวอร์ชัน 1
/api/v2/color/FF5733 # เวอร์ชัน 2 (เมื่อต้องการการเปลี่ยนแปลงที่ขัดแย้ง)
รักษาเวอร์ชันก่อนหน้าอย่างน้อยหนึ่งปีหลังจากการเปิดตัวเวอร์ชันหลัก ใช้ deprecation header บนเวอร์ชันเก่า:
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"
การกำหนดค่า CORS
สำหรับ API สาธารณะที่ใช้โดย browser client:
# settings.py (ใช้ django-cors-headers)
CORS_ALLOWED_ORIGINS = [
"https://yoursite.com",
"https://app.yoursite.com",
]
# หรือสำหรับ API สาธารณะอย่างสมบูรณ์:
CORS_ALLOW_ALL_ORIGINS = True
# จำกัด method และ header เสมอ
CORS_ALLOW_METHODS = ['GET', 'POST', 'OPTIONS']
CORS_ALLOW_HEADERS = ['Content-Type', 'Authorization', 'X-API-Key']
สถาปัตยกรรมการ Caching
ข้อมูลสี cache ได้สูง Object สีสำหรับ #FF5733 จะไม่เปลี่ยนแปลงเลย — การคำนวณการแปลงเป็น deterministic วางโครงสร้างกลยุทธ์ cache ของคุณตามนั้น:
| Endpoint | Cache-Control | CDN TTL |
|---|---|---|
/api/color/{hex} |
public, max-age=86400, stale-while-revalidate=604800 |
1 วัน |
/api/search?q={q} |
public, max-age=3600 |
1 ชั่วโมง |
/api/contrast?fg=X&bg=Y |
public, max-age=86400 |
1 วัน |
/api/palette/{hex} |
public, max-age=86400 |
1 วัน |
เพิ่ม header ETag ตาม hex code สำหรับคำขอแบบมีเงื่อนไข:
response["ETag"] = f'"{hex_code}"'
response["Last-Modified"] = "Thu, 01 Jan 2026 00:00:00 GMT" # คงที่ — เนื้อหาไม่เปลี่ยนแปลง
Client ที่ส่ง If-None-Match: "FF5733" จะได้รับการตอบสนอง 304 Not Modified โดยไม่มีเนื้อหา — ประหยัด bandwidth สำหรับคำขอซ้ำ
ประเด็นสำคัญ
- Object สี canonical ควรรวมการแสดงผลรูปแบบทั้งหมด (hex, RGB, HSL, CMYK, OKLCH) ในการตอบสนองเดียว — อย่าให้ client ต้องส่งคำขอหลายครั้งเพื่อรับรูปแบบที่แตกต่างกัน
- Normalize อินพุต hex อย่างจริงจัง: ลบ
#, ขยาย 3-หลัก, ใช้ตัวพิมพ์ใหญ่ ตรวจสอบด้วย regex ก่อนการประมวลผลใดๆ - Endpoint คอนทราสต์คือหัวใจของเครื่องมือการเข้าถึงใดๆ — คืนค่าอัตราส่วน WCAG, ผ่าน/ล้มเหลวต่อระดับ และระดับที่ผ่านสูงสุดในการตอบสนองเดียว
- การค้นหาสีที่มีชื่อทำงานได้อย่างน่าเชื่อถือด้วยการค้นหาคำนำหน้าใน memory สำหรับฐานข้อมูลขนาดเล็ก; ใช้ PostgreSQL
pg_trgmสำหรับฐานข้อมูลขนาดใหญ่ - จำกัดอัตราตาม IP สำหรับคำขอที่ไม่ผ่านการตรวจสอบสิทธิ์ (60/นาทีเป็นค่าที่สมเหตุสมผล); ใช้ API key สำหรับระดับที่สูงกว่า
- ทุกการตอบสนองข้อผิดพลาดต้องปฏิบัติตาม schema เดียวกันพร้อม
error(เครื่องอ่านได้) และmessage(มนุษย์อ่านได้) อย่างน้อย - ข้อมูลสี cache ได้สูง — CDN cache 24 ชั่วโมงเหมาะสมสำหรับ endpoint การแปลง; ใช้
ETagและstale-while-revalidateสำหรับการยกเลิก cache ที่มีประสิทธิภาพ - ลอง Color Converter และ Contrast Checker เพื่อดู endpoint API เหล่านี้ในการใช้งานจริง