Payload CMS
Payload CMS เป็น Headless CMS สร้างด้วย TypeScript Node.js Open Source MongoDB PostgreSQL Admin Panel REST API GraphQL API Authentication Access Control
Distributed System ใช้ Payload เป็น Content API หลาย Frontend เรียก API CDN Cache Redis Webhooks Load Balancer
Payload CMS Setup
# === Payload CMS Setup ===
# 1. สร้างโปรเจค
# npx create-payload-app@latest my-cms
# cd my-cms
# 2. payload.config.ts
# import { buildConfig } from 'payload/config'
# import { mongooseAdapter } from '@payloadcms/db-mongodb'
# import { slateEditor } from '@payloadcms/richtext-slate'
# import { webpackBundler } from '@payloadcms/bundler-webpack'
# import { Articles } from './collections/Articles'
# import { Users } from './collections/Users'
# import { Media } from './collections/Media'
#
# export default buildConfig({
# admin: { user: Users.slug, bundler: webpackBundler() },
# editor: slateEditor({}),
# db: mongooseAdapter({ url: process.env.MONGODB_URI }),
# collections: [Users, Articles, Media],
# typescript: { outputFile: './types.ts' },
# graphQL: { schemaOutputFile: './schema.graphql' },
# })
# 3. Collections — Articles
# // collections/Articles.ts
# import { CollectionConfig } from 'payload/types'
#
# export const Articles: CollectionConfig = {
# slug: 'articles',
# admin: {
# useAsTitle: 'title',
# defaultColumns: ['title', 'status', 'publishedAt'],
# },
# access: {
# read: () => true, // Public read
# create: ({ req: { user } }) => !!user,
# update: ({ req: { user } }) => !!user,
# delete: ({ req: { user } }) => user?.role === 'admin',
# },
# hooks: {
# afterChange: [
# async ({ doc, operation }) => {
# // Webhook: แจ้ง Services อื่นเมื่อ Content เปลี่ยน
# if (operation === 'create' || operation === 'update') {
# await fetch(process.env.WEBHOOK_URL, {
# method: 'POST',
# headers: { 'Content-Type': 'application/json' },
# body: JSON.stringify({
# event: `article.`,
# data: { id: doc.id, title: doc.title, slug: doc.slug },
# }),
# }).catch(console.error)
# }
# },
# ],
# },
# fields: [
# { name: 'title', type: 'text', required: true },
# { name: 'slug', type: 'text', unique: true, admin: { position: 'sidebar' } },
# { name: 'content', type: 'richText' },
# { name: 'excerpt', type: 'textarea' },
# { name: 'coverImage', type: 'upload', relationTo: 'media' },
# { name: 'author', type: 'relationship', relationTo: 'users' },
# { name: 'category', type: 'select', options: [
# { label: 'Technology', value: 'tech' },
# { label: 'Business', value: 'business' },
# { label: 'Lifestyle', value: 'lifestyle' },
# ]},
# { name: 'tags', type: 'array', fields: [
# { name: 'tag', type: 'text' },
# ]},
# { name: 'status', type: 'select', defaultValue: 'draft', options: [
# { label: 'Draft', value: 'draft' },
# { label: 'Published', value: 'published' },
# ]},
# { name: 'publishedAt', type: 'date', admin: { position: 'sidebar' } },
# ],
# }
import json
config = {
"collections": ["Users", "Articles", "Media", "Categories"],
"database": "MongoDB or PostgreSQL",
"api": ["REST API", "GraphQL API", "Local API"],
"auth": "Built-in Authentication + Access Control",
"hooks": "beforeChange, afterChange, beforeRead, afterRead",
"plugins": "SEO, Form Builder, Nested Docs, Redirects",
}
print("Payload CMS Configuration:")
for key, value in config.items():
if isinstance(value, list):
print(f" {key}: {', '.join(value)}")
else:
print(f" {key}: {value}")
Distributed Architecture
# distributed_payload.py — Payload CMS Distributed Architecture
from dataclasses import dataclass, field
from typing import List, Dict
@dataclass
class ServiceNode:
name: str
role: str
technology: str
port: int
replicas: int
class PayloadDistributed:
"""Payload CMS Distributed Architecture"""
def __init__(self):
self.services: List[ServiceNode] = []
def add_service(self, svc: ServiceNode):
self.services.append(svc)
def show_architecture(self):
print(f"\n{'='*55}")
print(f"Payload CMS — Distributed Architecture")
print(f"{'='*55}")
by_role = {}
for svc in self.services:
if svc.role not in by_role:
by_role[svc.role] = []
by_role[svc.role].append(svc)
for role, services in by_role.items():
print(f"\n [{role}]")
for svc in services:
print(f" {svc.name} ({svc.technology})")
print(f" Port: {svc.port} | Replicas: {svc.replicas}")
def caching_strategy(self):
"""Caching Strategy"""
strategies = {
"CDN Cache": {
"what": "Cache API Response ที่ CDN Edge",
"ttl": "5-60 นาที",
"invalidation": "Webhook จาก Payload afterChange hook",
"tools": "Cloudflare, Fastly, CloudFront",
},
"Redis Cache": {
"what": "Cache Database Queries ใน Redis",
"ttl": "1-5 นาที",
"invalidation": "afterChange hook ลบ Cache Key",
"tools": "Redis, Upstash, ElastiCache",
},
"ISR (Next.js)": {
"what": "Incremental Static Regeneration",
"ttl": "60 วินาที revalidate",
"invalidation": "On-demand Revalidation API",
"tools": "Next.js, Vercel",
},
}
print(f"\n Caching Strategies:")
for name, info in strategies.items():
print(f"\n [{name}]")
for key, value in info.items():
print(f" {key}: {value}")
# ตัวอย่าง
arch = PayloadDistributed()
services = [
ServiceNode("Payload CMS", "Backend", "Node.js + TypeScript", 3000, 3),
ServiceNode("MongoDB Replica Set", "Database", "MongoDB 7.0", 27017, 3),
ServiceNode("Redis Cache", "Cache", "Redis 7.2", 6379, 2),
ServiceNode("Next.js Frontend", "Frontend", "Next.js 14", 3001, 3),
ServiceNode("Nginx Load Balancer", "Edge", "Nginx", 80, 2),
ServiceNode("MinIO/S3", "Storage", "MinIO", 9000, 1),
ServiceNode("Webhook Worker", "Worker", "BullMQ", 0, 2),
]
for svc in services:
arch.add_service(svc)
arch.show_architecture()
arch.caching_strategy()
# Docker Compose
docker_services = {
"payload": "Node.js CMS Server (Port 3000)",
"mongodb": "MongoDB Replica Set (Port 27017)",
"redis": "Redis Cache (Port 6379)",
"nginx": "Reverse Proxy + Load Balancer (Port 80)",
"minio": "S3-compatible Object Storage (Port 9000)",
"frontend": "Next.js SSR Frontend (Port 3001)",
}
print(f"\n\nDocker Compose Services:")
for svc, desc in docker_services.items():
print(f" {svc}: {desc}")
Multi-tenancy และ Scaling
# multi_tenant.py — Multi-tenancy Pattern
# Payload CMS Multi-tenancy approaches
approaches = {
"Database per Tenant": {
"description": "แต่ละ Tenant มี Database แยก",
"pros": ["Data Isolation สูงสุด", "Performance แยก", "Backup แยก"],
"cons": ["จัดการ Database หลายตัว", "Cost สูง", "Migration ซับซ้อน"],
"code": """
# Payload Config per Tenant
# const tenantConfig = (tenantId) => buildConfig({
# db: mongooseAdapter({
# url: `mongodb://localhost/_db`
# }),
# serverURL: `https://.myapp.com`,
# })
""",
},
"Shared Database + Tenant Field": {
"description": "Database เดียว เพิ่ม tenant field ทุก Collection",
"pros": ["จัดการง่าย", "Cost ต่ำ", "Migration ง่าย"],
"cons": ["Data Leak Risk", "Performance ร่วม", "Query ซับซ้อน"],
"code": """
# Collection with Tenant Field
# {
# slug: 'articles',
# fields: [
# { name: 'tenant', type: 'relationship', relationTo: 'tenants' },
# // ... other fields
# ],
# access: {
# read: ({ req }) => ({
# tenant: { equals: req.user?.tenant }
# }),
# },
# }
""",
},
"Collection per Tenant": {
"description": "Dynamic Collections ตาม Tenant",
"pros": ["แยก Collection ชัดเจน", "Flexible Schema"],
"cons": ["ซับซ้อน", "Payload ไม่รองรับ Dynamic Collections"],
"code": "# ไม่แนะนำ — ใช้ Shared DB + Tenant Field แทน",
},
}
print("Multi-tenancy Approaches:")
for name, info in approaches.items():
print(f"\n [{name}]")
print(f" {info['description']}")
print(f" Pros: {', '.join(info['pros'])}")
print(f" Cons: {', '.join(info['cons'])}")
# Scaling Strategies
scaling = {
"Horizontal Scaling": {
"what": "เพิ่มจำนวน Payload Instances",
"how": "Kubernetes Deployment replicas: 3-10",
"requirement": "Shared Database + Redis Session Store",
},
"Database Scaling": {
"what": "MongoDB Replica Set + Sharding",
"how": "Primary-Secondary-Secondary + Shard Key",
"requirement": "MongoDB Atlas หรือ Self-hosted Replica Set",
},
"CDN + Edge Caching": {
"what": "Cache API Response ที่ Edge",
"how": "Cloudflare Workers + KV Store",
"requirement": "Cache-Control Headers + Webhook Invalidation",
},
"Read Replicas": {
"what": "แยก Read/Write Database",
"how": "Read จาก Secondary, Write จาก Primary",
"requirement": "MongoDB Read Preference: secondaryPreferred",
},
}
print(f"\n\nScaling Strategies:")
for name, info in scaling.items():
print(f"\n [{name}]")
for key, value in info.items():
print(f" {key}: {value}")
Best Practices
- TypeScript: ใช้ TypeScript ตลอด Type-safe Collections Fields Hooks
- Access Control: กำหนด Access Control ทุก Collection ทุก Operation
- Hooks: ใช้ afterChange Hook ส่ง Webhook Invalidate Cache
- CDN Cache: Cache API Response ที่ CDN ลด Load
- Redis Session: ใช้ Redis เก็บ Session สำหรับ Multi-instance
- Monitoring: ใช้ Prometheus Grafana Monitor API Performance
Payload CMS คืออะไร
Headless CMS TypeScript Node.js Open Source MongoDB PostgreSQL Admin Panel REST API GraphQL API Authentication Access Control Hooks Plugins
Headless CMS ต่างจาก Traditional CMS อย่างไร
Headless แยก Backend Frontend ส่ง API ใช้ Frontend อะไรก็ได้ Traditional รวมไว้ด้วยกัน WordPress Headless ยืดหยุ่น Scale ง่ายกว่า
Payload CMS ใช้กับ Distributed System อย่างไร
Content API Server หลาย Frontend เรียก API CDN Cache Redis Cache Webhooks แจ้ง Services Load Balancer กระจาย Traffic Multi-instance
Payload CMS เทียบกับ Strapi อย่างไร
Payload TypeScript Type-safe Local API Code-first Strapi Community ใหญ่ Plugin มาก Payload ยืดหยุ่นกว่า ไม่ต้อง GUI สร้าง Schema
สรุป
Payload CMS Headless CMS TypeScript Node.js Distributed System Content API CDN Cache Redis Webhooks Load Balancer Multi-tenancy Shared DB Tenant Field Horizontal Scaling MongoDB Replica Set Access Control Hooks TypeScript Type-safe
