রিঅ্যাক্ট সার্ভার অ্যাকশন ভ্যালিডেশন শিখুন। Zod, useFormState এবং useFormStatus ব্যবহার করে ফর্ম প্রসেসিং, নিরাপত্তা এবং উন্নত কৌশলগুলির বিস্তারিত আলোচনা।
রিঅ্যাক্ট সার্ভার অ্যাকশন ভ্যালিডেশন: ফর্ম ইনপুট প্রসেসিং এবং নিরাপত্তার একটি সম্পূর্ণ নির্দেশিকা
Next.js-এর মতো ফ্রেমওয়ার্কের সাথে রিঅ্যাক্ট সার্ভার অ্যাকশনের প্রবর্তন ফুল-স্ট্যাক ডেভেলপমেন্টে একটি গুরুত্বপূর্ণ পরিবর্তন এনেছে। ক্লায়েন্ট কম্পোনেন্টগুলিকে সরাসরি সার্ভার-সাইড ফাংশন কল করার অনুমতি দিয়ে, আমরা এখন কম বয়লারপ্লেট কোড ব্যবহার করে আরও সুসংহত, দক্ষ এবং ইন্টারেক্টিভ অ্যাপ্লিকেশন তৈরি করতে পারি। তবে, এই শক্তিশালী নতুন অ্যাবস্ট্র্যাকশনের সাথে একটি গুরুত্বপূর্ণ দায়িত্বও আসে: শক্তিশালী ইনপুট ভ্যালিডেশন এবং নিরাপত্তা।
যখন ক্লায়েন্ট এবং সার্ভারের মধ্যে সীমানা এত মসৃণ হয়ে যায়, তখন ওয়েব নিরাপত্তার মৌলিক নীতিগুলি উপেক্ষা করা সহজ হয়ে পড়ে। ব্যবহারকারীর কাছ থেকে আসা যেকোনো ইনপুট অবিশ্বস্ত এবং সার্ভারে কঠোরভাবে যাচাই করা আবশ্যক। এই নির্দেশিকাটি রিঅ্যাক্ট সার্ভার অ্যাকশনের মধ্যে ফর্ম ইনপুট প্রসেসিং এবং ভ্যালিডেশনের একটি বিশদ বিবরণ প্রদান করে, যেখানে মৌলিক নীতি থেকে শুরু করে উন্নত, প্রোডাকশন-রেডি প্যাটার্ন পর্যন্ত সবকিছু অন্তর্ভুক্ত রয়েছে যা আপনার অ্যাপ্লিকেশনকে ব্যবহারকারী-বান্ধব এবং সুরক্ষিত উভয়ই নিশ্চিত করে।
রিঅ্যাক্ট সার্ভার অ্যাকশন আসলে কী?
ভ্যালিডেশনে যাওয়ার আগে, চলুন সংক্ষেপে জেনে নেওয়া যাক সার্ভার অ্যাকশন কী। মূলত, এগুলি এমন ফাংশন যা আপনি সার্ভারে ডিফাইন করেন কিন্তু ক্লায়েন্ট থেকে এক্সিকিউট করতে পারেন। যখন একজন ব্যবহারকারী একটি ফর্ম জমা দেয় বা একটি বোতামে ক্লিক করে, তখন একটি সার্ভার অ্যাকশন সরাসরি কল করা যেতে পারে, যার ফলে ম্যানুয়ালি এপিআই এন্ডপয়েন্ট তৈরি করা, `fetch` রিকোয়েস্ট হ্যান্ডেল করা এবং লোডিং/ত্রুটির স্টেট পরিচালনা করার প্রয়োজন হয় না।
এগুলি HTML ফর্ম এবং ওয়েব প্ল্যাটফর্মের `FormData` এপিআই-এর উপর ভিত্তি করে তৈরি, যা তাদের ডিফল্টরূপে প্রগতিশীলভাবে উন্নত করে। এর মানে হল আপনার ফর্মগুলি জাভাস্ক্রিপ্ট লোড হতে ব্যর্থ হলেও কাজ করবে, যা একটি স্থিতিশীল ব্যবহারকারীর অভিজ্ঞতা প্রদান করে।
একটি বেসিক সার্ভার অ্যাকশনের উদাহরণ:
// app/actions.js
'use server';
export async function createUser(formData) {
const name = formData.get('name');
const email = formData.get('email');
// ... logic to save user to the database
console.log('Creating user:', { name, email });
}
// app/page.js
import { createUser } from './actions';
export default function UserForm() {
return (
<form action={createUser}>
<input type="text" name="name" placeholder="Name" />
<input type="email" name="email" placeholder="Email" />
<button type="submit">Create User</button>
</form>
);
}
এই সরলতা শক্তিশালী, কিন্তু এটি কী ঘটছে তার জটিলতাও লুকিয়ে রাখে। `createUser` ফাংশনটি শুধুমাত্র সার্ভারে চলছে, তবুও এটি একটি ক্লায়েন্ট কম্পোনেন্ট থেকে কল করা হচ্ছে। আপনার সার্ভার লজিকের সাথে এই সরাসরি সংযোগই হল কারণ কেন ভ্যালিডেশন শুধুমাত্র একটি বৈশিষ্ট্য নয় - এটি একটি অপরিহার্য প্রয়োজনীয়তা।
ভ্যালিডেশনের অটল গুরুত্ব
সার্ভার অ্যাকশনের জগতে, প্রতিটি ফাংশন আপনার সার্ভারের জন্য একটি খোলা দরজা। সঠিক ভ্যালিডেশন সেই দরজার প্রহরীর মতো কাজ করে। এখানে কেন এটি অপরিহার্য তার কারণগুলি দেওয়া হলো:
- ডেটা ইন্টিগ্রিটি: আপনার ডেটাবেস এবং অ্যাপ্লিকেশন স্টেট পরিষ্কার, অনুমানযোগ্য ডেটার উপর নির্ভর করে। ভ্যালিডেশন নিশ্চিত করে যে আপনি ভুল ফর্ম্যাটের ইমেল ঠিকানা, নামের জায়গায় খালি স্ট্রিং, বা সংখ্যার ফিল্ডে টেক্সট সংরক্ষণ করছেন না।
- উন্নত ব্যবহারকারী অভিজ্ঞতা (UX): ব্যবহারকারীরা ভুল করে। স্পষ্ট, তাৎক্ষণিক এবং প্রাসঙ্গিক ত্রুটি বার্তাগুলি তাদের ইনপুট সংশোধন করতে গাইড করে, যা হতাশা কমায় এবং ফর্ম পূরণের হার বাড়ায়।
- লোহার মতো মজবুত নিরাপত্তা: এটি সবচেয়ে গুরুত্বপূর্ণ দিক। সার্ভার-সাইড ভ্যালিডেশন ছাড়া, আপনার অ্যাপ্লিকেশন বিভিন্ন আক্রমণের জন্য ঝুঁকিপূর্ণ হয়ে পড়ে, যার মধ্যে রয়েছে:
- SQL ইনজেকশন: একজন দূষিত হ্যাকার আপনার ডেটাবেস ম্যানিপুলেট করার জন্য ফর্ম ফিল্ডে SQL কমান্ড জমা দিতে পারে।
- ক্রস-সাইট স্ক্রিপ্টিং (XSS): আপনি যদি স্যানিটাইজ না করা ব্যবহারকারীর ইনপুট সংরক্ষণ এবং রেন্ডার করেন, একজন আক্রমণকারী দূষিত স্ক্রিপ্ট ইনজেক্ট করতে পারে যা অন্য ব্যবহারকারীদের ব্রাউজারে এক্সিকিউট হবে।
- ডেনায়েল অফ সার্ভিস (DoS): অপ্রত্যাশিতভাবে বড় বা কম্পিউটেশনালি ব্যয়বহুল ডেটা জমা দিলে আপনার সার্ভার রিসোর্স ওভারলোড হতে পারে।
ক্লায়েন্ট-সাইড বনাম সার্ভার-সাইড ভ্যালিডেশন: একটি প্রয়োজনীয় অংশীদারিত্ব
এটা বোঝা গুরুত্বপূর্ণ যে ভ্যালিডেশন দুটি জায়গায় হওয়া উচিত:
- ক্লায়েন্ট-সাইড ভ্যালিডেশন: এটি UX-এর জন্য। এটি নেটওয়ার্ক রাউন্ড-ট্রিপ ছাড়াই তাৎক্ষণিক প্রতিক্রিয়া প্রদান করে। আপনি `required`, `minLength`, `pattern`-এর মতো সাধারণ HTML5 অ্যাট্রিবিউট বা জাভাস্ক্রিপ্ট ব্যবহার করে ব্যবহারকারীর টাইপ করার সাথে সাথে ফর্ম্যাট চেক করতে পারেন। তবে, জাভাস্ক্রিপ্ট নিষ্ক্রিয় করে বা ডেভেলপার টুল ব্যবহার করে এটি সহজেই বাইপাস করা যায়।
- সার্ভার-সাইড ভ্যালিডেশন: এটি নিরাপত্তা এবং ডেটা ইন্টিগ্রিটির জন্য। এটি আপনার অ্যাপ্লিকেশনের চূড়ান্ত সত্যের উৎস। ক্লায়েন্টে যা-ই ঘটুক না কেন, সার্ভারকে অবশ্যই তার প্রাপ্ত সবকিছু পুনরায় ভ্যালিডেট করতে হবে। সার্ভার অ্যাকশন এই লজিক প্রয়োগ করার জন্য উপযুক্ত জায়গা।
সাধারণ নিয়ম: উন্নত ব্যবহারকারী অভিজ্ঞতার জন্য ক্লায়েন্ট-সাইড ভ্যালিডেশন ব্যবহার করুন, কিন্তু সর্বদা শুধুমাত্র সার্ভার-সাইড ভ্যালিডেশনকে বিশ্বাস করুন।
সার্ভার অ্যাকশনে ভ্যালিডেশন প্রয়োগ করা: বেসিক থেকে অ্যাডভান্সড
চলুন আমাদের ভ্যালিডেশন কৌশলটি ধাপে ধাপে তৈরি করি, একটি সহজ পদ্ধতি দিয়ে শুরু করে এবং আধুনিক সরঞ্জাম ব্যবহার করে আরও শক্তিশালী, স্কেলেবল সমাধানে এগিয়ে যাই।
পদ্ধতি ১: ম্যানুয়াল ভ্যালিডেশন এবং স্টেট রিটার্ন করা
ভ্যালিডেশন হ্যান্ডেল করার সবচেয়ে সহজ উপায় হল আপনার সার্ভার অ্যাকশনের ভিতরে `if` স্টেটমেন্ট যোগ করা এবং সাফল্য বা ব্যর্থতা নির্দেশ করে একটি অবজেক্ট রিটার্ন করা।
// app/actions.js
'use server';
import { redirect } from 'next/navigation';
export async function createInvoice(formData) {
const customerName = formData.get('customerName');
const amount = formData.get('amount');
if (!customerName || customerName.trim() === '') {
return { success: false, message: 'Customer name is required.' };
}
if (!amount || isNaN(Number(amount)) || Number(amount) <= 0) {
return { success: false, message: 'Please enter a valid amount greater than zero.' };
}
// ... logic to create the invoice in the database
console.log('Invoice created for', customerName, 'with amount', amount);
redirect('/dashboard/invoices');
}
এই পদ্ধতিটি কাজ করে, কিন্তু এর একটি বড় UX ত্রুটি রয়েছে: ত্রুটি বার্তা প্রদর্শনের জন্য একটি সম্পূর্ণ পৃষ্ঠা রিলোড করতে হয়। আমরা সহজেই ফর্ম পেজেই বার্তাটি দেখাতে পারি না। এখানেই রিঅ্যাক্টের সার্ভার অ্যাকশনের জন্য হুকগুলি কাজে আসে।
পদ্ধতি ২: `useFormState` ব্যবহার করে মসৃণ ত্রুটি হ্যান্ডলিং
`useFormState` হুকটি বিশেষভাবে এই উদ্দেশ্যে ডিজাইন করা হয়েছে। এটি একটি সার্ভার অ্যাকশনকে এমন স্টেট রিটার্ন করার অনুমতি দেয় যা একটি সম্পূর্ণ নেভিগেশন ইভেন্ট ছাড়াই UI আপডেট করতে ব্যবহার করা যেতে পারে। এটি সার্ভার অ্যাকশনসহ আধুনিক ফর্ম হ্যান্ডলিংয়ের ভিত্তি।
চলুন আমাদের ইনভয়েস তৈরির ফর্মটি রিফ্যাক্টর করি।
ধাপ ১: সার্ভার অ্যাকশন আপডেট করুন
অ্যাকশনটিকে এখন দুটি আর্গুমেন্ট গ্রহণ করতে হবে: `prevState` এবং `formData`। এটি একটি নতুন স্টেট অবজেক্ট রিটার্ন করবে যা `useFormState` কম্পোনেন্ট আপডেট করতে ব্যবহার করবে।
// app/actions.js
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
// Define the initial state shape
const initialState = {
message: null,
errors: {},
};
export async function createInvoice(prevState, formData) {
const customerName = formData.get('customerName');
const amount = formData.get('amount');
const status = formData.get('status');
const errors = {};
if (!customerName || customerName.trim().length < 2) {
errors.customerName = 'Customer name must be at least 2 characters.';
}
if (!amount || isNaN(Number(amount)) || Number(amount) <= 0) {
errors.amount = 'Please enter a valid amount.';
}
if (status !== 'pending' && status !== 'paid') {
errors.status = 'Please select a valid status.';
}
if (Object.keys(errors).length > 0) {
return {
message: 'Failed to create invoice. Please check the fields.',
errors,
};
}
try {
// ... logic to save to database
console.log('Invoice created successfully!');
} catch (e) {
return {
message: 'Database Error: Failed to create invoice.',
errors: {},
};
}
// Revalidate the cache for the invoices page and redirect
revalidatePath('/dashboard/invoices');
redirect('/dashboard/invoices');
}
ধাপ ২: `useFormState` দিয়ে ফর্ম কম্পোনেন্ট আপডেট করুন
আমাদের ক্লায়েন্ট কম্পোনেন্টে, আমরা ফর্মের স্টেট পরিচালনা করতে এবং ত্রুটিগুলি প্রদর্শন করতে হুকটি ব্যবহার করব।
// app/ui/invoices/create-form.js
'use client';
import { useFormState } from 'react-dom';
import { createInvoice } from '@/app/actions';
const initialState = {
message: null,
errors: {},
};
export function CreateInvoiceForm() {
const [state, dispatch] = useFormState(createInvoice, initialState);
return (
<form action={dispatch}>
<div>
<label htmlFor="customerName">Customer Name</label>
<input id="customerName" name="customerName" type="text" />
{state.errors?.customerName &&
<p style={{ color: 'red' }}>{state.errors.customerName}</p>}
</div>
<div>
<label htmlFor="amount">Amount</label>
<input id="amount" name="amount" type="number" step="0.01" />
{state.errors?.amount &&
<p style={{ color: 'red' }}>{state.errors.amount}</p>}
</div>
{/* ... other fields ... */}
{state.message && <p style={{ color: 'red' }}>{state.message}</p>}
<button type="submit">Create Invoice</button>
</form>
);
}
এখন, যখন ব্যবহারকারী একটি অবৈধ ফর্ম জমা দেয়, সার্ভার অ্যাকশনটি চলে, ত্রুটি অবজেক্ট রিটার্ন করে, এবং `useFormState` `state` ভেরিয়েবল আপডেট করে। কম্পোনেন্টটি পুনরায় রেন্ডার হয়, সংশ্লিষ্ট ফিল্ডগুলির পাশে নির্দিষ্ট ত্রুটি বার্তাগুলি প্রদর্শন করে—সবই পৃষ্ঠা রিলোড ছাড়াই। এটি একটি বিশাল UX উন্নতি!
পদ্ধতি ৩: `useFormStatus` দিয়ে UX উন্নত করা
সার্ভার অ্যাকশন চলার সময় কী হয়? ব্যবহারকারী সাবমিট বোতামটি একাধিকবার ক্লিক করতে পারে। আমরা `useFormStatus` হুক ব্যবহার করে প্রতিক্রিয়া জানাতে পারি, যা আমাদের শেষ ফর্ম সাবমিশনের স্ট্যাটাস সম্পর্কে তথ্য দেয়।
গুরুত্বপূর্ণ: `useFormStatus` অবশ্যই এমন একটি কম্পোনেন্টে ব্যবহার করতে হবে যা `<form>` এলিমেন্টের একটি চাইল্ড।
// app/ui/submit-button.js
'use client';
import { useFormStatus } from 'react-dom';
export function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" aria-disabled={pending} disabled={pending}>
{pending ? 'Creating...' : 'Create Invoice'}
</button>
);
}
// In your CreateInvoiceForm component:
import { SubmitButton } from './submit-button';
// ...
<form action={dispatch}>
{/* ... form fields ... */}
<SubmitButton />
</form>
এই সাধারণ সংযোজনের সাথে, সাবমিট বোতামটি এখন নিষ্ক্রিয় থাকবে এবং সার্ভার অনুরোধটি প্রক্রিয়া করার সময় "Creating..." দেখাবে, যা ডুপ্লিকেট সাবমিশন রোধ করে এবং ব্যবহারকারীকে স্পষ্ট প্রতিক্রিয়া দেয়।
পদ্ধতি ৪: Zod ব্যবহার করে প্রোডাকশন-গ্রেড ভ্যালিডেশন
সাধারণ ফর্মের জন্য ম্যানুয়াল `if` চেক ঠিক আছে, কিন্তু জটিল অ্যাপ্লিকেশনের জন্য, সেগুলি ভার্বোস, ত্রুটি-প্রবণ এবং রক্ষণাবেক্ষণ করা কঠিন হয়ে পড়ে। এখানেই স্কিমা ভ্যালিডেশন লাইব্রেরি যেমন Zod, Yup, বা Valibot অসাধারণ কাজ করে।
Zod একটি TypeScript-ফার্স্ট স্কিমা ডিক্লারেশন এবং ভ্যালিডেশন লাইব্রেরি। এটি আপনাকে আপনার ডেটার জন্য একটি একক স্কিমা সংজ্ঞায়িত করার অনুমতি দেয় যা সার্ভার এবং ক্লায়েন্ট উভয় ক্ষেত্রেই ব্যবহার করা যেতে পারে, যা সামঞ্জস্য এবং টাইপ সেফটি নিশ্চিত করে।
ধাপ ১: একটি Zod স্কিমা ডিফাইন করুন
চলুন আমাদের ইনভয়েস ফর্মের জন্য একটি স্কিমা ডিফাইন করি। এটি একটি বৈধ ইনভয়েস কী হবে তার জন্য একমাত্র সত্যের উৎস হয়ে ওঠে।
// app/lib/schemas.js
import { z } from 'zod';
export const InvoiceSchema = z.object({
id: z.string(), // We'll have this for updates, but not creation
customerId: z.string({ invalid_type_error: 'Please select a customer.' }),
amount: z.coerce
.number()
.gt(0, { message: 'Please enter an amount greater than $0.' }),
status: z.enum(['pending', 'paid'], {
invalid_type_error: 'Please select an invoice status.',
}),
date: z.string(),
});
export const CreateInvoice = InvoiceSchema.omit({ id: true, date: true });
ধাপ ২: আপনার সার্ভার অ্যাকশনে স্কিমাটি ব্যবহার করুন
এখন, আমরা আমাদের সমস্ত ম্যানুয়াল চেকগুলিকে আমাদের Zod স্কিমাতে একটি একক কল দিয়ে প্রতিস্থাপন করতে পারি। Zod-এর `safeParse` মেথড হয় ভ্যালিডেটেড ডেটা অথবা একটি বিস্তারিত ত্রুটি অবজেক্ট রিটার্ন করবে।
// app/actions.js
'use server';
import { z } from 'zod';
import { CreateInvoice } from '@/app/lib/schemas';
export async function createInvoiceWithZod(prevState, formData) {
// 1. Validate form fields using Zod
const validatedFields = CreateInvoice.safeParse({
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
});
// 2. If validation fails, return errors early.
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
message: 'Missing Fields. Failed to Create Invoice.',
};
}
// 3. Prepare data for insertion into the database
const { customerId, amount, status } = validatedFields.data;
const amountInCents = amount * 100;
const date = new Date().toISOString().split('T')[0];
// 4. Insert data into the database
try {
// await sql`...` your database query here
console.log('Successfully created invoice with validated data.');
} catch (error) {
return {
message: 'Database Error: Failed to Create Invoice.',
};
}
// 5. Revalidate and redirect
revalidatePath('/dashboard/invoices');
redirect('/dashboard/invoices');
}
লক্ষ্য করুন এটি কতটা পরিষ্কার। ভ্যালিডেশন লজিকটি ডিক্লারেটিভ এবং আমাদের স্কিমা ফাইলে একত্রিত করা হয়েছে। অ্যাকশনের দায়িত্ব এখন ভ্যালিডেশন, ডেটা প্রসেসিং এবং ডেটাবেস ইন্টারঅ্যাকশনকে সমন্বয় করা। Zod-এর `flatten().fieldErrors` মেথড একটি নিখুঁতভাবে স্ট্রাকচার্ড অবজেক্ট প্রদান করে যা ফিল্ডের নামগুলিকে ত্রুটি বার্তার একটি অ্যারের সাথে ম্যাপ করে, যা আমাদের ফর্ম কম্পোনেন্টের জন্য ঠিক যা প্রয়োজন।
সার্ভার অ্যাকশনের জন্য গুরুত্বপূর্ণ নিরাপত্তা সেরা অনুশীলন
একটি ভ্যালিডেশন লাইব্রেরি ব্যবহার করা একটি বিশাল পদক্ষেপ, কিন্তু সত্যিকারের নিরাপত্তার জন্য একটি বহু-স্তরীয় পদ্ধতির প্রয়োজন। প্রতিটি সার্ভার অ্যাকশন একটি সম্ভাব্য আক্রমণ ভেক্টর।
১. সর্বদা দূষিত উদ্দেশ্য অনুমান করুন
কখনোই, কখনোই ব্যবহারকারীর ইনপুটকে বিশ্বাস করবেন না। এটিই স্বর্ণ সূত্র। এমনকি যদি আপনার ক্লায়েন্ট-সাইড কোড নিখুঁত ডেটা পাঠায়, একজন আক্রমণকারী `curl` বা Postman-এর মতো টুল ব্যবহার করে সরাসরি আপনার সার্ভার অ্যাকশন এন্ডপয়েন্টে একটি হস্তনির্মিত অনুরোধ পাঠাতে পারে। আপনার সার্ভার-সাইড লজিকই আপনার একমাত্র নির্ভরযোগ্য প্রতিরক্ষা।
- সবকিছু ভ্যালিডেট করুন: শুধু ফর্ম্যাট নয়, বিজনেস লজিকও ভ্যালিডেট করুন। এই ব্যবহারকারী কি *এই* গ্রাহকের জন্য একটি ইনভয়েস তৈরি করার অনুমতিপ্রাপ্ত? পরিমাণটি কি একটি যুক্তিসঙ্গত পরিসরের মধ্যে আছে?
- আউটপুটের জন্য স্যানিটাইজ করুন: যদিও রিঅ্যাক্ট রেন্ডার করার সময় XSS প্রতিরোধ করতে স্বয়ংক্রিয়ভাবে ডেটা এস্কেপ করে, যদি আপনি অন্যান্য প্রসঙ্গে ডেটা ব্যবহার করেন (যেমন, ইমেল তৈরি করা, লগিং), তবে সেই সিস্টেমগুলিতে স্ক্রিপ্ট ইনজেকশন প্রতিরোধ করতে এটি স্যানিটাইজ করার বিষয়ে সচেতন থাকুন।
২. প্রমাণীকরণ এবং অনুমোদন বাধ্যতামূলক
প্রতিটি সার্ভার অ্যাকশন যা একটি মিউটেশন (তৈরি, আপডেট, ডিলিট) সম্পাদন করে বা সুরক্ষিত ডেটা অ্যাক্সেস করে, তাকে অবশ্যই ব্যবহারকারীর পরিচয় এবং অনুমতি যাচাই করতে হবে।
সর্বদা আপনার অ্যাকশন একটি সেশন চেক দিয়ে শুরু করুন:
// app/actions.js
'use server';
import { auth } from '@/auth'; // Assuming you use NextAuth.js or similar
import { sql } from '@vercel/postgres';
export async function deleteInvoice(id) {
const session = await auth();
if (!session?.user) {
// Or throw an error
return { message: 'Authentication required.' };
}
// Authorization check: Does this user have permission to delete?
// This could involve checking roles or ownership.
const userRole = session.user.role;
if (userRole !== 'admin') {
return { message: 'Unauthorized action.' };
}
try {
await sql`DELETE FROM invoices WHERE id = ${id}`;
revalidatePath('/dashboard/invoices');
return { message: 'Deleted Invoice.' };
} catch (error) {
return { message: 'Database Error: Failed to Delete Invoice.' };
}
}
৩. CSRF (ক্রস-সাইট রিকোয়েস্ট ফোরজারি) এর বিরুদ্ধে সুরক্ষা
CSRF আক্রমণ একজন লগ-ইন করা ব্যবহারকারীকে অজান্তেই আপনার অ্যাপ্লিকেশনে একটি দূষিত অনুরোধ জমা দেওয়ার জন্য প্রতারিত করে। সৌভাগ্যবশত, Next.js-এর মতো ফ্রেমওয়ার্কগুলিতে সার্ভার অ্যাকশনের জন্য বিল্ট-ইন CSRF সুরক্ষা রয়েছে, যা ডাবল-সাবমিট কুকি এবং অরিজিন চেকের মতো বিভিন্ন কৌশল ব্যবহার করে। যদিও এটি আপনার জন্য হ্যান্ডেল করা হয়, তবে এটি সম্পর্কে সচেতন থাকা এবং আপনার সেটআপ ভুলভাবে কনফিগার করে অসাবধানতাবশত দুর্বলতা তৈরি না করা নিশ্চিত করা অত্যন্ত গুরুত্বপূর্ণ।
৪. রেট লিমিটিং প্রয়োগ করুন
একজন দূষিত ব্যবহারকারী বা বট আপনার ফর্ম সাবমিশনে স্প্যাম করতে পারে, একটি লগইন ব্রুট-ফোর্স করার চেষ্টা করতে পারে, আপনার ডেটাবেস সংযোগ শেষ করে দিতে পারে, বা আপনার সিস্টেমকে জাঙ্ক ডেটা দিয়ে ভরে ফেলতে পারে। গুরুত্বপূর্ণ সার্ভার অ্যাকশনগুলিতে রেট লিমিটিং প্রয়োগ করা অপরিহার্য।
আপনি Upstash Rate Limiting-এর মতো পরিষেবা ব্যবহার করতে পারেন অথবা Redis-এর মতো একটি কী-ভ্যালু স্টোর ব্যবহার করে আপনার নিজস্ব লজিক প্রয়োগ করতে পারেন। কী সাধারণত ব্যবহারকারীর আইপি ঠিকানা বা ব্যবহারকারী আইডি হয়।
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
// Create a new ratelimiter, allowing 5 requests per 10 seconds
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(5, '10 s'),
});
export async function sensitiveAction(formData) {
const ip = headers().get('x-forwarded-for'); // Or get user ID from session
const { success } = await ratelimit.limit(ip);
if (!success) {
return { message: 'Too many requests. Please try again later.' };
}
// ... rest of the action logic
}
বৈশ্বিক এবং অ্যাক্সেসিবিলিটি বিবেচনা
একটি বিশ্বব্যাপী দর্শকদের জন্য তৈরি করার জন্য কেবল প্রযুক্তিগত বাস্তবায়নের বাইরেও চিন্তা করা প্রয়োজন।
ত্রুটি বার্তাগুলির আন্তর্জাতিকীকরণ (i18n)
একটি বিশ্বব্যাপী অ্যাপ্লিকেশনের জন্য ইংরেজিতে ত্রুটি বার্তা হার্ডকোড করা আদর্শ নয়। একটি ভালো পদ্ধতি হল আপনার সার্ভার অ্যাকশনকে ত্রুটি *কোড* বা *কী* রিটার্ন করানো।
// In the action
if (!validatedFields.success) {
// ...
return { errors: { customerId: ['error.customer.required'] } };
}
// In the client component, using a library like 'react-i18next'
import { useTranslation } from 'react-i18next';
function MyForm() {
const { t } = useTranslation();
const [state, dispatch] = useFormState(...);
// ...
{state.errors?.customerId &&
<p>{t(state.errors.customerId[0])}</p>}
}
এটি আপনার ভ্যালিডেশন লজিককে আপনার প্রেজেন্টেশন থেকে আলাদা করে এবং আপনার ফ্রন্ট-এন্ডকে অনুবাদগুলি মসৃণভাবে পরিচালনা করতে দেয়।
অ্যাক্সেসিবিলিটি (a11y)
ত্রুটি প্রদর্শন করার সময়, নিশ্চিত করুন যে সেগুলি সহায়ক প্রযুক্তির ব্যবহারকারীদের জন্য অ্যাক্সেসযোগ্য। `aria-describedby` অ্যাট্রিবিউট ব্যবহার করে ত্রুটি বার্তাগুলিকে তাদের সংশ্লিষ্ট ফর্ম ইনপুটগুলির সাথে সংযুক্ত করুন।
<div>
<label htmlFor="customerName">Customer Name</label>
<input
id="customerName"
name="customerName"
type="text"
aria-describedby="customer-error"
/>
<div id="customer-error" aria-live="polite" role="alert">
{state.errors?.customerName &&
state.errors.customerName.map((error) => (
<p key={error}>{error}</p>
))}
</div>
</div>
`aria-live="polite"` অ্যাট্রিবিউটটি নিশ্চিত করবে যে স্ক্রিন রিডাররা যখন ত্রুটি বার্তাটি প্রদর্শিত হবে তখন তা ঘোষণা করবে।
উপসংহার: দায়িত্বের একটি নতুন যুগ
রিঅ্যাক্ট সার্ভার অ্যাকশন একটি শক্তিশালী টুল যা ফুল-স্ট্যাক ডেভেলপমেন্টকে সহজ করে, সার্ভার লজিককে UI-এর আগের চেয়ে অনেক কাছাকাছি নিয়ে আসে। তবে, এই শক্তির জন্য একটি শৃঙ্খলাবদ্ধ এবং নিরাপত্তা-প্রথম মানসিকতা প্রয়োজন।
`useFormState` এবং `useFormStatus`-এর মতো হুক ব্যবহার করে, আমরা একটি চমৎকার ব্যবহারকারী অভিজ্ঞতার সাথে প্রগতিশীলভাবে উন্নত ফর্ম তৈরি করতে পারি। Zod-এর মতো শক্তিশালী স্কিমা ভ্যালিডেশন লাইব্রেরিগুলিকে একীভূত করে, আমরা পরিষ্কার, রক্ষণাবেক্ষণযোগ্য এবং টাইপ-সেফ ভ্যালিডেশন লজিক লিখতে পারি। এবং মৌলিক নিরাপত্তা নীতিগুলি—প্রমাণীকরণ, অনুমোদন, রেট লিমিটিং এবং সর্বদা সার্ভারে ভ্যালিডেট করা—মেনে চলার মাধ্যমে, আমরা এমন অ্যাপ্লিকেশন তৈরি করতে পারি যা কেবল মার্জিত এবং দক্ষই নয়, স্থিতিশীল এবং সুরক্ষিতও।
প্রতিটি সার্ভার অ্যাকশনকে একটি পাবলিক এপিআই এন্ডপয়েন্ট হিসাবে বিবেচনা করুন। এর ইনপুটগুলি ভ্যালিডেট করুন, এর অনুমতিগুলি পরীক্ষা করুন এবং এর ত্রুটিগুলি সুন্দরভাবে পরিচালনা করুন। এটি করার মাধ্যমে, আপনি রিঅ্যাক্ট ডেভেলপমেন্টের এই উত্তেজনাপূর্ণ নতুন অধ্যায়ের সম্পূর্ণ সম্ভাবনাকে আত্মবিশ্বাসের সাথে কাজে লাগাতে পারেন।