Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

สร้าง LinkTree Alternative แบบฟรี Open-source ด้วย Astro, Upstash และ GitHub

Post นี้เล่าเรื่อง itsmy.fyi (Open-source Alternative to LinkTree) สร้างด้วย Upstash, Astro, GitHub และ Edgio Upstash ช่วย Manage (CRUD) Data ของ Users ทุกคน ให้ Rate Limiting ก้าวหน้ามากกว่า GitHub API สำหรับ CRUD Operations และ Implement Granular Rate Limiting

สร้าง LinkTree Alternative แบบฟรี Open-source ด้วย Astro, Upstash และ GitHub

What we'll be using - เครื่องมือที่ใช้

  • Astro (Front-end และ Back-end)
  • Upstash (Rate Limiting & CRUD Operations)
  • GitHub Issues & Webhooks (Public CMS จัดการ User Profiles)
  • Tailwind CSS (Styling)
  • Edgio (Deployment)

What you'll need - สิ่งที่ต้องมี

  • GitHub Account
  • Upstash Account สร้าง Database

Setting up Upstash Redis - ตั้งค่า Upstash Redis

Login Upstash ไป Tab Redis กด Create Database

สร้าง LinkTree Alternative แบบฟรี Open-source ด้วย Astro, Upstash และ GitHub

สร้าง LinkTree Alternative แบบฟรี Open-source ด้วย Astro, Upstash และ GitHub

สร้าง Database เสร็จ ไป Details Tab Scroll หา Connect your database Section Copy Content เก็บไว้

สร้าง LinkTree Alternative แบบฟรี Open-source ด้วย Astro, Upstash และ GitHub

Scroll หา REST API Section เลือก .env Button Copy Content เก็บไว้

สร้าง LinkTree Alternative แบบฟรี Open-source ด้วย Astro, Upstash และ GitHub

Setting up the Project - ตั้งค่าโปรเจกต์

Clone App Repo Follow Tutorial Fork Project:

git clone https://github.com/rishi-raj-jain/itsmy.fyi
cd itsmy.fyi
yarn install

Clone เสร็จ สร้าง .env File ใส่ Items ที่ Save:

# Obtained from your GitHub repo
GITHUB_API_TOKEN="to_create_and_update_github_comments"
GITHUB_WEBHOOK_SECRET="if_you_are_matching_github_webhooks_sha256"
# Obtained from the steps as above
UPSTASH_DB="your_upstash_redis_from_above"
UPSTASH_REDIS_REST_URL="your_upstash_redis_rest__url_from_above"
UPSTASH_REDIS_REST_TOKEN="your_upstash_redis_rest__token_from_above"

หลัง Steps นี้ รัน Local Environment ได้:

yarn run edgio:dev

Repository Structure - โครงสร้าง Repository

Main Folder Structure โปรเจกต์ Circle สีแดง Files ที่จะ Discuss เกี่ยว CRUD Operations, Rate Limiting และ Files ที่ Reference

สร้าง LinkTree Alternative แบบฟรี Open-source ด้วย Astro, Upstash และ GitHub

High-Level Data Flow - High-Level Data Flow

High-level Diagram Data Flow:

  • User Visit itsmy.fyi/me/slug Response ไม่ Cached (หรือ Revalidating) → Call getUserInfo Function → Retrieve User JSON จาก Upstash DB
  • User Create/Update/Delete GitHub Issue → GitHub Trigger Webhook POST ไป Endpoint → Upstash Rate Limiting Evaluate Change → Upstash Create/Update/Delete User JSON(s)

สร้าง LinkTree Alternative แบบฟรี Open-source ด้วย Astro, Upstash และ GitHub

User Profile(s) CRUD Operations via Upstash Redis - CRUD Operations ผ่าน Upstash Redis

Deep Dive Data Fetching, Updating, Deletion สำหรับ User Profile(s) ใช้ Upstash (via ioredis) Fetch/Display Data ตลอด

Why I moved over from GitHub to Upstash for CRUD Operations? - ทำไมย้ายจาก GitHub ไป Upstash?

เริ่มต้น GitHub เป็น Data Source: GitHub Issues as Data Forms, GitHub Webhooks CRUD User JSONs ใน Repo แต่ Limitations GitHub REST API: 1,000 requests per hour per repository Restrict และ Regress Intended Usage

Upstash ดีกว่าเสนอ 10K Commands Daily Free Plan Minimal Rate เมื่อ Scale Up อนุญาต Greater User Acquisition Near-zero Cost Iterate Faster ไม่กังวล Scaling/Managing Costs Database

getUserInfo: Fetching User Profile Function

getUserInfo Function ใช้ ioredis hget via Slug as Key API Request ไป Upstash ขอ User Profile Page ระบุ Unique slug หาก Profile ไม่มี (หรือ Error) Return { code: 0 } Redirect User ไป 404 Auto ใน Astro Dynamic Route

// File: lib/Upstash/users/get.js
// Read User Profile Code
import redis from "../setup";
export async function getUserInfo(slug) {
  try {
    const userData = await redis.hget("profiles", slug);
    const parsedData = JSON.parse(userData);
    if (parsedData.slug === slug) { return { ...parsedData, code: 1 }; }
    return { code: 0, error: `slug doesn't match for the user.` };
  } catch (e) {
    const error = e.message || e.toString();
    console.log(error);
    return { code: 0, error };
  }
}

CRUD Operations ที่เหลือ:

