Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Redis

Xây dựng kho lưu trữ phiên an toàn, có thể mở rộng với Clerk &Upstash Redis

Một trong những trường hợp sử dụng chính của Redis là lưu trữ và quản lý phiên người dùng để duy trì trạng thái theo yêu cầu trong ứng dụng web. Việc này có thể được thực hiện theo nhiều cách và một số công cụ serverless mới hơn cung cấp các tùy chọn dễ triển khai.

Quản lý dữ liệu phiên người dùng rất quan trọng đối với các ứng dụng kinh doanh khác nhau. Ví dụ:các nền tảng cá nhân hóa sử dụng Redis để lưu trữ các tương tác và sở thích của người dùng, cho phép họ cung cấp nội dung hoặc đề xuất sản phẩm tùy chỉnh. Trong thế giới trò chơi, Redis giúp quản lý dữ liệu người dùng để mang lại trải nghiệm nhiều người chơi mượt mà bằng cách theo dõi tương tác của người chơi trong thời gian thực. Các nền tảng quảng cáo cũng dựa vào Redis để lưu trữ dữ liệu phiên, giúp tối ưu hóa việc phân phối quảng cáo và cá nhân hóa các chiến dịch trong tương lai. Trong ví dụ sau, chúng ta sẽ đặc biệt tập trung vào một ứng dụng thương mại điện tử và khám phá cách sử dụng Redis để quản lý giỏ hàng một cách hiệu quả.

Mô tả dự án

Trong bài đăng trên blog này, chúng tôi sẽ sử dụng Clerk, Next.js và Upstash Redis để xây dựng cửa hàng phiên cho ứng dụng mua sắm. Đây là danh sách các tính năng chúng tôi sẽ triển khai trong dự án này:

  • Người dùng sẽ có thể thực hiện các hành động đăng ký, đăng nhập và đăng xuất.
  • Mỗi người dùng có thể thêm, xóa các mặt hàng vào giỏ hàng riêng của mình.
  • Người dùng sẽ có thể cập nhật số lượng mặt hàng trong giỏ hàng.

Dự án này có một số tính năng khác liên quan đến QStash và Upstash Ratelimit:

  • Một số hành động nhất định trong ứng dụng sẽ bắt đầu các sự kiện, dẫn đến việc lên lịch gửi email thông qua QStash. Những email này sau đó sẽ được gửi đi bằng cách Gửi lại. Ví dụ:khi thanh toán, email xác nhận giao hàng sẽ được lên lịch trong 24 giờ sau. Tương tự, sau khi mua một mặt hàng, người dùng sẽ nhận được lời nhắc sau một khoảng thời gian trễ nhất định, khuyến khích họ đánh giá giao dịch mua hàng của mình.
  • Người dùng cũng có tùy chọn để xếp hạng các mục. Tất cả dữ liệu xếp hạng được lưu trữ tỉ mỉ trong cấu trúc dữ liệu phù hợp trên Upstash Redis. Để đảm bảo luồng tương tác cân bằng của người dùng và ngăn chặn mọi hành vi sử dụng sai mục đích có thể xảy ra, sự kiện xếp hạng được điều chỉnh bởi khả năng giới hạn tỷ lệ của Upstash Ratelimit.

Bản trình diễn

Bạn có thể xem bản demo đã triển khai của dự án tại đây.

Bạn cũng có thể truy cập kho lưu trữ Github của dự án này tại đây.

Tạo ứng dụng Next.js

Mở một cửa sổ terminal mới và tạo ứng dụng với lời nhắc bên dưới:

npx create-next-app@latest

Thao tác này sẽ hỏi bạn các tùy chọn dự án và bạn sẽ có sẵn mẫu dự án Next.js.

npx create-next-app@latest
Need to install the following packages:
 create-next-app@13.4.18
Ok to proceed? (y) y
✔ What is your project named? shopstash
✔ Would you like to use TypeScript? No / -> Yes
✔ Would you like to use ESLint? No / -> Yes
✔ Would you like to use Tailwind CSS? No / -> Yes
✔ Would you like to use `src/` directory? -> No / Yes
✔ Would you like to use App Router? (recommended) No / -> Yes
✔ Would you like to customize the default import alias? -> No / Yes
Creating a new Next.js app in /Users/***/shopstash.

