ในโพสต์นี้ เราจะแสดงวิธีใช้ Upstash สร้าง quota system สำหรับ SaaS application ที่ทำด้วย Next.js และ Prisma เราจะใช้ Next.js API routes สร้าง simple API
ใน SaaS application หลายแห่ง คุณอาจเคยเจอ quota system ระบบที่จำกัดจำนวน actions ที่ user ทำได้ในเวลาที่กำหนด
ในตัวอย่างของเรา user สามารถส่ง 1,000 API requests ต่อเดือน บนแผน "Free" เท่านั้น หาก user พยายามส่งเกิน 1,000 requests แอปพลิเคชันจะบล็อก user ไม่ให้ส่ง requests เพิ่ม
API requests เหล่านี้ใช้ดึงเนื้อหา spreadsheet แปลงเป็น JSON นี่คือสิ่งที่ fastsheet ทำ แปลง Google Sheets spreadsheet ใดๆ ให้เป็น JSON API

กำหนด Database Schema
ตามที่กล่าว เราจะใช้ Prisma เป็น ORM โต้ตอบกับ database นี่คือตัวอย่าง User model ที่ implement quota system รวมถึง Spreadsheet model เก็บ spreadsheets:
model User {
id Int @id @default(autoincrement())
planId String @default("FREE")
email String @unique
quota Int @default(0)
spreadsheets Spreadsheet[]
}
model Spreadsheet {
id Int @id @default(autoincrement())
userId Int
user User @relation(fields: [userId], references: [id])
content String
}
- Field
planIdคือแผนที่ user subscribe - Field
quotaคือจำนวน API requests ที่ user ทำในเดือนปัจจุบัน - Model
Spreadsheetแทน spreadsheet ที่เชื่อมต่อกับUser Userหนึ่งมีSpreadsheetหลายตัว link เข้าไปได้
เพื่อให้เรียบง่าย เราจะจัดการแค่แผน "FREE" ที่อนุญาต 1,000 API requests ต่อเดือน
เพิ่ม Quota ของ User
เราอยากเพิ่ม quota ของ user ทุกครั้งที่ user ส่ง API request
ต nhìnดูง่ายและตรงไปตรงมา เราสามารถเพิ่ม quota ของ user ทีละ 1 ทุกครั้งที่ user ส่ง API request บน Next.js API route:
// pages/api/spreadsheets/[id]/index.ts
import type { NextApiRequest, NextApiResponse } from "next";
const MAX_FREE_TIER_QUOTA = 1000;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
// ดึง ID ของ spreadsheet จาก URL
const { id } = req.params;
// ดึง spreadsheet และ user ที่เกี่ยวข้องจาก database
const spreadsheet = await prisma.spreadsheet.findUnique({
where: { id: Number(id) },
include: { user: true },
});
// Verify spreadsheet exists
if (!spreadsheet) {
return res.status(404).json({ error: "Spreadsheet not found" });
}
// Verify user not over quota
if (spreadsheet.user.quota >= MAX_FREE_TIER_QUOTA) {
return res.status(429).json({ error: "Quota exceeded" });
}
// เพิ่ม quota ของ user ทีละ 1
await prisma.user.update({
where: { id: spreadsheet.user.id },
data: { quota: { increment: 1 } },
});
// ส่งคืนเนื้อหา spreadsheet
return res.json({ content: spreadsheet.content });
}
แต่มีปัญหากับวิธีนี้ ไม่เร็วเท่าที่ควร
เราสร้าง database transaction เพื่อเพิ่ม quota ของ user บนทุก API request
เราเข้าถึง database ได้ quota ของ user สร้าง transaction increment quota ทีละ 1 แล้วส่ง spreadsheet content
สิ่งนี้ไม่ optimal หลายเหตุผล:
- Database ของคุณอาจช้า จะทำให้ response time ช้า
- Database อาจอยู่ไกลจาก server อีกครั้ง response time ช้า
- โค้ดนี้ยากที่จะทำให้ Edge compatible ต้องสร้าง multiple database replicas รอบโลก (ไม่ใช่งานง่าย!)
ใช้ Upstash เพิ่ม Quota ของ User
เพื่อแก้ปัญหานี้ เราจะใช้ Upstash Redis® เพิ่ม quota ของ user Upstash Redis® เป็น Redis® database ที่เร็วและน่าเชื่อถือ host บน cloud สำหรับ serverless environments พร้อมสำหรับ Edge (รันโค้ดใกล้ผู้ใช้)
เราจะใช้คำสั่ง INCR ของ Redis® เพื่อเพิ่ม quota ของ user ทีละ 1 คำสั่งนี้เป็น atomic หมายถึง execute แค่ครั้งเดียว และเร็วมาก
นี่คือโค้ดเดียวกับก่อนหน้า แต่ใช้ Upstash Redis® จัดการ quota ของ user:
// pages/api/spreadsheets/[id]/index.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { Redis } from "@upstash/redis";
const MAX_FREE_TIER_QUOTA = 1000;
// Generate Upstash Redis instance จาก environment variables
// ตรวจสอบว่า defined ใน .env file แล้ว
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
// ดึง ID ของ spreadsheet จาก URL
const { id } = req.params;
// ดึง spreadsheet และ user ที่เกี่ยวข้องจาก database
const spreadsheet = await prisma.spreadsheet.findUnique({
where: { id: Number(id) },
include: { user: true },
});
// Verify spreadsheet exists
if (!spreadsheet) {
return res.status(404).json({ error: "Spreadsheet not found" });
}
// Redis quota key unique สำหรับแต่ละ user โดย ID
const quotaKey = `user:${spreadsheet.user.id}:quota`;
// ดึง quota ของ user จาก Redis
const quota = await redis.incr(quotaKey);
// ถ้า key ไม่มีอยู่ก่อน ค่าที่คืนจะเป็น 1
// ตั้ง expiration 1 วัน
if (quota === 1) {
await redis.expire(quotaKey, 60 * 60 * 24);
}
// Verify user not over quota
if (quota > MAX_FREE_TIER_QUOTA) {
return res.status(429).json({ error: "Quota exceeded" });
}
// ส่งคืนเนื้อหา spreadsheet
return res.json({ content: spreadsheet.content });
}
การใช้ Upstash Redis® ทำให้โค้ด scalable ได้ง่าย ใช้พลังของ Upstash Redis® Edge รันโค้ดใกล้ผู้ใช้
สรุป
Upstash Redis® ทำให้ implement caching system สำหรับ serverless environment ง่ายมาก
เราใช้ implement quota system แต่วิดีโอแนวคิดเดิมนำไปใช้ได้กับ use-cases มากกว่านี้ เช่น caching database queries เพิ่มเติม หรือ caching ผลลัพธ์ของ API request
เป้าหมายสุดท้ายของ caching คือทำให้ API เร็วขึ้น และน่าจะ consume ทรัพยากรน้อยลง (เช่น Planetscale database read/writes queries)
ในบทความหน้า เราจะดูวิธีใช้ Upstash QStash ดึง quota ของ user จาก Redis® เก็บใน database เพื่อให้ quota ของ user ทันสมัยเสมอและไม่หาย
ดู Live
อยากดู optimization แบบนี้ live ไปเช็ก fastsheet API service ที่แปลง Google Sheets เป็น API ในไม่กี่คลิก ฟรีใช้งานพร้อม free tier ใหญ่