ในคู่มือทีละขั้นตอนนี้ ผมจะแชร์วิธีการสร้างตัวจัดตารางโพสต์ X (Twitter) โดยใช้ Upstash QStash, Upstash Redis, Next.js Server Actions และ Vercel การจัดตารางโพสต์ช่วยให้คุณรักษาความปรากฏตัวที่สม่ำเสมอ เข้าถึงผู้ติดตามในเวลาที่เหมาะสม และจัดการกลยุทธ์เนื้อหาได้อย่างมีประสิทธิภาพ
ข้อกำหนดเบื้องต้น
คุณจะต้องมีสิ่งต่อไปนี้:
- Node.js 18 หรือใหม่กว่า
- บัญชี Upstash
- บัญชี X (Twitter)
- บัญชี Vercel
เทคโนโลยีที่ใช้
| เทคโนโลยี | คำอธิบาย |
|---|---|
| Upstash | แพลตฟอร์มฐานข้อมูล Serverless ใช้ Upstash Queue และ QStash สำหรับเก็บโพสต์ในคิวและเรียก API ตามเวลาที่กำหนด |
| Next.js | เฟรมเวิร์ก React สำหรับเว็บ ใช้ shadcn/ui สำหรับสร้าง UI รวดเร็ว |
| TailwindCSS | เฟรมเวิร์ก CSS สำหรับออกแบบที่ปรับแต่งได้ |
| Vercel | แพลตฟอร์มคลาวด์สำหรับปรับใช้และปรับขนาดแอปพลิเคชันเว็บ |
| Prettier | เครื่องมือจัดรูปแบบโค้ดแบบมีความคิดเห็น สำหรับสไตล์โค้ดที่สม่ำเสมอ |
ขั้นตอนการทำงาน
เพื่อสร้างและปรับใช้ตัวจัดตารางโพสต์ของคุณเอง ให้ทำตามขั้นตอนเหล่านี้:
- ตั้งค่า Upstash Redis
- ตั้งค่า Upstash QStash
- ตั้งค่า Twitter Developer Application
- สร้างแอปพลิเคชัน Next.js ใหม่
- นำระบบยืนยันตัวตนผู้ใช้ด้วย Twitter OAuth 2.0 ไปใช้
- สร้าง UI สำหรับจัดตารางโพสต์
- จัดตารางโพสต์ด้วย Upstash QStash
- ปรับใช้งานบน Vercel
ตั้งค่า Upstash Redis
หลังจากสร้างบัญชี Upstash และเข้าสู่ระบบแล้ว ให้ไปที่แท็บ Redis และสร้างฐานข้อมูล


หลังจากสร้างฐานข้อมูลแล้ว ให้เลื่อนลงจนเจอส่วน REST API และเลือกปุ่ม .env คัดลอกเนื้อหาและเก็บไว้ในที่ปลอดภัย

ตั้งค่า Upstash QStash
เพื่อจัดตารางส่งคำขอ POST ไปยัง endpoint ตามช่วงเวลาที่กำหนด ให้ใช้ QStash ไปที่แท็บ QStash และเลื่อนลงไปที่แท็บ Request Builder

คัดลอก QStash URL, TOKEN, Current Signing Key และ Next Signing Key แล้วเก็บไว้ในที่ปลอดภัย

