ในบทความนี้ ผมสำรวจบทบาทสำคัญของ Upstash ในการพัฒนา PlanTripAI สตาร์ทอัพวางแผนการเดินทางขับเคลื่อนด้วย AI การใช้ Upstash Redis มีบทบาทสำคัญในการจัดการด้านสำคัญ เช่น เก็บ license keys สำหรับเข้าถึงผู้ใช้ และบันทึก/แคช trip content อย่างมีประสิทธิภาพ นอกจากนี้ ฟีเจอร์ Rate Limit ของ Upstash เล่นบทบาทสำคัญในการปกป้องระบบ จัดการความถี่ request ให้การป้องกันที่แข็งแกร่งต่อ security threats และรับประกันบริการที่ราบรื่นต่อเนื่องสำหรับผู้ใช้
plantripai.com เป็น AI-based trip planner สร้าง itinerary เฉพาะบุคคลอย่างรวดเร็วจาก preferences: destination, ระยะเวลาพัก, travel style, budget มี unlimited itinerary creation และ download formats หลากหลาย
- @upstash/redis
- @upstash/ratelimit
- next.js
วิธีการทำงาน
บริษัท AI ทั่วไปมักมีหลายวิธีให้ผู้ใช้ลองผลิตภัณฑ์ ที่ PlanTripAI เราให้ทั้ง freemium และ paid plan
ในแผน freemium (ยอดนิยมสุด): ทุกครั้งที่ผู้ใช้สร้าง trip โดยไม่มี valid license จะสร้าง random key ใหม่ เก็บใน Redis hash (redis.hset(trip:${key}, data)) พร้อม trip content Key ให้ temporary access ไปหน้ารายละเอียด trip เมื่อ access หมดอายุ ผู้ใช้ตัดสินใจว่าจะรักษา permanent access ได้หรือไม่
Freemium users มี rate limit (fixedWindow) 3 requests ต่อ 10 วินาที ป้องกัน attacks

แผน paid: ผู้ใช้ซื้อผ่าน payment gateway webhook emit success event เก็บ key ใน Redis เป็น valid key (redis.set(${licenseKey}, true))
ผู้ใช้ paid สร้าง trip: สร้าง random key เก็บใน Redis เพิ่ม trip ใหม่ใน user object link กับ license key
Paid users ได้ rate limit กว้างขึ้น ตรวจสอบ valid key ใน Redis แบบไดนามิก

เก็บ Trip Data
PlanTripAI ใช้ Redis เก็บ trips ที่ผู้ใช้สร้าง Redis เหมาะเพราะ key-value store ที่เร็ว Trips ที่สร้างแล้ว immutable และ hashes ให้เก็บ metadata พร้อม key data
โครงสร้าง trip data ใน Redis hash:
{
"itinerary": [
{ "day": "Day 1", "data": [...] }
],
"info": "This itinerary is designed for a city explorer visiting Paris, France for 2 days.",
"inputs": {
"city": "Paris, France",
"days": 2,
"accommodation": "Paris France Hotel",
"kind": "city explorer",
"currency": "USD",
"budget": 3000,
"transportation": "bus"
},
"createdAt": ...,
"shareable": false
}
ใช้ Upstash Redis API over HTTP เก็บใน free Redis database (10,000 requests/วัน ฟรี พอสำหรับความต้องการ)
import { Redis } from "@upstash/redis";
const redis = new Redis({
url: "..." // UPSTASH_REDIS_REST_URL
token: "..." // UPSTASH_REDIS_REST_TOKEN
});
redis.hset(`trip:${id}`, data);
เก็บ Valid License Keys
Paid subscription: แต่ละ paid user ได้ valid license key เก็บใน Redis เหมาะสำหรับ quick validation (key-value DB ที่เร็ว)
Mark user ว่า valid:
import { Redis } from "@upstash/redis";
const redis = new Redis({ url: "...", token: "..." });
await redis.set(licenseKey, true);
Verify user validity:
import { Redis } from "@upstash/redis";
const redis = new Redis({ url: "...", token: "..." });
const redisLicenseKey = await redis.get(licenseKey);
const valid = Boolean(redisLicenseKey);
if (!valid) {
return new Response(
JSON.stringify({ message: "The license key is invalid!" }),
{ status: 401 }
);
}
// successful code here...
นำ Rate Limits ไปใช้
ฟีเจอร์สำคัญปกป้องแอป: rate limit logic รองรับโดย Upstash Rate Limit (รายละเอียดเพิ่มเติม) บริการนี้สำคัญสำหรับ seamless experience ทั้ง free และ paid users ป้องกัน attacks จากผู้ใช้ร้ายได้สำเร็จโดยไม่กระทบแอปโดยรวม
Code snippet ใช้ next.js middleware จัดการ IP checks ตัดสินใจ proceed หรือไม่:
import { NextResponse, type NextFetchEvent, type NextRequest } from "next/server";
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const cache = new Map();
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.fixedWindow(3, "10s"),
ephemeralCache: cache,
analytics: true,
});
export default async function middleware(
request: NextRequest,
event: NextFetchEvent
): Promise {
const id = request.ip ?? "anonymous";
const blockeds = [];
if (blockeds.includes(id.trim())) {
return new NextResponse(
JSON.stringify({ message: "Blocked :)" }),
{ status: 429, headers: { "Content-Type": "application/json" } }
);
}
const { success, pending, limit, reset, remaining } = await ratelimit.limit(id);
event.waitUntil(pending);
request.headers.set("X-RateLimit-Limit", limit.toString());
request.headers.set("X-RateLimit-Remaining", remaining.toString());
request.headers.set("X-RateLimit-Reset", reset.toString());
return success ? NextResponse.next() : new NextResponse(
JSON.stringify({ message: "Request cannot be processed! You sent too many requests in a given amount of time." }),
{ status: 429, headers: { "Content-Type": "application/json" } }
);
}
export const config = {
matcher: ["/api/generate-trip", "/api/get-license/(.*)"],
};
คำสุดท้าย
ขอบคุณที่อ่าน! PlanTripAI เริ่มต้นเป็น side project จากความอยากรู้เกี่ยวกับการผสาน Upstash Redis กับ Next.js ใน SaaS framework
หวังว่าจะมีประโยชน์และให้ insight คำถามหรือ feedback ยินดีต้อนรับ ติดต่อได้ที่ Twitter