import redis from "../setup";
// File: @/lib/Upstash/users/delete.js
// Delete User Profile Code
export async function deleteUserInfo(slug) {
  try { await redis.hdel("profiles", slug); return { code: 1 }; }
  catch (e) { console.log(e.message || e.toString()); return { code: 0 }; }
}
// File: @/lib/Upstash/users/post.js
// Create/Update User Profile Code
export async function postUserInfo(data) {
  try { await redis.hset("profiles", data.slug, JSON.stringify(data)); return { code: 1 }; }
  catch (e) { const error = e.message || e.toString(); console.log(error); return { code: 0, error }; }
}

Rate Limiting - การจำกัดอัตรา

Implement Rate-limiting ใน Serverless Environments กับ Edgio ใช้ Upstash Redis Database Client และ Rate Limiter Library @upstash/ratelimit

// @/lib/Upstash/ratelimit.js
// Reference Function to ratelimiting
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
import { getENV } from "@/lib/env";
const url = getENV("UPSTASH_REDIS_REST_URL");
const token = getENV("UPSTASH_REDIS_REST_TOKEN");
export const ratelimit = (number, time) => {
  if (url && token) {
    return new Ratelimit({ redis: new Redis({ url, token }), limiter: Ratelimit.fixedWindow(number, time) });
  }
  return;
};

Rate Limiting ทำได้:

A. Make the use of service - totally free & unlimited

Expose Profile Creation API Publicly! Showcase Benefits: Easy Setup Profile via GUI Anyone สร้าง 3 Profiles ต่อสัปดาห์ผ่าน Website (itsmy.fyi) Unlimited Access Features (Edit Profile, Create Unlimited Profiles) Switch ไป GitHub Way Enforce Rate Limit 3 Profiles/Week โดย IP Address as Key

// Rate limit 3 profiles in a week via the web for a user
const ratelimitUser = ratelimit(3, 7 * 24 * 60 * 60 + " s");
if (rateLimiter) {
  // Look at the x-0-client-ip set by Edgio in serverless
  const result = await rateLimiter.limit("x-0-client-ip");
  limit = result.limit;
  remaining = result.remaining;
  if (!result.success) { // Return a message }
}

B. Implement Granular Moderation to Number of Edits by a User

Rate Limiting Moderate Number of Edits โดย User ใน 1 นาที Based on GitHub Usernames ปัจจุบัน Allow 3 Changes/Minute Reduce Unseen Spam

// @/pages/github/hook/issue.js
const rateLimiter = ratelimit(3, "60 s");
if (rateLimiter) {
  const result = await rateLimiter.limit(context.sender.login);
  limit = result.limit;
  remaining = result.remaining;
  if (!result.success) {
    return { headers: { "X-RateLimit-Limit": limit, "X-RateLimit-Remaining": remaining }, body: JSON.stringify({ message: "Too many updates in 1 minute. Please try again in a few minutes." }) };
  }
}

Implementing stale-while-revalidate on the edge for all user profiles - Implement Stale-While-Revalidate บน Edge สำหรับ User Profiles ทั้งหมด

Code อธิบายการใช้ Stale While Revalidate ปรับปรุง Cache Hit Rates ใน routes.js router.match Match All User Profiles (Start with /me/) Inside Cache Method Prevent Browser Caching Enable Edge Caching Only Serve Users Fast + Latest Content Edge Option maxAgeSeconds: 1 Cache Data 1 วินาที staleWhileRevalidateSeconds Set 1 Year Serve จาก Cache ขณะ Refresh Cache

// User path(s)
router.match("/me/:path", ({ cache, removeUpstreamResponseHeader }) => {
  // Remove cache-control header from Astro's standalone server
  removeUpstreamResponseHeader("cache-control");
  // Disable in browser caching, use Edgio's edge to use SWR
  cache({
    edge: { maxAgeSeconds: 1, staleWhileRevalidateSeconds: 60 * 60 * 24 * 365 },
    browser: false,
  });
});

Stale While Revalidate ช่วย Improve Performance ลด Load Server Response เร็วกว่าให้ User

Creating dynamic user profiles on the fly - สร้าง Dynamic User Profiles On-the-fly

Astro ทำ Dynamic Routes ง่าย ใน App มี src/pages/me/[slug].astro Map Pages Start with /me/ Examples: /me/rishi-raj-jain, /me/some-other-user

Fetching user profiles - ดึง User Profiles

Fetch Data Current User ใช้ slug Query Parameter จาก Astro Params เรียก getUserInfo Function (อธิบายข้างบน) ได้ User Data Related หาก Not Found/Error Redirect Visitor ไป 404

import { getUserInfo } from "@/lib/Upstash/users";
// Extract slug query
const { slug } = Astro.params;
// Get data from Upstash using getUserInfo function
const { name: userName, image: userImage, links = [], socials = [], about = "", og = {}, background = {}, code = 1 } = await getUserInfo(userSlug);
// In case code: 0 received, redirect to 404
if (code === 0) { return Astro.redirect("/404"); }

Deploy from CLI - Deploy จาก CLI

Production Build Test Local:

yarn run edgio:build && yarn run edgio:production

Deploy ต้องการ Edgio Account Sign Up Free Deploy Edgio รัน Command Root Folder:

yarn run edgio:deploy

Deployment Done! แค่นี้แหละ

Conclusion - สรุป

Project ให้ Experience ค่า Implement Granular Rate Limiting, CRUD Data Operations ใน Serverless, Use GitHub Issues as CMS, Decide Better Ship MVP ด้วย Service ที่ Scale ตามความต้องการ คือ Upstash