Thư ký tích hợp

Thêm Thư ký vào một dự án khá đơn giản. Chúng tôi sẽ sử dụng SDK Next.js của Clerk cho các thành phần và hook dựng sẵn. Trước tiên hãy cài đặt nó:

npm install clerk@nextjs

Sau đó, chúng ta sẽ tạo ứng dụng của mình trên bảng điều khiển Clerk. Bạn có thể thực hiện cấu hình cho thông tin đăng ký bắt buộc, dựa trên lựa chọn của bạn. Khi bạn tạo ứng dụng, thông tin xác thực cần thiết sẽ được nhắc. Chúng tôi sẽ sao chép chúng vào .env.local tập tin. Đây là một ví dụ:

.env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_********
CLERK_SECRET_KEY=sk_test_********

Chúng tôi cũng sẽ định cấu hình đường dẫn cho Thư ký trong tệp '.env.local'.

.env.local
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/

Bây giờ, để sử dụng phiên hoạt động và bối cảnh người dùng, chúng ta sẽ bao bọc bố cục gốc bằng <ClerkProvider> . Đi sâu hơn vào hướng dẫn này, chúng tôi cũng sẽ triển khai Header thành phần.

bố cục.tsx
import { ClerkProvider } from "@clerk/nextjs";
 
import Header from "./components/Header";
 
export default function RootLayout({
 children,
}: {
 children: React.ReactNode;
}) {
 return (
 <ClerkProvider>
 <html lang="en">
 <body className="bg-white">
 <Header />
 <main className="container bg-white">
 <div className="flex min-h-screen items-start justify-center ">
 <div className="mt-5">{children}</div>
 </div>
 </main>
 </body>
 </html>
 </ClerkProvider>
 );
}

Bây giờ, Clerk đã được cài đặt trong dự án của chúng ta. Bước tiếp theo là quyết định trang nào sẽ ẩn sau xác thực. Chúng tôi sẽ thực hiện thao tác này trong middleware.tsx tập tin được đặt trong thư mục gốc.

phần mềm trung gian.tsx
import { authMiddleware } from "@clerk/nextjs";
 
export default authMiddleware({});
 
export const config = {
 matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};

Với điều này, toàn bộ ứng dụng được bảo vệ. Nếu bạn cố truy cập bất kỳ trang nào mà không đăng nhập, bạn sẽ được chuyển hướng đến trang chỉ mục để xác thực.

Tại thời điểm này, ứng dụng của chúng tôi cần các trang đăng ký và đăng nhập. Chúng tôi sẽ cung cấp điều hướng đến các tệp này từ Tiêu đề và thành phần này sẽ được hiển thị dựa trên người dùng đang hoạt động. Nếu có người dùng đang hoạt động thì người dùng sẽ có thể đăng xuất và xem hồ sơ của họ. Ngược lại, lộ trình đăng nhập và đăng ký sẽ được hiển thị.

Đây là trạng thái của tiêu đề khi không có người dùng hoạt động:

Xây dựng kho lưu trữ phiên an toàn, có thể mở rộng với Clerk &Upstash Redis

Bước cuối cùng là xây dựng các lộ trình cần thiết cho hành động của người dùng. Đối với dự án này, chúng tôi sẽ sử dụng các thành phần đăng ký/đăng nhập Thư ký tích hợp, nhưng bạn cũng có thể tạo thiết kế trang tùy chỉnh cho các luồng ký tên người dùng duy nhất. Để đăng ký, chúng ta sẽ tạo app/sign-in/[[...sign-up]]/page.tsx lộ trình.

app/đăng nhập/[[...đăng ký]]/page.tsx
import { SignUp } from "@clerk/nextjs";
 
const SignUpPage = () => {
 return (
 <>
 <SignUp />
 </>
 );
};
export default SignUpPage;

Trang đăng ký gần giống với trang đăng nhập và chúng tôi sẽ triển khai nó theo đường dẫn tương tự.

app/đăng nhập/[[...đăng ký]]/page.tsx
import { SignIn } from "@clerk/nextjs";
 
const SignInPage = () => {
 return (
 <>
 <SignIn />
 </>
 );
};
export default SignInPage;

