<पी> इस ब्लॉग में, हम एक न्यूज़लेटर ऐप बनाएंगे जहां उपयोगकर्ता सदस्यता ले सकेंगे और चुन सकेंगे कि वे कितनी बार अपने न्यूज़लेटर प्राप्त करना चाहते हैं। हम अपस्टैश रेडिस का उपयोग करेंगे सदस्यता डेटा और अपस्टैश वर्कफ़्लो संग्रहीत करने के लिए उपयोगकर्ता की प्राथमिकताओं के आधार पर डेटा संग्रहीत करने, स्वागत ईमेल भेजने और न्यूज़लेटर शेड्यूल करने की क्रियाओं को प्रबंधित करना। प्रेरणा
<पी> सबसे पहले, सर्वर रहित वातावरण बढ़िया हैं! वे अत्यधिक स्केलेबल और बजट में आसान हैं। हालाँकि, वे कुछ सीमाओं के साथ आते हैं, जैसे निष्पादन समय सीमा। यह विशेष रूप से समस्याग्रस्त हो सकता है जब आपको लंबे समय तक चलने वाले कार्यों को चलाने की आवश्यकता होती है। <पी> यहीं अपस्टैश वर्कफ़्लो है खेल में आता है. अपस्टैश वर्कफ़्लो के साथ, आप लगातार वर्कफ़्लो बना सकते हैं जो आवश्यकतानुसार लंबे समय तक चल सकते हैं। तो, अब आपको सर्वर रहित फ़ंक्शन टाइमआउट के बारे में चिंता करने की ज़रूरत नहीं है। <पी> यहां उन सुविधाओं की सूची दी गई है जो आपको अपस्टैश वर्कफ़्लो का उपयोग करते समय मिलती हैं: - अब सर्वर रहित फ़ंक्शन टाइमआउट नहीं :आपके वर्कफ़्लो तब तक चल सकते हैं जब तक उन्हें आवश्यकता हो।
- स्वचालित पुनर्प्राप्ति :यदि कुछ गलत हो जाता है और वर्कफ़्लो बीच में ही विफल हो जाता है, तो यह स्वचालित रूप से ठीक हो जाता है।
- स्वचालित पुनः प्रयास :यदि वर्कफ़्लो में कोई भी चरण विफल हो जाता है, तो इसे स्वचालित रूप से पुनः प्रयास किया जाएगा।
- वास्तविक समय की निगरानी :आप अपस्टैश कंसोल से वास्तविक समय में अपने वर्कफ़्लो की निगरानी कर सकते हैं।
आवश्यकताएँ
- Next.js अनुप्रयोगों की बुनियादी समझ।
- रेडिस और QStash टोकन के लिए एक अपस्टैश खाता।
- परिनियोजन के लिए वर्सेल खाता।
- स्थानीय विकास के लिए ngrok (अनुशंसित)।
प्रोजेक्ट सेटअप
<पी> आइए create-next-app का उपयोग करके एक नया Next.js प्रोजेक्ट बूटस्ट्रैप करके प्रारंभ करें : npx create-next-app@latest --typescript newsletter-app
cd newsletter-app
<पी> अब, आइए अपस्टैश QStash और Redis सेवाओं के साथ इंटरैक्ट करने के लिए आवश्यक निर्भरताएँ जोड़ें: npm install @upstash/qstash @upstash/redis
निर्देशिका संरचना
<पी> कोड में गोता लगाने से पहले, आइए एक नज़र डालें कि हम अपने प्रोजेक्ट को कैसे व्यवस्थित करेंगे: src/app/ :यह वह जगह है जहां हमारे मुख्य एप्लिकेशन घटक और पेज रहेंगे।
src/app/api/ :हम अपने एपीआई रूट यहां रखेंगे - सदस्यता लेने, सदस्यता समाप्त करने और वर्कफ़्लो को संभालने के लिए।
src/components/ :इस फ़ोल्डर में हमारी सदस्यता और सदस्यता-रहित फ़ॉर्म घटक शामिल होंगे।
src/lib/ :रेडिस और ईमेल भेजने के लिए उपयोगिता कार्य यहां जाएंगे।
src/types/ :हम इस निर्देशिका में अपनी टाइपस्क्रिप्ट प्रकार की परिभाषाएँ रखेंगे।
पर्यावरण चर
<पी> हमें एक .env बनाने की आवश्यकता है हमारे प्रोजेक्ट के मूल में फ़ाइल करें और निम्नलिखित जोड़ें: QSTASH_TOKEN=
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
EMAIL_SERVICE_URL=
NEXT_PUBLIC_BASE_URL=
- QSTASH_TOKEN :हमारे अपस्टैश QStash टोकन को अपस्टैश कंसोल से एक्सेस किया गया।
- UPSTASH_REDIS_REST_URL और UPSTASH_REDIS_REST_TOKEN :हमारे अपस्टैश रेडिस क्रेडेंशियल्स को अपस्टैश कंसोल से एक्सेस किया गया।
- EMAIL_SERVICE_URL :हमारे ईमेल भेजने वाले एपीआई का समापन बिंदु।
- NEXT_PUBLIC_BASE_URL :हमारे तैनात एप्लिकेशन का आधार यूआरएल (उदाहरण के लिए,
https://your-app.vercel.app ).
<पी> हम UPSTASH_WORKFLOW_URL भी सेट कर सकते हैं हमारे .env में परिवर्तनशील हमारे एनग्रोक यूआरएल के साथ स्थानीय विकास के लिए फ़ाइल। एनग्रोक के साथ स्थानीय स्तर पर वर्कफ़्लो कैसे विकसित करें, इसके बारे में अधिक जानने के लिए, अपस्टैश दस्तावेज़ीकरण देखें। <पी> UPSTASH_WORKFLOW_URL पर्यावरण चर केवल स्थानीय विकास के लिए आवश्यक है। उत्पादन में, baseUrl पैरामीटर स्वचालित रूप से सेट हो जाता है और छोड़ा जा सकता है। परियोजना कार्यान्वयन
सदस्यता फॉर्म घटक
<पी> SubscriptionForm घटक उपयोगकर्ताओं को अपना ईमेल दर्ज करने और यह चुनने की अनुमति देता है कि वे कितनी बार न्यूज़लेटर प्राप्त करना चाहते हैं। जब फॉर्म सबमिट हो जाता है, तो हम /api/subscribe पर एक POST अनुरोध भेजते हैं प्रपत्र डेटा के साथ. src/components/SubscriptionForm.tsx"use client";
import React, { useState } from "react";
export default function SubscriptionForm() {
const [frequency, setFrequency] = useState("daily");
const [showCustomFrequency, setShowCustomFrequency] = useState(false);
const [message, setMessage] = useState("");
const [isError, setIsError] = useState(false);
// Handle frequency selection
const handleFrequencyChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value;
setFrequency(value);
setShowCustomFrequency(value === "custom");
};
// Handle form submission
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setMessage("");
setIsError(false);
const formData = new FormData(e.currentTarget);
try {
const response = await fetch("/api/subscribe", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(Object.fromEntries(formData.entries())),
});
const result = await response.json();
if (!response.ok) {
setIsError(true);
setMessage(result.error || "An error occurred during subscription.");
} else {
setIsError(false);
setMessage(result.message || "Subscription successful!");
}
} catch (error) {
console.error("An unexpected error occurred:", error);
setIsError(true);
setMessage("An unexpected error occurred.");
}
};
// Render the form
return (
<form className="flex flex-col gap-4 text-gray-700" onSubmit={handleSubmit}>
<input
type="email"
name="email"
placeholder="Your Email"
required
className="border p-2 rounded"
/>
<select
name="frequency"
value={frequency}
onChange={handleFrequencyChange}
required
className="border p-2 rounded text-gray-700"
>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="monthly">Monthly</option>
<option value="custom">Custom Amount of Days</option>
</select>
{showCustomFrequency && (
<input
type="number"
name="customFrequency"
placeholder="Enter number of days"
min="1"
className="border p-2 rounded text-gray-700"
required
/>
)}
<button type="submit" className="bg-blue-500 text-white p-2 rounded">
Subscribe
</button>
{message && (
<p className={`mt-2 ${isError ? "text-red-500" : "text-green-500"}`}>
{message}
</p>
)}
</form>
);
}
फॉर्म घटक की सदस्यता समाप्त करें
<पी> UnsubscribeForm घटक उपयोगकर्ताओं को न्यूज़लेटर से सदस्यता समाप्त करने के लिए अपना ईमेल दर्ज करने की अनुमति देता है। जब फॉर्म सबमिट हो जाता है, तो हम /api/unsubscribe पर एक POST अनुरोध भेजते हैं ईमेल डेटा के साथ. यदि उपयोगकर्ता किसी ईमेल में सदस्यता समाप्त करने वाले लिंक पर क्लिक करता है तो यह ईमेल फ़ील्ड को पहले से भर देता है। src/components/UnsubscribeForm.tsx"use client";
import { useState, useEffect, Suspense } from "react";
import { useSearchParams } from "next/navigation";
const UnsubscribeForm = () => {
const searchParams = useSearchParams();
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");
const [isError, setIsError] = useState(false);
// Pre-fill email from query parameter
useEffect(() => {
const emailParam = searchParams.get("email");
if (emailParam) {
setEmail(emailParam);
}
}, [searchParams]);
// Handle form submission
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setMessage("");
setIsError(false);
try {
const response = await fetch("/api/unsubscribe", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
});
const data = await response.json();
if (response.ok) {
setIsError(false);
setMessage("You have been unsubscribed successfully.");
} else {
setIsError(true);
setMessage(data.error || "Something went wrong. Please try again.");
}
} catch (error) {
console.error("Error unsubscribing:", error);
setIsError(true);
setMessage("An unexpected error occurred. Please try again.");
}
};
// Render the form
return (
<form className="flex flex-col gap-4 text-gray-700" onSubmit={handleSubmit}>
<input
type="email"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Your Email"
required
className="border p-2 rounded"
/>
<button
type="submit"
className="bg-red-500 hover:bg-red-700 text-white p-2 rounded"
>
Unsubscribe
</button>
{message && (
<p className={`mt-2 ${isError ? "text-red-500" : "text-green-500"}`}>
{message}
</p>
)}
</form>
);
};
export default function UnsubscribePage() {
return (
<Suspense fallback={<div>Loading...</div>}>
<UnsubscribeForm />
</Suspense>
);
}
रेडिस में डेटा संग्रहीत करें
<पी> हम उपयोगकर्ता सदस्यता डेटा संग्रहीत करने के लिए अपस्टैश रेडिस का उपयोग करेंगे। <पी> अपस्टैश रेडिस का उपयोग करने के लिए, हमें सबसे पहले अपस्टैश कंसोल पर एक रेडिस डेटाबेस स्थापित करना होगा और अपना REST URL और टोकन प्राप्त करना होगा। इस पर अधिक जानकारी के लिए, आप अपस्टैश डॉक्यूमेंटेशन देख सकते हैं। <पी> redis.ts इसमें Redis के साथ इंटरैक्ट करने के लिए हमारे Redis क्लाइंट और सहायक फ़ंक्शन शामिल होंगे: src/lib/redis.tsimport { 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<number | null> {
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<void> {
await redis.del(`user:${email}`);
}
export async function checkSubscription(email: string): Promise<boolean> {
return (await getUserFrequency(email)) !== null;
}
ईमेल भेजने का कार्य
<पी> ईमेल भेजने के लिए, हम अपने स्वयं के ईमेल एपीआई का उपयोग करेंगे जिसे हमने QStash Python SDK के साथ एक ईमेल शेड्यूलर बनाने के बारे में पिछले ब्लॉग पोस्ट में विकसित किया था। src/lib/email.tsexport 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());
}
}
प्रकार परिभाषाएँ
<पी> हमें सदस्यता डेटा के लिए एक प्रकार की परिभाषा की भी आवश्यकता है: src/types/index.tsexport type SubscriptionData = {
email: string;
frequency: string;
customFrequency?: string;
};
एपीआई रूट की सदस्यता लें
<पी> हम एक एपीआई रूट बनाएंगे जो सदस्यता अनुरोधों को संभालेगा। जब कोई उपयोगकर्ता सदस्यता फ़ॉर्म सबमिट करता है, तो यह एंडपॉइंट जांच करेगा कि क्या उपयोगकर्ता पहले से ही सदस्यता ले चुका है और उपयोगकर्ता की चुनी हुई आवृत्ति के आधार पर ईमेल भेजने को संभालने के लिए वर्कफ़्लो को सूचीबद्ध करता है। src/app/api/subscribe/route.tsimport { 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");
// 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 }
);
}
};
एपीआई रूट की सदस्यता समाप्त करें
<पी> चूँकि हमारे पास एक सदस्यता मार्ग है, हमें एक सदस्यता समाप्त मार्ग की भी आवश्यकता है। जब कोई अनुरोध किया जाता है, तो हम जांच करेंगे कि उपयोगकर्ता ने सदस्यता ली है या नहीं और Redis से उनका डेटा हटा देंगे। हम एक पुष्टिकरण ईमेल भी भेजेंगे। src/app/api/unsubscribe/route.tsimport { 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 }
);
}
// Remove the user from Redis
await redis.del(`user:${email}`);
// Send an email to confirm unsubscription
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 }
);
}
};
वर्कफ़्लो एपीआई रूट
<पी> अब, यहाँ मज़ेदार हिस्सा है! हम एक एपीआई रूट बनाएंगे जो निर्दिष्ट आवृत्ति अंतराल पर समाचार पत्र भेजने के लिए वर्कफ़्लो को संभालेगा। <पी> हमारा वर्कफ़्लो निम्नलिखित कार्य करेगा: - उपयोगकर्ता के सदस्यता डेटा को Redis में संग्रहीत करें।
- एक स्वागत योग्य ईमेल भेजें।
- एक लूप दर्ज करें:
- निर्दिष्ट आवृत्ति अवधि तक प्रतीक्षा करें।
- जांचें कि क्या उपयोगकर्ता ने अभी भी सदस्यता ली है।
- न्यूज़लेटर ईमेल भेजें।
- एक निश्चित संख्या में न्यूज़लेटर भेजे जाने तक इसे दोहराते रहें क्योंकि हम अनंत लूप नहीं चाहते हैं।
src/app/api/workflow/route.ts <पी> यहां उस उपयोगकर्ता के लिए पूर्ण वर्कफ़्लो का एक उदाहरण दिया गया है जिसने सदस्यता ली है, एक न्यूज़लेटर प्राप्त किया है और सदस्यता छोड़ दी है: <पी>
<पी> आप अपस्टैश कंसोल से अपने वर्कफ़्लो तक पहुंच सकते हैं और उसकी निगरानी कर सकते हैं। मुख्य पृष्ठ घटक
<पी> आइए अपने एप्लिकेशन का मुख्य पृष्ठ सेट करें। इस पृष्ठ में सदस्यता फ़ॉर्म और सदस्यता समाप्त पृष्ठ का लिंक शामिल होगा। src/app/page.tsximport SubscriptionForm from "@/components/SubscriptionForm";
import Link from "next/link";
export default function Home() {
return (
<main className="flex flex-col items-center justify-center min-h-screen p-4">
<h1 className="text-3xl font-bold mb-6">
Subscribe to Upstash Newsletter
</h1>
{/* Subscription Form */}
<SubscriptionForm />
{/* Unsubscribe Link */}
<div className="mt-8">
<p className="text-gray-600">
Already subscribed and want to unsubscribe?
<Link
href="/unsubscribe"
className="text-red-500 hover:text-red-700 font-bold ml-2"
>
Click here to unsubscribe
</Link>
</p>
</div>
</main>
);
}
पेज घटक की सदस्यता समाप्त करें
<पी> अंत में, आइए अनसब्सक्रिप्शन पेज बनाएं। src/app/unsubscribe/page.tsximport UnsubscribePage from "@/components/UnsubscribeForm";
export default function UnsubscribeHome() {
return (
<main className="flex flex-col items-center justify-center min-h-screen p-4">
<h1 className="text-3xl font-bold mb-6">
Unsubscribe from Upstash Newsletter
</h1>
{/* Unsubscribe Form */}
<UnsubscribePage />
</main>
);
}
निष्कर्ष
<पी> और यह आपके पास है! हमने सर्वर रहित फ़ंक्शन टाइमआउट की चिंता किए बिना एक सरल न्यूज़लेटर ऐप बनाया है। <पी> आप इस प्रोजेक्ट के लिए संपूर्ण स्रोत कोड GitHub पर पा सकते हैं और आप यहां लाइव डेमो देख सकते हैं। <पी> अपस्टैश वर्कफ़्लो पर अधिक जानकारी के लिए, आप अपस्टैश दस्तावेज़ीकरण का संदर्भ ले सकते हैं। <पी> यदि आपके कोई प्रश्न हैं, तो बेझिझक डिस्कॉर्ड पर हमसे संपर्क करें। इसके अलावा, अधिक ट्यूटोरियल और उपयोग के मामलों के लिए अपस्टैश ब्लॉग को देखना न भूलें।