अपस्टैश रेडिस और वर्कफ्लो का उपयोग करके स्केलेबल न्यूज़लेटर ऐप बनाएं
इस ब्लॉग में, हम एक न्यूज़लेटर ऐप बनाएंगे जहां यूजर्स सब्सक्राइब कर सकेंगे और चुन सकेंगे कि वे कितनी बार अपने न्यूज़लेटर प्राप्त करना चाहते हैं। हम अपस्टैश रेडिस का उपयोग सब्सक्रिप्शन डेटा स्टोर करने के लिए और अपस्टैश वर्कफ्लो का उपयोग डेटा स्टोर करने, वेलकम ईमेल भेजने, और यूजर की प्राथमिकताओं के आधार पर न्यूज़लेटर शेड्यूल करने के कार्यों को प्रबंधित करने के लिए करेंगे।
प्रेरणा
सबसे पहले, सर्वरलेस एनवायरनमेंट्स महान हैं! वे अत्यधिक स्केलेबल हैं और बजट पर आसान हैं। हालांकि, वे कुछ सीमाओं के साथ आते हैं, जैसे एक्जीक्यूशन टाइम लिमिट्स। यह विशेष रूप से समस्याग्रस्त हो सकता है जब आपको लंबे समय तक चलने वाले कार्यों को चलाने की आवश्यकता होती है।
यहीं अपस्टैश वर्कफ्लो काम आता है। अपस्टैश वर्कफ्लो के साथ, आप लगातार वर्कफ्लोज़ बना सकते हैं जो जब तक चाहें तब तक चल सकते हैं। इसलिए, आपको सर्वरलेस फंक्शन टाइमआउट के बारे में चिंता करने की ज़रूरत नहीं है।
यहां उन विशेषताओं की सूची है जो आपको अपस्टैश वर्कफ्लो का उपयोग करने पर मिलती हैं:
अब सर्वरलेस फंक्शन टाइमआउट नहीं: आपके वर्कफ्लोज़ जब तक चाहें तब तक चल सकते हैं।
ऑटोमैटिक रिकवरी: अगर कुछ गलत होता है और वर्कफ्लो बीच में विफल होता है, तो यह स्वचालित रूप से रिकवर हो जाता है।
ऑटोमैटिक रीट्राई: अगर वर्कफ्लो में कोई चरण विफल होता है, तो यह स्वचालित रूप से रीट्राई किया जाएगा।
रियल-टाइम मॉनिटरिंग: आप अपस्टैश कंसोल से अपने वर्कफ्लोज़ को रियल-टाइम में मॉनिटर कर सकते हैं।
पूर्वापेक्षाएँ
Next.js एप्लिकेशन की बुनियादी समझ।
रेडिस और QStash टोकन के लिए अपस्टैश अकाउंट।
डिप्लॉयमेंट के लिए Vercel अकाउंट।
स्थानीय डेवलपमेंट के लिए ngrok (अनुशंसित)।
प्रोजेक्ट सेटअप
आइए create-next-app का उपयोग करके एक नया Next.js प्रोजेक्ट बूटस्ट्रैप करके शुरू करें:
npx create-next-app@latest --typescript newsletter-app
cd newsletter-app
अब, अपस्टैश QStash और रेडिस सर्विसेज के साथ इंटरैक्ट करने के लिए आवश्यक निर्भरताएं जोड़ें:
npm install @upstash/qstash @upstash/redis
डायरेक्टरी स्ट्रक्चर
कोड में गोता लगाने से पहले, आइए एक त्वरित नज़र डालें कि हम अपने प्रोजेक्ट को कैसे व्यवस्थित करेंगे:
src/app/: यहीं हमारे मुख्य एप्लिकेशन कंपोनेंट्स और पेज रहेंगे।
src/app/api/: हम यहां अपने API रूट्स रखेंगे - सब्सक्राइब करने, अनसब्सक्राइब करने, और वर्कफ्लोज़ हैंडल करने के लिए।
src/components/: इस फ़ोल्डर में हमारे सब्सक्रिप्शन और अनसब्सक्रिप्शन फॉर्म कंपोनेंट्स होंगे।
src/lib/: रेडिस और ईमेल भेजने के लिए यूटिलिटी फंक्शन्स यहां जाएंगे।
src/types/: हम यहां अपने TypeScript टाइप डेफिनिशन रखेंगे।
एनवायरनमेंट वेरिएबल्स
हमें अपने प्रोजेक्ट के रूट पर एक .env फ़ाइल बनाने और निम्नलिखित जोड़ने की आवश्यकता है:
QSTASH_TOKEN: हमारा अपस्टैश QStash टोकन अपस्टैश कंसोल से एक्सेस किया गया।
UPSTASH_REDIS_REST_URL और UPSTASH_REDIS_REST_TOKEN: हमारे अपस्टैश रेडिस क्रेडेंशियल्स अपस्टैश कंसोल से एक्सेस किए गए।
EMAIL_SERVICE_URL: हमारे ईमेल भेजने वाले API का एंडपॉइंट।
NEXT_PUBLIC_BASE_URL: हमारे डिप्लॉयड एप्लिकेशन का बेस URL (जैसे, https://your-app.vercel.app)।
हम अपने .env फ़ाइल में UPSTASH_WORKFLOW_URL वेरिएबल भी सेट कर सकते हैं स्थानीय डेवलपमेंट के लिए हमारे ngrok URL के साथ। ngrok के साथ स्थानीय रूप से वर्कफ्लोज़ विकसित करने के बारे में अधिक जानने के लिए, अपस्टैश डॉक्यूमेंटेशन देखें।
UPSTASH_WORKFLOW_URL एनवायरनमेंट वेरिएबल केवल स्थानीय डेवलपमेंट के लिए आवश्यक है। प्रोडक्शन में, baseUrl पैरामीटर स्वचालित रूप से सेट हो जाता है और इसे छोड़ा जा सकता है।
प्रोजेक्ट इम्प्लीमेंटेशन
सब्सक्रिप्शन फॉर्म कंपोनेंट
SubscriptionForm कंपोनेंट यूजर्स को अपना ईमेल दर्ज करने और चुनने की अनुमति देता है कि वे कितनी बार न्यूज़लेटर प्राप्त करना चाहते हैं। जब फॉर्म सबमिट किया जाता है, तो हम फॉर्म डेटा के साथ /api/subscribe पर POST अनुरोध भेजते हैं।
UnsubscribeForm कंपोनेंट यूजर्स को न्यूज़लेटर से अनसब्सक्राइब करने के लिए अपना ईमेल दर्ज करने की अनुमति देता है। जब फॉर्म सबमिट किया जाता है, तो हम ईमेल डेटा के साथ /api/unsubscribe पर POST अनुरोध भेजते हैं। यह ईमेल फ़ील्ड को प्री-फिल भी करता है यदि यूजर ने ईमेल में अनसब्सक्राइब लिंक पर क्लिक किया हो।
हम यूजर सब्सक्रिप्शन डेटा स्टोर करने के लिए अपस्टैश रेडिस का उपयोग करेंगे।
अपस्टैश रेडिस का उपयोग करने के लिए, हमें पहले अपस्टैश कंसोल पर एक रेडिस डेटाबेस सेट अप करना होगा और अपना REST URL और टोकन प्राप्त करना होगा। इस बारे में अधिक जानकारी के लिए, आप अपस्टैश डॉक्यूमेंटेशन देख सकते हैं।
redis.ts में हमारा रेडिस क्लाइंट और रेडिस के साथ इंटरैक्ट करने के लिए हेल्पर फंक्शन्स होंगे:
src/lib/redis.ts
import { Redis } from "@upstash/redis";
export const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
export async function getUserFrequency(email: string): Promise {
const data = await redis.get(`user:${email}`);
console.log("User data:", data);
if (!data) return null;
const parsed = JSON.parse(JSON.stringify(data));
return parsed.frequency;
}
export async function removeUser(email: string): Promise {
await redis.del(`user:${email}`);
}
export async function checkSubscription(email: string): Promise {
return (await getUserFrequency(email)) !== null;
}
ईमेल भेजने वाला फंक्शन
ईमेल भेजने के लिए, हम अपने स्वयं के ईमेल API का उपयोग करेंगे जिसे हमने QStash Python SDK के साथ ईमेल शेड्यूलर बनाने के बारे में पिछले ब्लॉग पोस्ट में विकसित किया था।
src/lib/email.ts
export async function sendEmail(message: string, email: string) {
console.log(`Sending email to ${email}`);
const url = process.env.EMAIL_SERVICE_URL;
const payload = {
to_email: email,
subject: "Upstash Newsletter",
content: message,
};
if (!url) {
console.error("EMAIL_SERVICE_URL is not defined.");
return;
}
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
console.error("Failed to send email:", await response.text());
}
}
टाइप डेफिनिशन्स
हमें सब्सक्रिप्शन डेटा के लिए एक टाइप डेफिनिशन की भी आवश्यकता है:
हम एक API रूट बनाएंगे जो सब्सक्रिप्शन अनुरोधों को संभालता है। जब कोई यूजर सब्सक्रिप्शन फॉर्म सबमिट करता है, तो यह एंडपॉइंट जांचेगा कि यूजर पहले से सब्सक्राइब्ड है या नहीं और यूजर की चुनी हुई फ्रीक्वेंसी के आधार पर ईमेल भेजने को संभालने के लिए एक वर्कफ्लो एनक्यू करेगा।
src/app/api/subscribe/route.ts
import { NextRequest, NextResponse } from "next/server";
import { checkSubscription } from "@/lib/redis";
export const POST = async (request: NextRequest) => {
try {
const { email, frequency: freq, customFrequency } = await request.json();
console.log("Email:", email);
console.log("Frequency:", freq);
console.log("Custom Frequency:", customFrequency);
if (!email || !freq) {
console.error("Email and frequency are required.");
return NextResponse.json(
{ error: "Email and frequency are required." },
{ status: 400 }
);
}
let frequency = freq;
if (frequency === "custom") {
if (!customFrequency) {
console.error("Custom frequency days are required.");
return NextResponse.json(
{ error: "Custom frequency days are required." },
{ status: 400 }
);
}
frequency = customFrequency;
}
if (frequency === "daily") {
frequency = "1";
} else if (frequency === "weekly") {
frequency = "7";
} else if (frequency === "monthly") {
frequency = "30";
}
const frequencyNumber = Number(frequency);
if (isNaN(frequencyNumber) || frequencyNumber <= 0) {
console.error("Invalid frequency value.");
return NextResponse.json(
{ error: "Invalid frequency value." },
{ status: 400 }
);
}
const exists = await checkSubscription(email);
if (exists) {
console.error("Email is already subscribed.");
return NextResponse.json(
{ error: "Email is already subscribed." },
{ status: 400 }
);
}
console.log("Subscription successful!");
console.log("Enqueue the workflow");
// वर्कफ्लो एनक्यू करें
await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/workflow`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.QSTASH_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
frequency: frequencyNumber,
}),
})
.then((response) => {
if (!response.ok) {
console.error("Failed to enqueue workflow:", response.statusText);
return NextResponse.json(
{ error: "Failed to enqueue workflow." },
{ status: 500 }
);
} else {
console.log("Workflow enqueued successfully");
}
})
.catch((error) => {
console.error("Error enqueuing workflow:", error);
return NextResponse.json(
{ error: "Error enqueuing workflow." },
{ status: 500 }
);
});
return NextResponse.json({ message: "Subscription successful!" });
} catch (error) {
console.error("Error occurred:", error);
return NextResponse.json(
{ error: "An error occurred during subscription." },
{ status: 500 }
);
}
};
अनसब्सक्राइब API रूट
चूंकि हमारे पास एक सब्सक्रिप्शन रूट है, हमें एक अनसब्सक्राइब रूट की भी आवश्यकता है। जब अनुरोध किया जाता है, तो हम जांचेंगे कि यूजर सब्सक्राइब्ड है या नहीं और रेडिस से उनका डेटा हटा देंगे। हम एक कन्फर्मेशन ईमेल भी भेजेंगे।
src/app/api/unsubscribe/route.ts
import { NextRequest, NextResponse } from "next/server";
import { redis } from "@/lib/redis";
import { sendEmail } from "@/lib/email";
export const POST = async (request: NextRequest) => {
try {
const { email } = await request.json();
if (!email) {
return NextResponse.json(
{ error: "Email is required." },
{ status: 400 }
);
}
const userExists = await redis.exists(`user:${email}`);
if (!userExists) {
return NextResponse.json(
{ error: "Email is not subscribed." },
{ status: 400 }
);
}
// रेडिस से यूजर हटाएं
await redis.del(`user:${email}`);
// अनसब्सक्रिप्शन कन्फर्म करने के लिए ईमेल भेजें
await sendEmail(
"You have been unsubscribed from Upstash Newsletter.",
email
);
return NextResponse.json({ message: "You have been unsubscribed." });
} catch (error) {
console.error("Unsubscribe error:", error);
return NextResponse.json(
{ error: "An error occurred. Please try again." },
{ status: 500 }
);
}
};
वर्कफ्लो API रूट
अब, यहां मजेदार हिस्सा आता है! हम एक API रूट बनाएंगे जो निर्दिष्ट फ्रीक्वेंसी अंतराल पर न्यूज़लेटर भेजने के लिए वर्कफ्लो को संभालता है।
हमारा वर्कफ्लो निम्नलिखित करेगा:
यूजर के सब्सक्रिप्शन डेटा को रेडिस में स्टोर करें।
वेलकम ईमेल भेजें।
एक लूप में प्रवेश करें:
निर्दिष्ट फ्रीक्वेंसी अवधि के लिए प्रतीक्षा करें।
जांचें कि यूजर अभी भी सब्सक्राइब्ड है या नहीं।
न्यूज़लेटर ईमेल भेजें।
तब तक दोहराएं जब तक एक निर्धारित संख्या में न्यूज़लेटर भेजे नहीं गए क्योंकि हम अनंत लूप नहीं चाहते।
src/app/api/workflow/route.ts
यहां एक पूर्ण वर्कफ्लो का उदाहरण है एक यूजर के लिए जिसने सब्सक्राइब किया, एक न्यूज़लेटर प्राप्त किया, और अनसब्सक्राइब किया:
आप अपस्टैश कंसोल से अपने वर्कफ्लोज़ को एक्सेस और मॉनिटर कर सकते हैं।
मेन पेज कंपोनेंट
आइए अपने एप्लिकेशन के मेन पेज को सेट अप करें। इस पेज में सब्सक्रिप्शन फॉर्म और अनसब्सक्राइब पेज का लिंक शामिल होगा।
src/app/page.tsx
import SubscriptionForm from "@/components/SubscriptionForm";
import Link from "next/link";
export default function Home() {
return (
Subscribe to Upstash Newsletter
{/* Subscription Form */}
{/* Unsubscribe Link */}
Already subscribed and want to unsubscribe?
Click here to unsubscribe
);
}
अनसब्सक्राइब पेज कंपोनेंट
अंत में, आइए अनसब्सक्रिप्शन पेज बनाएं।
src/app/unsubscribe/page.tsx
import UnsubscribePage from "@/components/UnsubscribeForm";
export default function UnsubscribeHome() {
return (
Unsubscribe from Upstash Newsletter
{/* Unsubscribe Form */}
);
}
निष्कर्ष
और बस! हमने सर्वरलेस फंक्शन टाइमआउट की चिंता किए बिना एक सरल न्यूज़लेटर ऐप बनाया है।
आप इस प्रोजेक्ट का पूरा सोर्स कोड GitHub पर पा सकते हैं और आप यहां लाइव डेमो देख सकते हैं।
अपस्टैश वर्कफ्लो के बारे में अधिक जानकारी के लिए, आप अपस्टैश डॉक्यूमेंटेशन देख सकते हैं।
यदि आपके कोई प्रश्न हैं, तो बेझिझक हमसे Discord पर संपर्क करें। साथ ही, अधिक ट्यूटोरियल और उपयोग के मामलों के लिए अपस्टैश ब्लॉग देखना न भूलें।
AppSignal का उपयोग करने वाले ऐप को चलाने वाला हर सर्वर हर 30 सेकंड में सैंपल्स और मेट्रिक्स का एक संग्रह हमारे पुश API को भेजता है। प्रत्येक रिक्वेस्ट में एक कुंजी होती है जिसका उपयोग हम यह निर्धारित करने के लिए करते हैं कि डेटा किस ऐप से आया है। ऐसा करने के लिए, हमें प्रत्येक इनकमिंग रिक्वेस्ट के ल
मान लीजिए हमारे पास एक संख्या n है, हमें पास्कल के त्रिभुज की nवीं (0-इंडेक्स्ड) पंक्ति ढूंढनी है। जैसा कि हम जानते हैं पास्कल का त्रिभुज निम्नानुसार बनाया जा सकता है:
सबसे ऊपर की पंक्ति में 1 की सरणी होती है।
बाद की पंक्ति ऊपर-बाएं और ऊपर-दाएं की संख्याओं को जोड़कर बनाई जाती है।
कुछ पंक्तियां इस
HTML DOM Video play() मेथड
HTML DOM Video play() का उपयोग वर्तमान मीडिया कंटेंट को चलाने के लिए किया जाता है और मीडिया को रोकने के लिए pause() का उपयोग करें।
सिंटैक्स
videoObject.play()
उदाहरण
आइए Video play() प्रॉपर्टी का एक उदाहरण देखें:
<!DOCTYPE html> <html> <head> <titl