Với việc tích hợp thành công Clerk vào dự án của chúng tôi, giờ đây chúng tôi đã sẵn sàng để tiến xa hơn nữa. Tính năng người dùng đang hoạt động đã sẵn sàng, tạo tiền đề cho chúng tôi thiết lập một cửa hàng phiên duy nhất trên Redis cho mỗi người dùng riêng biệt. Chiến lược của chúng tôi liên quan đến việc truy xuất ID người dùng từ Thư ký và sau đó lưu trữ dữ liệu phiên cho người dùng nói trên trên Upstash Redis. Để minh họa quá trình này, hãy xem xét việc xây dựng một giỏ hàng. Tại đây, mỗi phiên riêng lẻ sẽ giữ lại dữ liệu mục giỏ hàng tương ứng.

Đầu tiên và quan trọng nhất, chúng ta cần khái niệm hóa những gì tạo nên một mặt hàng trong giỏ hàng. Điều này sẽ đóng vai trò là bản thiết kế chi tiết của chúng tôi khi chúng tôi tạo phần còn lại của ứng dụng. Nếu bạn đang tìm cách cung cấp nhiều loại mục cho ứng dụng của mình thì các công cụ như ChatGPT có thể là vô giá. Ngoài ra, một cách tiếp cận trực tiếp hơn sẽ liên quan đến việc tìm nguồn cung ứng chúng từ kho lưu trữ GitHub được liên kết với ví dụ này. Và tất nhiên, để thực sự khiến giao diện người dùng của bạn trở nên sống động, bạn sẽ cần thiết kế hoặc tìm nguồn hình ảnh phù hợp cho từng mục.

công khai/items.tsx
export const items = [
 {
 id: 1,
 title: "Elegant Leather Watch",
 image: "/images/1.png",
 description: "A sophisticated leather watch for all occasions.",
 company: "Timepiece Creations",
 price: 99.99,
 },
];

Triển khai giỏ hàng

Trong một ứng dụng mua sắm thông thường, giỏ hàng của người dùng phải có thể truy cập được từ nhiều phần. Điều này cho phép các thành phần có liên quan hiển thị dựa trên nội dung của giỏ hàng. Ví dụ:cho dù bạn đang ở trang chi tiết của một mặt hàng riêng lẻ hay đang xem danh sách tất cả các mặt hàng, bạn đều có thể biết liệu sản phẩm đã có trong giỏ hàng của mình hay chưa. Dưới đây là một cái nhìn sơ lược về cách ví dụ của chúng tôi sẽ xuất hiện:

Xây dựng kho lưu trữ phiên an toàn, có thể mở rộng với Clerk &Upstash Redis

Để thực hiện được điều này, chúng tôi sẽ sử dụng React Context API, cung cấp quyền truy cập vào các hoạt động cần thiết trong giỏ hàng (như thêm mặt hàng, xóa chúng hoặc đặt lại giỏ hàng) trên phạm vi bán toàn cầu.

Để thiết lập kết nối với Upstash Redis, chúng tôi sẽ sao chép UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN các giá trị từ Bảng điều khiển và dán chúng vào .env của bạn tập tin.

.env
UPSTASH_REDIS_REST_URL=<YOUR_URL>
UPSTASH_REDIS_REST_TOKEN=<YOUR_TOKEN>

Ngữ cảnh giỏ hàng của chúng tôi sẽ được đặt trong app/context/CartContext.tsx tập tin. Chúng ta sẽ bao bọc bối cảnh này xung quanh ứng dụng chính, cho phép chúng ta sử dụng các phương thức mà nó cung cấp. Dưới đây là tóm tắt nhanh về các khả năng:

  • Người dùng có thể thêm mặt hàng vào giỏ hàng và điều chỉnh số lượng.
  • Có thể xóa các mặt hàng khỏi giỏ hàng.
  • Có thể đặt lại toàn bộ giỏ hàng.
  • Ngoài ra còn có tính năng thanh toán. Đây là cái nhìn tổng thể về API ngữ cảnh. Chúng tôi sẽ chia nhỏ và triển khai từng phương pháp từng bước.

