Remix ফুলস্ট্যাক React ফ্রেমওয়ার্ক হওয়ার একটি নতুন দৃষ্টিভঙ্গি, বিদ্যমান ওয়েব স্ট্যান্ডার্ডস ফোকাস করে এবং ফ্রন্টএন্ডকে ব্যাকএন্ডের সাথে ঘনিষ্ঠভাবে বাঁধে। এই টাইট কাপলিং হল একটি নতুন বাতাস যখন আপনি দেখবেন কীভাবে সহজে ডেটা আপনার React কম্পোনেন্টে লোড করা যায় বা কীভাবে ফর্ম থেকে সাবমিট করা ডেটা প্রসেস করা যায়।
এই আর্টিকেলে আমরা Remix-এর শক্তি দেখব একটি সিম্পল ফিচার ফ্ল্যাগ ম্যানেজমেন্ট সিস্টেম তৈরি করে Upstash Redis-কে ডেটাবেস হিসেবে ব্যবহার করে।
ফুল সোর্স কোড এখানে পাওয়া যাবে।
সেটআপ
আপনি npx create-remix@latest চালিয়ে একটি ব্র্যান্ড নিউ Remix অ্যাপ পাবেন এবং আপনার পছন্দের ডিপ্লয়মেন্ট এনভায়রনমেন্ট বেছে নেবেন। আমি Vercel-এ গিয়েছিলাম কিন্তু এই টিউটোরিয়ালের জন্য কোন পার্থক্য করবে না।
Upstash রেডিসের সাথে কানেক্ট করার দুটি উপায় আছে: প্রথমটি হল TCP কানেকশন যার মাধ্যমে আপনি যেকোনো স্ট্যান্ডার্ড রেডিস ক্লায়েন্ট লাইব্রেরি ব্যবহার করতে পারেন যা আপনি অভ্যস্ত। দ্বিতীয়টি হল Upstash-এর REST API-এর মাধ্যমে। আমরা দ্বিতীয় অপশনটির সাথে যাব কারণ এটি সব সার্ভারলেস এনভায়রনমেন্টে উপলব্ধ, যেমন যে Cloudflare Workers এনভায়রনমেন্ট যেখানে Remix ডিপ্লয় করা যায়। Upstash-এর একটি প্যাকেজ আছে যা আসল Redis কমান্ডগুলোর নকল করে, যা জানা সহজ করে দেয় কোন ফাংশন কল করতে হবে।
আমাদের এখন দুটি এনভায়রনমেন্ট ভেরিয়েবল স্টোর করার একটি উপায় দরকার আমাদের Upstash রেডিস ডেটাবেসের সাথে কানেক্ট করার জন্য। Remix development env var support আউট-অফ-দ্য-বক্স দেয় না, কিন্তু এটি dotenv ডেভেলপমেন্ট ডিপেন্ডেন্সি হিসেবে যোগ করে অর্জন করা যায়।
npm add --save-dev dotenv
আমাদের .env ফাইলে (যেটি .gitignore-এ যোগ করা উচিত) আমরা Upstash রেডিসের সাথে কানেক্ট করার জন্য প্রয়োজনীয় দুটি env var সেট আপ করতে পারি। @upstash/redis প্যাকেজটি অটোমেটিক্যালি এইগুলোর ডিটেক্ট করে তাই আমাদের কোডের ভিতরে কানেক্ট করার প্রয়োজন নেই। এই ভ্যালুগুলো Upstash ড্যাশবোর্ডে একটি নতুন রেডিস ডেটাবেস তৈরি করার পর পাওয়া যাবে।
UPSTASH_REDIS_REST_URL="https://..."
UPSTASH_REDIS_REST_TOKEN="..."
আমাদের dev স্ক্রিপ্ট আপডেট করতে হবে dotenv এনভায়রনমেন্ট ভেরিয়েবল পিক আপ করার জন্য। অন্যান্য স্ক্রিপ্টগুলো একই থাকতে পারে।
{
"scripts": {
"dev": "node -r dotenv/config node_modules/.bin/remix dev"
}
}
ফিচার ডেটা স্টোর করা
ফিচার ফ্ল্যাগগুলো অবিশ্বাস্যভাবে জটিল হতে পারে, আপনার ইউজারবেসের পার্সেন্টেজের কাছে রোলআউট প্ল্যান, নির্দিষ্ট গ্রুপের ইউজারদের জন্য ইনএবল, কিন্তু তারা সিম্পল "on" এবং "off" ও হতে পারে। আমরা আমাদের ফিচার ফ্ল্যাগগুলো স্টোর করব রেডিস প্রদত্ত Hash ডেটা টাইপ ব্যবহার করে। আমাদের ডেটা নিচের JSON-এর মত দেখাবে, যেখানে "1" হল enabled/on এবং "0" হল disabled/off।
{
"chart": "1",
"graph": "0"
}
এই ডেটা অ্যাক্সেস এবং ম্যানিপুলেট করার জন্য আমরা রেডিস দ্বারা প্রদত্ত চারটি কমান্ড/ফাংশন ব্যবহার করব:
hgetallসব কি (ফিচার) এবং ভ্যালু (enabled/disabled) রিট্রিভ করতে।hsetএকটি নির্দিষ্ট ফিচার ফ্ল্যাগ ইনএবল বা ডিসএবল করতে।hdelএকটি নির্দিষ্ট ফিচার ফ্ল্যাগ ডিলিট করতে।hmgetএকবারে একাধিক কিন্তু নির্দিষ্ট ফিচার ফ্ল্যাগ ভ্যালু পেতে।
ফিচার ম্যানেজ করা
আমরা /features-এ অবস্থিত একটি পেজ বিল্ড করব যা ফিচার তৈরি এবং ম্যানেজ (ইনএবল/ডিসএবল/ডিলিট) করার দায়িত্বে থাকবে। আমরা AddFeature এবং FeatureList কী করবে সেটি বিস্তারিত আলোচনা করব যখন ডেটা লোড এবং তারপর ডেটা লেখা নিয়ে কথা বলব।
// app/routes/features.tsx
export default function Features() {
return (
<div>
<h1>Features</h1>
<AddFeature />
<FeatureList />
</div>
);
}
ডেটা লোডার
একটি ডেটা লোডার Remix-এ একটি এক্সপোর্টেড ফাংশন যেটির নাম loader যা সার্ভারে রান করে এবং ডেটা রিটার্ন করে যা আমাদের React কম্পোনেন্টে একটি হук দিয়ে উপলব্ধ করা হয়।
আমরা ফিচার ফ্ল্যাগ তৈরি এবং ম্যানেজ করার একটি পেজ দিয়ে শুরু করছি, এবং এই কেসে আমরা সব ফিচার রিটার্ন করতে চাই। সেগুলো এক জোড়া আरे হিসেবে রিটার্ন হবে:
[
["graph", true],
["chart", false]
]
একটি TypeScript টাইপ ডেফিনিশন দিয়ে শুরু করি, তারপর আমরা loadAllFeatures নামক একটি ফাংশন দেখব যা @upstash/redis- থেকে hgetall ফাংশন ব্যবহার করে।
import { Redis } from "@upstash/redis";
type LoaderData = {
features: Array<[string, boolean]>;
};
const loadAllFeatures = async () => {
const redis = Redis.fromEnv();
const data = await redis.hgetall("features");
const features: Array<[string, boolean]> = [];
for (let i = 0; i < data.length; i += 2) {
features.push([data[i], data[i + 1] === "1"]);
}
return features.sort((a, b) => {
if (a[0] > b[0]) return 1;
if (a[0] < b[0]) return -1;
return 0;
});
};
এক্সপোর্টেড loader ফাংশনটি নিজে loadAllFeatures ফাংশন কল করবে, ফিচারগুলো রিটার্ন করে React কম্পোনেন্টে পাস করার জন্য।
export const loader: LoaderFunction = async (): Promise => {
// আপনি এখানে অথেনটিকেশন/অথরাইজেশন যোগ করতে চাবেন
const features = await loadAllFeatures();
return { features };
};
আমরা এই React কম্পোনেন্টের ডিটেইলস পরে কভার করব, কিন্তু লোডার ফাংশন থেকে রিটার্ন করা ডেটা কীভাবে এক্সেস করতে হয় সেটি দেখাতে হলে, আপনি Remix-এর useLoaderData হুক ব্যবহার করবেন।
const FeatureList = () => {
const { features } = useLoaderData();
return (
<ul>
{features.map(([feature, active]) => (
<li key={feature}>{/* coming soon */}</li>
))}
</ul>
);
};
ফর্ম অ্যাকশন
আমরা দেখেছি ডেটা কীভাবে লোড হয়, কিন্তু এই স্টেজে আমাদের ফিচার ফ্ল্যাগ ডেটাবেসে আসলে কোনো ফিচার নেই! এখানেই ফর্ম অ্যাকশন কাজে আসে। Remix-এ ডেটা প্রসেস করা হয় একটি এক্সপোর্টেড ফাংশন দিয়ে যেটির নাম action। loader-এর মতো, এটি সার্ভারে রান করে এবং এটি সাধারণত json ডেটা রিটার্ন করে যা React কম্পোনেন্ট অন্য একটি হুক দিয়ে এক্সেস করতে পারে, অথবা এটি ব্রাউজারকে redirect করতে পারে অন্য একটি পেজে।
নিচের action ফাংশনটি আসলে চার ধরনের অ্যাকশন হ্যান্ডেল করে, একটি ফিচার তৈরি করা, একটি ফিচার ইনএবল/ডিসএবল করা, এবং একটি ফিচার ডিলিট করা। আমরা এটিকে একটি switch স্টেটমেন্ট দিয়ে হ্যান্ডেল করি যা তারপর উপযুক্ত রেডিস ফাংশন/কমান্ড কল করে।
export const action: ActionFunction = async ({ request }) => {
// আপনি এখানে অথেনটিকেশন/অথরাইজেশন যোগ করতে চাবেন
const formData = await request.formData();
const feature = formData.get("feature") as string;
const action = formData.get("_action") as string;
if (!feature || feature.length === 0) {
// এটি বর্তমানে আমাদের কম্পোনেন্টে ডিসপ্লে হয় না
return json({ error: "Please provide a feature" });
}
switch (action) {
case "create":
case "enable":
await redis.hset("features", { [feature]: 1 });
break;
case "disable":
await redis.hset("features", { [feature]: 0 });
break;
case "delete":
await redis.hdel("features", feature);
break;
}
return redirect("/features");
};
আপনি লক্ষ্য করবেন যে সাকসেস হলে, আমি একই পেজে রিডাইরেক্ট করছি যেখানে ইউজার বর্তমানে আছে। এটি অনিয়ন্ত্রে একটি পেজ রিলোড ট্রিগার করে, loader ফাংশন কল করে এবং ইউজারকে যা ডিসপ্লে হচ্ছে সেটি আপডেট করে।
একটি নতুন ফিচার ফ্ল্যাগ যোগ করতে, AddFeature কম্পোনেন্টটি Remix Form কম্পোনেন্ট ব্যবহার করবে যা ডেটা সাবমিট করবে উপরের action ফাংশনে। আমি স্পেসিফাই করেছি যে এটি post মেথড দিয়ে সাবমিট হবে এবং replace প্রপও দিয়েছি যাতে প্রতিবার ফিচার ফ্ল্যাগ তৈরি করার সময় ব্রাউজারের হিস্টরিতে নতুন পেজ অ্যাড না হয়।
const AddFeature = () => {
return (
<Form method="post" replace>
<input type="hidden" name="_action" value="create" />
<input type="text" name="feature" required placeholder="name" />
<button type="submit">Add</button>
</Form>
);
};
একবার ফিচার তৈরি হলে, আমরা সব করেন্ট ফিচার ফ্ল্যাগ দেখাতে চাই যাতে সেগুলো ম্যানেজ করা যায়। প্রতিটি ফিচার ফ্ল্যাগ আসলে দুটি ফর্ম ডিসপ্লে করে: একটি ফিচার ফ্ল্যাগ ইনএবল/ডিসএবল করার জন্য, এবং দ্বিতীয়টি সেটি ডিলিট করার জন্য।
লক্ষ্য করুন যে দুটি হিডেন ফিল্ড আছে: _action যাতে আমাদের action ফাংশনটি জানে আমরা ফিচারের সাথে কী করার চেষ্টা করছি, এবং feature যেটি সেই ফ্ল্যাগের নাম পাঠায় जिसे আমরা মডিফাই করতে চাই।
const FeatureList = () => {
const { features } = useLoaderData();
return (
<ul>
{features.map(([feature, active]) => (
<li key={feature}>
<Form method="post" replace>
<input type="hidden" name="_action" value={active ? "disable" : "enable"} />
<input type="hidden" name="feature" value={feature} />
<button type="submit" className="btn-naked">
{active ? "💪" : "🦾"}
</button>
</Form>
<span>{feature}</span>
<Form method="post" replace>
<input type="hidden" name="_action" value="delete" />
<input type="hidden" name="feature" value={feature} />
<button type="submit">Delete</button>
</Form>
</li>
))}
</ul>
);
};
ফিচার ব্যবহার করা
আমাদের Upstash রেডিস ডেটাবেসে ফিচার ফ্ল্যাগ আছে, কিন্তু এটার কী ফায়দা যদি আমরা এই ফ্ল্যাগের উপর ভিত্তি করে আমাদের অ্যাপের ফাংশনালিটি অন/অফ না করি। আমরা একটি লোডার ফাংশন ব্যবহার করব নির্দিষ্ট ফিচার ডেটাবেস থেকে লোড করতে hmget ব্যবহার করে, এবং তারপর কয়েকটু ডেটা ম্যানিপুলেশন করে সেটিকে সঠিক স্ট্রাকচারে আনা।
যদি আমরা ["chart", "graph", "fake"] ফ্ল্যাগ লোড করতে চাই, রেডিস আমাদের ["1", "0", null] রিটার্ন করবে... মনে রাখবেন যে যদি ফ্ল্যাগ অস্তিত্বে না থাকে, এর ভ্যালু null হবে, যা আমি fake ফ্ল্যাগ ইনক্লুড করে দেখাতে চেয়েছিলাম।
type LoaderData = {
features: Record;
};
const loadFeatures = async (keys: Array) => {
const data = await redis.hmget("features", ...keys);
const features = keys.reduce>((acc, key, index) => {
acc[key] = data[index] === "1";
return acc;
}, {});
return features;
};
export const loader: LoaderFunction = async (): Promise => {
const features = await loadFeatures(["chart", "graph"]);
return { features };
};
আমরা এখন লোড করা ডেটা আমাদের কম্পোনেন্টে এক্সেস করতে পারি, আবার Remix-এর useLoaderData হুক ব্যবহার করে। তারপর চুনে নিন আমাদের ওয়েবসাইটের ফাংশনালিটি কীভাবে পরিবর্তন হওয়া উচিত দেওয়া ফ্ল্যাগ বর্তমানে ইনএবলড বা ডিসএবলড।
export default function Index() {
const { features } = useLoaderData();
return (
<div>
<h1>Dashboard</h1>
{features.chart ? <h2>Chart</h2> : <h2>No Chart</h2>}
{features.graph ? <h2>Graph</h2> : <h2>No Graph</h2>}
</div>
);
}
উপসংহার
এই আর্টিকেলে আমরা দেখেছি কীভাবে Upstash রেডিস ব্যবহার করে Remix-এ একটি সিম্পল ফিচার ফ্ল্যাগ সিস্টেম তৈরি করতে হয়, এর ডেটা লোডার এবং ফর্ম অ্যাকশন সার্ভার-সাইড ফাংশনগুলোর সুবিধা নিয়ে। এইগুলো আমাদের একটি নির্দিষ্ট পেজের ব্যাকএন্ড এবং ফ্রন্টএন্ডকে টাইটলি কাপলড রাখতে দেয়, দ্রুত ইটারেট করতে দেয় একটি আলাদা GraphQL API সেটআপ করার প্রয়োজন ছাড়া এবং ফ্রন্টএন্ডে স্ট্যান্ডার্ড ফর্ম সাবমিশন ইভেন্ট ওভাররাইড করার প্রয়োজন ছাড়া। Remix যেমন আমরা দেখেছি ওয়েব স্ট্যান্ডার্ডসের দিকে ঝুকে যা ফর্মগুলো কীভাবে তাদের ডেটা সাবমিট করে।