ตั้งค่า Twitter Developer Application
เพื่อตั้งค่าการยืนยันตัวตนด้วย Twitter OAuth 2.0 ให้สร้างแอปพลิเคชันใน Twitter Developer Portal:
- เปิด Twitter Developer Portal > Projects
- สร้าง Project
- ไปที่แท็บ Settings ในการตั้งค่าแอปพลิเคชัน และทำดังนี้:
- เลือก
Read and write and Direct messageใน App permissions - เลือก
Web App, Automated App or Botใน Type of App - ใส่
https://localhost:3000/api/auth/callback/twitterเป็น Callback URI / Redirect URL
- เลือก
- ไปที่แท็บ
Keys and tokensเลื่อนลงและทำดังนี้:- คัดลอก
Client IDเก็บเป็นTWITTER_CLIENT_ID - คัดลอก
Client Secretเก็บเป็นTWITTER_CLIENT_SECRET
- คัดลอก
เพียงเท่านี้ก็ตั้งค่า Twitter Developer Application สำหรับ OAuth 2.0 เรียบร้อยแล้ว
สร้างแอปพลิเคชัน Next.js ใหม่
เริ่มต้นด้วยการสร้างโปรเจกต์ Next.js ใหม่ เปิดเทอร์มินัลและรันคำสั่ง:
npx create-next-app@latest schedule-qstash-queue-upstash
เมื่อถูกถาม ให้เลือก:
Yesสำหรับ TypeScriptNoสำหรับ ESLintYesสำหรับ Tailwind CSSNoสำหรับโฟลเดอร์src/Yesสำหรับ App RouterNoสำหรับปรับแต่ง import alias เริ่มต้น (@/*)
เมื่อเสร็จแล้ว ให้เข้าไปในไดเรกทอรีโปรเจกต์และรันในโหมดพัฒนา:
cd schedule-qstash-queue-upstash npm run dev
แอปจะรันบน localhost:3000
สร้างไฟล์ .env ที่รูทของโปรเจกต์ และเพิ่มค่าที่เก็บไว้จากขั้นตอนก่อนหน้า:
# .env
# Twitter Environment Variables
TWITTER_CLIENT_ID="..."
TWITTER_CLIENT_SECRET="..."
TWITTER_AUTH_CALLBACK_URL="https://localhost:3000/api/auth/callback/twitter"
# Upstash Environment Variables
UPSTASH_REDIS_REST_URL="https://...upstash.io"
UPSTASH_REDIS_REST_TOKEN="...="
QSTASH_URL="https://qstash.upstash.io/v2/publish/"
QSTASH_TOKEN="...="
QSTASH_CURRENT_SIGNING_KEY="sig_..."
QSTASH_NEXT_SIGNING_KEY="sig_..."
ผสานรวมคอมโพเนนต์ shadcn/ui
เพื่อสร้าง UI รวดเร็ว ให้ตั้งค่า shadcn/ui กับ Next.js รันคำสั่ง:
npx shadcn-ui@latest init
ตอบคำถามการตั้งค่า components.json ดังนี้:
Yesสำหรับ TypeScriptDefaultสำหรับสไตล์Slateสำหรับสีพื้นฐานapp/globals.cssสำหรับไฟล์ CSS ทั่วไปyesสำหรับ CSS variables- เว้นว่าง สำหรับ tailwind prefix
tailwind.config.tsสำหรับตำแหน่ง tailwind.config.js@/componentsสำหรับ alias คอมโพเนนต์@/lib/utilsสำหรับ alias utilsYesสำหรับ React Server ComponentsYesสำหรับเขียน config ลง components.json
ติดตั้งคอมโพเนนต์ที่ต้องการ:
npx shadcn-ui@latest add button npx shadcn-ui@latest add input npx shadcn-ui@latest add textarea npx shadcn-ui@latest add toast npx shadcn-ui@latest add popover npx shadcn-ui@latest add calendar
จะได้โฟลเดอร์ ui ภายใน app/components พร้อมคอมโพเนนต์ต่างๆ
แก้ไข app/layout.tsx เพื่อเพิ่ม Toaster:
// File: app/layout.tsx
import './globals.css'
import { cn } from '@/lib/utils'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { Toaster } from '@/components/ui/toaster'
const fontSans = Inter({
subsets: ['latin'],
variable: '--font-sans',
})
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode }>) {
return (
{children}
)
}
สร้าง Upstash Queue สำหรับเก็บโพสต์ที่จัดตารางไว้
ในส่วนนี้ จะเรียนรู้การใช้ Upstash Queue เก็บข้อมูลโพสต์ที่จัดตารางไว้ และสร้างโค้ดฝั่งเซิร์ฟเวอร์ที่ทำงานเป็นฟังก์ชัน (Server Actions) แทน API route
ติดตั้ง SDK ของ Upstash:
npm install @upstash/qstash @upstash/queue @upstash/redis@1.28.0
แพ็กเกจที่ติดตั้ง:
@upstash/qstash: SDK สำหรับโต้ตอบกับ QStash ผ่าน HTTP@upstash/queue: SDK จัดการคิวข้อความแบบสตรีม บน Upstash Redis@upstash/redis: SDK สำหรับ Redis ผ่าน REST API
เริ่มต้น Upstash Redis และ Queue Clients
สร้างไฟล์ lib/upstash.ts:
// File: lib/upstash.ts
import { Redis } from '@upstash/redis'
import { Queue } from '@upstash/queue'
export const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL as string,
token: process.env.UPSTASH_REDIS_REST_TOKEN as string,
})
export const queue = new Queue({
redis,
queueName: 'tweets',
concurrencyLimit: 5,
})
โค้ดข้างต้น:
- นำเข้าคลาส
RedisและQueue - ส่งออกอินสแตนซ์
redisชี้ไปที่ URL Upstash Redis พร้อม token - ส่งออกอินสแตนซ์
queueชื่อคิวtweetsและconcurrencyLimit= 5
ใช้ queue instance สร้าง Server Action ที่รับข้อความและวันที่โพสต์ แล้วส่งเข้าคิว สร้างไฟล์ app/schedule.server.tsx:
// File: app/schedule.server.tsx
'use server'
import { queue } from '@/lib/upstash'
import type { FormProps } from './form'
export async function schedule(_: any, formData: FormData): Promise {
try {
const tweet_text = formData.get('tweet_text') as string
const tweet_date = formData.get('tweet_date') as string
const now = new Date().getTime()
const delay = new Date(tweet_date).getTime() - now
await queue.sendMessage({ tweet_text, tweet_date }, delay)
return { ok: true, tweet_date }
} catch (e) {
console.log(e)
return { ok: false }
}
}
โค้ดทำงาน:
- นำเข้า
queueinstance - ส่งออก server action ชื่อ
scheduleรับฟอร์มที่มีข้อความและวันที่ - คำนวณช่วงเวลาจนถึงเวลาที่จัดตารางไว้
- ใส่ข้อมูลโพสต์พร้อม delay ที่คำนวณได้
วิธีนี้ทำให้โพสต์ถูกเพิ่มในคิวเมื่อถึงเวลาเท่านั้น ช่วยจัดการสถานะคิวได้ง่ายขึ้น
นำระบบยืนยันตัวตนผู้ใช้ด้วย Twitter OAuth 2.0 ไปใช้
ส่วนนี้จะสอนการตั้งค่า Twitter OAuth 2.0 โดยใช้ Twitter SDK สร้าง endpoint ให้ผู้ใช้อนุญาตสิทธิ์ และเก็บ access token ใน Upstash Redis
ติดตั้งไลบรารี Twitter OAuth:
npm install twitter-api-sdk
สร้าง Twitter Authentication Client
สร้างไฟล์ lib/twitter.ts:
// File: lib/twitter.ts
import { auth } from 'twitter-api-sdk'
export const authClient = new auth.OAuth2User({
client_id: process.env.TWITTER_CLIENT_ID as string,
callback: process.env.TWITTER_AUTH_CALLBACK_URL as string,
client_secret: process.env.TWITTER_CLIENT_SECRET as string,
scopes: ['tweet.write', 'tweet.read', 'offline.access', 'users.read'],
})
เตรียม Twitter Authorization URL
ขั้นแรกของ OAuth 2.0 คือ redirect ผู้ใช้ไปยัง authorization URL สร้างไฟล์ app/api/auth/twitter/route.ts:
// File: app/api/auth/twitter/route.ts
export const dynamic = 'force-dynamic'
import { NextResponse } from 'next/server'
import { authClient } from '@/lib/twitter'
export async function GET() {
const authUrl = authClient.generateAuthURL({
state: 'state',
code_challenge: 'challenge',
code_challenge_method: 'plain',
})
return NextResponse.redirect(authUrl, 303)
}
เตรียม Authorization Callback URL
ขั้นที่สอง คือจัดการ callback จาก Twitter สร้างไฟล์ app/api/auth/callback/twitter/route.ts:
// File: app/api/auth/callback/twitter/route.ts
export const dynamic = 'force-dynamic'
import { redis } from '@/lib/upstash'
import { NextResponse } from 'next/server'
import { authClient } from '@/lib/twitter'
export async function GET(request: Request) {
const code = new URL(request.url).searchParams.get('code')
if (!code) return NextResponse.json({}, { status: 403 })
authClient.generateAuthURL({
state: 'state',
code_challenge: 'challenge',
code_challenge_method: 'plain',
})
const { token: { access_token }, } = await authClient.requestAccessToken(code)
await redis.set('twitter_oauth_access_token', access_token)
return NextResponse.redirect(new URL('/', request.url), 303)
}
สร้าง endpoint ตรวจสอบสถานะการยืนยันตัวตน app/api/auth/twitter/authenticated/route.ts:
// File: app/api/auth/twitter/authenticated/route.ts
export const dynamic = 'force-dynamic'
import { redis } from '@/lib/upstash'
import { NextResponse } from 'next/server'
export async function GET() {
try {
const access_token = await redis.get('twitter_oauth_access_token')
if (!access_token) return NextResponse.json({ ok: false }, { status: 200 })
return NextResponse.json({ ok: true }, { status: 200 })
} catch(e) {}
return NextResponse.json({ ok: false }, { status: 200 })
}
สร้าง UI สำหรับจัดตารางโพสต์
สร้างคอมโพเนนต์ฟอร์ม reactive ด้วย React hooks useFormState และ useFormStatus สร้างไฟล์ app/form.tsx:
// File: app/form.tsx
'use client'
import { cn } from '@/lib/utils'
import { format } from 'date-fns'
import { Button } from '@/components/ui/button'
import { Calendar } from '@/components/ui/calendar'
import { useToast } from '@/components/ui/use-toast'
import { Textarea } from '@/components/ui/textarea'
import { Calendar as CalendarIcon } from 'lucide-react'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { useFormStatus } from 'react-dom'
import { useEffect, useState } from 'react'
export interface FormProps {
ok?: boolean
tweet_date?: string
}
export default function ({ ok, tweet_date }: FormProps) {
const { toast } = useToast()
const { pending } = useFormStatus()
useEffect(() => {
if (!pending) {
if (ok) {
const scheduleForm = document.getElementById('schedule_form') as HTMLFormElement
if (scheduleForm) scheduleForm.reset()
toast({ title: 'Scheduled Tweet', description: tweet_date })
} else {
toast({ variant: 'destructive', title: 'Uh oh! Something went wrong.', description: 'There was a problem with your request.' })
}
}
}, [pending])
const [date, setDate] = useState()
return (
<>
Tweet Scheduler
>
)
}
สร้างคอมโพเนนต์แสดงสถานะการยืนยันตัวตน Twitter components/twitter.tsx:
// File: components/twitter.tsx
'use client'
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
export default function () {
const state = {
pending: { message: '...', variant: 'outline' },
true: { message: '✔️ Authenticated Instance', variant: 'secondary' },
false: { message: '1-Time Authentication with Twitter →', variant: 'destructive' },
}
const [authenticated, setAuthenticated] = useState('pending')
useEffect(() => {
fetch('/api/auth/twitter/authenticated')
.then(res => res.json())
.then(res => { setAuthenticated(res.ok as boolean) })
}, [])
return (
)
}
สร้างหน้าหลัก app/page.tsx:
// File: app/page.tsx
'use client'
import Form from './form'
import { useFormState } from 'react-dom'
import Twitter from '@/components/twitter'
import { schedule } from './schedule.server'
export default function () {
const [state, formAction] = useFormState(schedule, {})
return (
)
}
จัดตารางโพสต์ด้วย Upstash QStash
สร้าง endpoint ที่ดึงข้อมูลจาก Upstash Queue และโพสต์ไปยัง Twitter API สร้างไฟล์ app/api/schedule/route.ts:
// File: app/api/schedule/route.ts
export const dynamic = 'force-dynamic'
import { NextResponse } from 'next/server'
import { queue, redis } from '@/lib/upstash'
import { verifySignatureAppRouter } from '@upstash/qstash/dist/nextjs'
interface TweetBody {
tweet_text?: string
tweet_date?: number
}
async function tweet(access_token: string, text: string | undefined) {
if (text) {
await fetch('https://api.twitter.com/2/tweets', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: ['Bearer', access_token].join(' '),
},
body: JSON.stringify({ text }),
})
}
}
async function handler() {
const access_token = await redis.get('twitter_oauth_access_token')
if (!access_token) return NextResponse.json({}, { status: 403 })
const tweets = await Promise.all(Array.from({ length: 4 }, () => queue.receiveMessage()))
await Promise.all(tweets.map(i => tweet(access_token, i?.body?.tweet_text)))
return NextResponse.json({}, { status: 200 })
}
export const POST = verifySignatureAppRouter(handler)
ใช้ QStash จัดตารางเรียก endpoint นี้ทุกวันเที่ยงคืน:
- ไปที่แท็บ QStash ในคอนโซล Upstash
- เลือกแท็บ Endpoint
- ใส่ URL เป็น absolute URL ของ
/api/schedule - เลือก Type =
Scheduled - เลือก Every =
every day at midnight - กด Schedule
ปรับใช้งานบน Vercel
ขั้นตอนปรับใช้:
- สร้าง GitHub repository สำหรับโค้ดแอป
- ไปที่ Vercel Dashboard สร้าง New Project
- เชื่อมกับ GitHub repository
- ใน Settings อัปเดต Environment Variables ให้ตรงกับไฟล์ .env ในเครื่อง
- กด Deploy
ข้อมูลเพิ่มเติม
- GitHub Repository
- Twitter OAuth 2.0 Flow
- Generating Twitter App Tokens
- React Form Hooks
- Next.js Server Actions
สรุป
ในคู่มือนี้ คุณได้เรียนรู้การสร้างตัวจัดตารางโพสต์ X ที่ทรงพลังด้วย Upstash Redis และ QStash ความสามารถในการปรับขนาดของ Upstash รับประกันการเก็บข้อมูลและจัดตารางที่น่าเชื่อถือ ร่วมกับการปรับใช้บน Vercel ที่ราบรื่น สร้างระบบอัตโนมัติสมบูรณ์