Đây là cách thuật toán hoạt động ở mức tổng quan cao hơn.

  • Giỏ hàng, được coi như một cửa hàng phiên, sẽ được lưu giữ dưới dạng hàm băm trên Upstash Redis. Mã định danh duy nhất cho hàm băm này sẽ dựa trên ID người dùng, vì vậy mỗi người dùng sẽ có một giỏ hàng có tên theo định dạng cart:<USER_ID> .
  • Khi lưu dữ liệu giỏ hàng vào hàm băm Redis, chúng tôi sẽ sử dụng ID mặt hàng làm khóa và số lượng của từng mặt hàng làm giá trị. Nhờ các lệnh tích hợp của Redis, việc sửa đổi giỏ hàng trở nên dễ dàng.
  • Về phía khách hàng, giỏ hàng sẽ được quản lý ở trạng thái bao gồm một mảng các đối tượng Item. Khi một trang được tải, useEffect hook lấy dữ liệu giỏ hàng từ Upstash Redis. Nếu có bất kỳ thay đổi nào đối với giỏ hàng, tất cả các thành phần có liên quan sẽ được hiển thị lại.
  • Cấu trúc dữ liệu đơn giản của Redis đơn giản hóa việc triển khai addItemremoveItem chức năng. Bằng cách triển khai redis.hincrby() lệnh, chúng ta có thể xử lý các tác vụ từ thêm hoặc xóa mặt hàng đến điều chỉnh số lượng mặt hàng trong giỏ hàng. Tiện ích này nhấn mạnh sức mạnh của Redis.
  • Đối với resetCart hàm, chúng ta sẽ xóa khóa băm khỏi cơ sở dữ liệu Upstash bằng cách sử dụng redis.del() .

Bây giờ bạn đã nắm được phác thảo khái niệm của xe đẩy, đã đến lúc xắn tay áo lên và bắt tay vào các phương pháp cốt lõi.

Thêm mặt hàng vào giỏ hàng

Theo quan điểm của hàm băm Redis, việc thêm một mục hoặc thay đổi số lượng của nó yêu cầu cùng một lệnh. hincrby lệnh tạo khóa và đặt giá trị của nó thành 1 hoặc tăng giá trị liên quan dựa trên increment tham số của lệnh.

Về phía khách hàng, chúng tôi sẽ phản ánh những hành động này bằng cách giới thiệu một mặt hàng mới hoặc điều chỉnh số lượng trong trạng thái của giỏ hàng.

bối cảnh/CartContext.tsx
const addItem = async (id: number) => {
 const item = items.find((i) => i.id === id);
 if (!item) return;
 
 const doesItemExist = cart.some((i) => {
 return id === i.id;
 });
 
 let newCart: Item[];
 
 if (!doesItemExist) {
 newCart = [...(cart || []), item];
 redis.hincrby(`user:${userId}`, id.toString(), 1);
 
 //We create an item in the state object with the given id, and set the quantity to 1.
 const newCartItemIDs = { ...cartItems, [id]: 1 };
 
 setCartItemIDs(newCartItemIDs);
 setCart(newCart);
 } else {
 const item = items.find((i) => i.id === id);
 
 //This item currently exists in the state object as key, so we increase the value by 1.
 const updatedItemQuantities = {
 ...cartItems,
 [id]: cartItems[id] + 1,
 };
 
 setCartItems(updatedItemQuantities);
 redis.hincrby(`user:${userId}`, id.toString(), 1);
 }
};

Xóa mặt hàng khỏi giỏ hàng

Thao tác xóa cũng tương tự như thao tác thêm. Bạn có thể giảm giá trị băm bằng cách sử dụng hincrby , bằng cách đưa ra increment tham số là -1 .

bối cảnh/CartContext.tsx
const removeItem = async (id: number, force: boolean = false) => {
 const doesItemExist = cart.some((i) => {
 return id === i.id;
 });
 
 if (!doesItemExist) return;
 
 if (cartItems[id] === 1 || force) {
 const newCart: Item[] = cart.filter((item: { id: number }) => {
 return item.id !== id;
 });
 
 // Creating the new state object for cart
 const newCartItems = { ...cartItems };
 delete newCartItems[id];
 
 //Removing the item from Upstash Redis hashset.
 redis.hdel(`user:${userId}`, id.toString());
 
 setCart(newCart);
 setCartItems(newCartItems);
 } else if (cartItems[id] > 1) {
 const updatedItemQuantities = {
 ...cartItems,
 [id]: cartItems[id] - 1,
 };
 
 setCartItems(updatedItemQuantities);
 redis.hincrby(`user:${userId}`, id.toString(), -1);
 }
};

Chức năng giỏ hàng hiện đã có sẵn, hoàn chỉnh với tất cả các phương pháp cần thiết. Nó được tích hợp liền mạch trong toàn bộ dự án với khả năng truy cập toàn cầu. Dưới đây là hai trường hợp sử dụng minh họa cho tính năng này:

  • Trang chỉ mục :Tại đây, tất cả các mục được hiển thị. Các nút độc đáo đi kèm với từng mặt hàng, cho phép bạn thêm chúng vào giỏ hàng của mình. Nếu một sản phẩm đã có trong giỏ hàng của bạn thì bạn sẽ có tùy chọn xóa sản phẩm đó.

  • Chúng tôi sẽ sử dụng shadcnui Thư viện giao diện người dùng phản ứng và xây dựng một phương thức/trang tính hợp nhất tất cả các mục trong giỏ hàng trên một trang. Không gian này không chỉ để duyệt; bạn có thể sửa đổi số lượng mặt hàng nếu cần. Và nếu bạn muốn thay đổi, các tùy chọn để đặt lại giỏ hàng hoặc tiến hành thanh toán đều có ngay tại đó.

Trang chỉ mục

Điểm lưu ý:các mục trên trang chỉ mục được hiển thị dành riêng cho người dùng đang hoạt động và đã đăng nhập. Đầu tiên, chúng tôi tìm nạp dữ liệu người dùng từ Clerk và hiển thị các thành phần dựa trên phản hồi từ Clerk.

Trong thành phần thẻ, chúng ta sẽ chỉ truy xuất các hàm và đối tượng cần thiết CartContext.

thành phần/CardComponent.tsx
export default function CardComponent(props: { item: cardProps }) {
 const { item } = props;
 const { id, title, image, company } = item;
 const { addItem, removeItem, cartItems } = useContext(CartContext);
 
 return (
 <>
 <Card className="transition duration-200 hover:shadow-lg">
 <Link href={`/products/${id}`}>
 <CardHeader>
 <CardTitle>{title}</CardTitle>
 </CardHeader>
 <CardContent>
 <Image src={image} alt={title} width={300} height={300}></Image>
 <CardDescription>{company}</CardDescription>
 </CardContent>
 </Link>
 <CardFooter>
 <div className="grid grid-rows-2">
 <CartButton
 id={id}
 cartItems={cartItems}
 addItem={addItem}
 removeItem={removeItem}
 />
 </div>
 </CardFooter>
 </Card>
 </>
 );
}

Thành phần quan trọng ở đây là nút giỏ hàng, nơi bạn có thể thêm hoặc xóa mặt hàng khỏi giỏ hàng. Nút này sẽ được hiển thị dựa trên trạng thái hiện tại của giỏ hàng.

thành phần/CartButton.tsx
const CartButton = ({
 id,
 cartItems,
 addItem,
 removeItem,
}: {
 id: number;
 cartItems: cartContent;
 addItem: (id: number) => Promise<void>;
 removeItem: (id: number, force: boolean) => Promise<void>;
}) => {
 const itemExists: boolean = cartItems?.hasOwnProperty(id);
 const { triggerEvent } = useContext(UserStateContext);
 return (
 <button
 className={`${
 itemExists ? "bg-red-400 text-black" : "bg-cyan-500 text-black"
 } flex items-center justify-center gap-3 rounded-full px-4 py-2 transition-all duration-300`}
 onClick={() => {
 if (itemExists) {
 removeItem(id, true);
 } else {
 addItem(id);
 }
 }}
 >
 <p className="text-sm font-bold">
 {itemExists ? "Remove from Cart" : "Add to Cart"}
 </p>
 <FaCartShopping size="25" />
 </button>
 );
};

Giờ đây, trạng thái giỏ hàng của chúng tôi là động, nó có thể nhắc kết xuất thành phần cụ thể dựa trên trạng thái hiện tại của nó. Tiếp theo, chúng tôi sẽ giới thiệu thành phần giỏ hàng chuyên dụng. Không gian này sẽ đóng vai trò là trung tâm để xem toàn bộ nội dung trong giỏ hàng, thực hiện điều chỉnh số lượng hoặc nhấn nút đặt lại.

Chúng tôi đang chuyển sang thành phần bảng shadcn/ui làm cơ sở và có kế hoạch cá nhân hóa nội thất của nó.

Trong trường hợp giỏ hàng bị bỏ trống, thành phần này sẽ vẽ ra một bức tranh rõ ràng về tình huống:

Xây dựng kho lưu trữ phiên an toàn, có thể mở rộng với Clerk &Upstash Redis

Tuy nhiên, khi các mặt hàng được đưa vào giỏ hàng, chúng sẽ hiển thị dễ dàng trong thành phần này. Bạn không chỉ có thể xem các lựa chọn của mình mà còn có quyền tự do điều chỉnh số lượng hoặc đánh giá giá trị chung của các mặt hàng của mình.

Xây dựng kho lưu trữ phiên an toàn, có thể mở rộng với Clerk &Upstash Redis

Để biết hướng dẫn đầy đủ về quá trình tạo thành phần này, hãy xem bên dưới. Nếu bạn đang tìm kiếm những chi tiết vụn vặt, chẳng hạn như cấu hình nút, tôi khuyên bạn nên truy cập kho lưu trữ GitHub của chúng tôi, nơi bạn có thể sử dụng toàn bộ cơ sở mã.

Với đoạn mã cuối cùng đó, dự án của chúng tôi đã đi đến kết luận. Chúng tôi đã thực hiện thành công từ ý tưởng đến triển khai, đưa tính năng giỏ hàng của mình vào cuộc sống bằng sức mạnh của Upstash Redis và tính linh hoạt của thư viện shadcn/ui.

Kết luận

Sự kết hợp giữa Clerk để quản lý người dùng và Upstash Redis để lưu trữ dữ liệu hiệu quả là công cụ tạo ra hệ thống giỏ hàng động của chúng tôi. Cùng nhau, họ đã tạo thành xương sống cho ứng dụng của chúng tôi, đảm bảo cả tính bảo mật và hiệu suất. Dự án này là một ví dụ tuyệt vời về cách các công cụ mạnh mẽ như Upstash Redis, Clerk được sử dụng cùng nhau để giải quyết các vấn đề rất phức tạp một cách suôn sẻ.

Dưới đây là một số đề xuất để cải thiện hơn nữa dự án này:

  • Trải nghiệm người dùng: Mặc dù chúng tôi đã thiết lập một giỏ hàng mạnh mẽ và đầy đủ chức năng, nhưng việc nghiên cứu sâu hơn các cải tiến về giao diện người dùng—hoạt ảnh, vòng phản hồi hoặc thậm chí là bản xem trước chi tiết sản phẩm—có thể mang lại hành trình người dùng liền mạch hơn nữa.

  • Hiệu suất: Với việc sử dụng Upstash Redis một cách cơ bản, chúng tôi có thể nghiên cứu sâu hơn về các chiến lược bộ nhớ đệm nâng cao, có thể tích hợp các nhân viên dịch vụ để cải thiện thời gian tải và trải nghiệm ngoại tuyến phong phú hơn.

  • Tính năng: Việc mở rộng khả năng của giỏ hàng bằng danh sách mong muốn, đề xuất sản phẩm phù hợp dựa trên nội dung giỏ hàng hiện tại hoặc hệ thống áp dụng mã khuyến mại có thể nâng cao trải nghiệm mua sắm.

  • Tích hợp: Có khả năng tích hợp các cổng thanh toán để có quy trình thanh toán suôn sẻ hoặc thậm chí giao tiếp với kho hàng của bên thứ ba hoặc hệ thống CRM để có giải pháp Thương mại điện tử toàn diện.

Cảm ơn bạn đã theo dõi cuộc phiêu lưu phát triển này. Chúng tôi rất mong nhận được phản hồi của bạn và muốn thấy những đổi mới mà bạn có thể mang lại cho cấu trúc nền tảng này! Nếu bạn có bất kỳ câu hỏi hoặc vấn đề nào về dự án này, vui lòng liên hệ với tôi theo địa chỉ fahreddin@upstash.com.

Bạn có thể tìm thấy Kho lưu trữ Github của dự án tại đây.

Phần chúng tôi lên lịch gửi email bằng QStash và Gửi lại sẽ được đề cập trong một bài đăng khác. Cho đến lúc đó bạn có thể kiểm tra kho ví dụ để xem cách triển khai.