SvelteKit হলো Svelte-এর জন্য আপকামিং ফুল-স্ট্যাক অ্যাপ্লিকেশন ফ্রেমওয়ার্ক, যেটি কম্পাইল টাইমে আপনার অ্যাপ বিল্ড করে ছোট, দ্রুত জাভাস্ক্রিপ্ট প্রোডিউস করে। স্ভেল্টকিট আপনাকে সার্ভার-সাইড লজিক লিখতে দেয় এন্ডপয়েন্ট ব্যবহার করে, কিন্তু আপনার অ্যাপ্লিকেশনের ডেটা কীভাবে পারসিস্ট করবেন সেটি আপনার ওপর নির্ভর।
এই পোস্টে, আমি দেখাব কীভাবে একটি SvelteKit অ্যাপ্লিকেশনে রেডিস ব্যবহার করে ডেটা স্টোর করতে হয়। আমরা রেডিস ব্যবহার করব মুভি এপিআই রেসপন্স ক্যাশ করতে এবং একটি র্যান্ডম মুভি ডিসপ্লে করতে, The Movie Database (TMDB) API-র ডেটা ব্যবহার করে।
আপনার একটা রেডিস কানেকশন স্ট্রিং থাকতে হবে বা লোকালি রেডিস রান করতে হবে এই ডেমো ফলো করার জন্য। যদি আপনার ইতিমধ্যে রেডিস ইনস্ট্যান্স না থাকে, আমি আপস্ট্যাশ-কে রিকমেন্ড করি। স্ভেল্টকিটের মত, এটি সার্ভারলেস অ্যাপ্লিকেশনের জন্য অপ্টিমাইজড এবং ফ্রি যদি আপনি বেশি রিকোয়েস্ট না করেন (লিখতে 당시 ১০k/দিন ক্যাপ)। আপনার রেডিস ইনস্ট্যান্স কোথা থেকে পেলে যায়, নিশ্চিত করুন এটি আপনার অ্যাপ্লিকেশন ডিপ্লয় করা লোকেশনের কাছে অবস্থিত লেটেন্সি কমাতে।
পূর্বশর্ত
- SvelteKit-এর সাথে বেসিক পরিচয় (যেমন পেজ বনাম এন্ডপয়েন্ট, লোডিং, দिये गये পেজের জন্য কোয়েরি এবং প্যারামস রিট্রিভ করা)
- একটি TMDB API key এবং রেডিস ইনস্ট্যান্স (যেমন আপস্ট্যাশ-এ) যদি আপনি ডেমো রান বা ডিপ্লয় করতে চান
স্টার্টার ওভারভিউ
স্টার্টার রিপো ক্লোন করুন। main ব্রাঞ্চে এন্ড রেজাল্ট আছে, তাই initial ব্রাঞ্চ চেকআউট করুন এই পোস্ট ফলো করার জন্য। যদি আপনি আপনার চেঞ্জ পুশ করতে চান, প্রথমে রিপো ফর্ক করুন।
git clone https://github.com/geoffrich/movie-search-redis.git
cd movie-search-redis
git checkout initial
এটি একটি ছোট SvelteKit অ্যাপ্লিকেশন যা মুভি সার্চ করতে এবং তাদের ডিটেইলস দেখতে দেয় TMDB API ব্যবহার করে। এটি টাইপস্ক্রিপ্ট ব্যবহার করে API রেসপন্সের সাথে ইন্টারঅ্যাক্ট করার জন্য সহজ করতে, কিন্তু এটি রেডিস বা SvelteKit ব্যবহার করার জন্য আবশ্যক নয়। নিচের রুটগুলো ইতিমধ্যে আছে (সহসংবিধ src/routes-এ ফাইলগুলোর সাথে):
/হোম পেজ রেন্ডার করে/searchসার্চ রেজাল্টের লিস্ট রেন্ডার করে। এটিqueryএবংpageকোয়েরি প্যারামিটার হিসেবে নেয়, যেমন?query=star wars&page=3"Star Wars" এর জন্য থার্ড পেজের রেজাল্ট দেখাবে/search.jsonহলো একটি সার্ভার এন্ডপয়েন্ট যা TMDB API কোয়েরি করে এবং রেজাল্টের লিস্ট রিটার্ন করে। এটি/search-এর মতোই কোয়েরি প্যারামিটার নেয়/movie/[id]দেয়া ID-র মুভির ডিটেইলস রেন্ডার করে, যেমন/movie/11/movie/[id].jsonহলো একটি সার্ভার এন্ডপয়েন্ট যা TMDB API থেকে মুভি ডিটেইলস রিটার্ন করে। যেহেতু TMDB রেসপন্স আমরা পেজে যা ব্যবহার করি তার চেয়ে বেশি ডেটা রিটার্ন করে, আমরা রেসপন্সকে অ্যাডাপ্ট করি সাবসেট ডেটা রিটার্ন করতে।
আপনি ডেমোটি Netlify-এ চলমান দেখতে পারেন।
নোট করুন TMDB API কলগুলো শুধু সার্ভার এন্ডপয়েন্টগুলোতে হয়। এটি আমাদের API কী ক্লায়েন্টে এক্সপোজ না হওয়ার জন্য।
লোকালি ডেমো রান করতে, প্রজেক্টের রুটে একটি .env ফাইল তৈরি করুন এবং আপনার TMDB API key এবং রেডিস কানেকশন স্ট্রিং যোগ করুন (নিচে স্যাম্পল)। তারপর npm install রান করুন ডিপেন্ডেন্সি ইনস্টল করতে এবং npm run dev রান করুন অ্যাপ চালাতে।
TMDB_API_KEY=KEY_GOES_HERE
REDIS_CONNECTION=CONNECTION_GOES_HERE
রানটাইমে, আমরা এই ভ্যালুগুলো অ্যাক্সেস করতে পারি process.env['TMDB_API_KEY'] বা process.env['REDIS_CONNECTION'] ব্যবহার করে। ভ্যালুগুলো .env ফাইল থেকে পড়া হয় dotenv ব্যবহার করে hooks.ts-এ।
রেডিসে এপিআই রেসপন্স ক্যাশ করা
এক্সিস্টিং প্রজেক্টে আমরা একটি উন্নতি আনতে পারি একটি ইনডিভিজুয়াল মুভির জন্য এপিআই রেসপন্স রেডিসে ক্যাশ করে। বর্তমানে, অ্যাপটি প্রতিবার মুভি পেজ লোড হলে TMDB API-এ রিকোয়েস্ট করে। যখন আমরা প্রথম রিকোয়েস্ট করি, আমরা এপিআই রেসপন্স রেডিসে স্টোর করতে পারি যাতে পরবর্তীতে আমাদের বারবার TMDB- থেকে ডেটা রিকোয়েস্ট করতে না হয়।
যদিও TMDB API দ্রুত এবং আবশ্যকি ক্যাশ করার প্রয়োজন নেই, এই টেকনিক APIs যেগুলো রেসপন্ড করতে সময় নেয় বা যেগুলোর রিকোয়েস্ট লিমিট আছে সেগুলোর জন্য উপযোগী হতে পারে। এটিও একটি লেভেল অফ রেজিলিয়েন্স প্রদান করে যদি API ডাউন চলে যায়।
আমরা ইতিমধ্যে ioredis ডিপেন্ডেন্সি হিসেবে যোগ করেছি। এটি Node-এর জন্য একটি রেডিস ক্লায়েন্ট যা আমাদের রেডিস ডেটাবেসের সাথে ইন্টারঅ্যাক্ট করতে সাহায্য করবে।
src/lib/redis.ts ফাইল তৈরি করুন। এই ফাইলটি রেডিস ক্লায়েন্ট ইনিশিয়ালাইজ করে এবং এক্সপোর্ট করে অন্য ফাংশনগুলো ব্যবহার করার জন্য। এটিও কিছু হেল্পার ফাংশন ধারণ করে কি পাওয়ার জন্য। নিচের কোড ফাইলে যোগ করুন।
import Redis from "ioredis";
const connectionString = process.env["REDIS_CONNECTION"];
export const MOVIE_IDS_KEY = "movie_ids";
/** Return the key used to store movie details for a given ID in Redis */
export function getMovieKey(id): string {
return `movie:${id}`;
}
export default connectionString
? new Redis(connectionString)
: new Redis();
নোট করুন এই ইমপ্লিমেন্টেশন সার্ভারলেস ইনস্ট্যান্স প্রতি একটি সিঙ্গল রেডিস ক্লায়েন্ট তৈরি করে। আরেকটি অপশন হলো প্রতিটি রিকোয়েস্টের জন্য একটি রেডিস কানেকশন তৈরি এবং ক্লোজ করা। আপস্ট্যাশের একটি রেস্ট এপিআইও আছে যেটি আপনাকে রেডিস কানেকশন ইনিশিয়ালাইজ করার প্রয়োজন নেই। সেরা অপশন আপনার অ্যাপের লেটেন্সি এবং মেমোরি রিকোয়ারমেন্টের ওপর নির্ভর করে।
/src/routes/movie/[id].json.ts ফাইলে যান। রেডিস ক্লায়েন্ট এবং getMovieKey ফাংশন ইমপোর্ট করুন redis.ts- থেকে।
import redis, { getMovieKey } from "$lib/redis";
getMovieDetailsFromApi ফাংশনটি দেখুন। এটি TMDB API কল করে মুভি ডিটেইলস এবং ক্রেডিটস পেতে এবং রিটার্ন করে। ডেটা রিটার্ন করার আগে, আমরা এটিকে আমাদের রেডিস ক্যাশে স্টোর করতে চাই যাতে পরবর্তীতে ক্যাশড ভার্সন রিট্রিভ করতে পারি এপিআই কল না করে। একটি নতুন cacheMovieResponse ফাংশন ফাইলে যোগ করুন রেডিসে ডেটা ক্যাশ করার জন্য।
async function cacheMovieResponse(id: number, movie, credits) {
try {
const cache: MovieDetails = {
movie,
credits,
};
// store movie response for 24 hours
await redis.set(getMovieKey(id), JSON.stringify(cache), "EX", 24 * 60 * 60);
} catch (e) {
console.log("Unable to cache", id, e);
}
}
প্রতিটি মুভি ID আলাদা কি-তে স্টোর হবে। উদাহরণস্বরূপ, যদি আমরা ID ১১-র মুভির ইনফরমেশন রিট্রিভ করি, কিটি হবে movie:11। redis.set-এর লাস্ট দুটি আর্গুমেন্ট বলে যে আমাদের ডেটা শুধু ২৪ ঘন্টা (৮৬৪০০ সেকেন্ড) ক্যাশ করা উচিত। আমরা ডেটা চিরকাল ক্যাশ করা উচিত না - এটি টার্মস অফ ইউজ লঙ্ঘন করে, এবং ডেটা একটি সময়ের পর পুরনো হয়ে যাবে।
নোট করুন যে জেএস অবজেক্টকে স্ট্রিংগিফাই করতে হবে ক্যাশে স্টোর করার আগে। আমরা এই অপারেশনের время থ্রো হওয়া যেকোনো এক্সেপশন ক্যাচ করি আমাদের এন্ডপয়েন্টকে আরও রেজিলিয়েন্ট बनाने জন্য। যদি আমরা ডেটা ক্যাশ না করতে পারি, আমাদের এপিআই থেকে ডেটা রিটার্ন করা উচিত অনহ্যান্ডলড এক্সেপশন থ্রো করার বদলে।
এখন আমরা নতুন cacheMovieResponse ফাংশনটি getMovieDetailsFromApi-এর ভেতরে ব্যবহার করতে পারি এপিআই রেসপন্স ক্যাশে স্টোর করতে।
async function getMovieDetailsFromApi(id: number) {
const [movieResponse, creditsResponse] = await Promise.all([
getMovieDetails(id),
getCredits(id),
]);
if (movieResponse.ok) {
const movie = await movieResponse.json();
const credits = await creditsResponse.json();
// add this line
await cacheMovieResponse(id, movie, credits);
return {
movie,
credits,
};
}
return {
status: movieResponse.status,
};
}
এখন আমরা ক্যাশে ডেটা স্টোর করেছি, কিন্তু আমাদের এখনো ক্যাশড ডেটা রিট্রিভ করতে হবে। চলুন একটি ফাংশন যোগ করি মুভি ডিটেইলস ক্যাশ থেকে পড়তে।
async function getMovieDetailsFromCache(
id: number
): Promise> {
try {
const cached: string = await redis.get(getMovieKey(id));
if (cached) {
const parsed: MovieDetails = JSON.parse(cached);
console.log(`Found ${id} in cache`);
return parsed;
}
} catch (e) {
console.log("Unable to retrieve from cache", id, e);
}
return {};
}
কারণ ডেটা একটি স্ট্রিং হিসেবে স্টোর করা হয়েছে, আমাদের এটিকে অবজেক্টে পার্স করতে হবে ব্যবহারযোগ্য बनाने জন্য। আগের ফাংশনের মত, আমরা যেকোনো এক্সেপশন লগ করি, কিন্তু সেগুলো ক্যাশিং ফাংশন থেকে বের হয়ে যাওয়া দেয় না। আমরা সবসময় এপিআই থেকে ডেটা রিট্রিভ করতে পারি ফলব্যাক হিসেবে।
শেষে, আমরা আমাদের ক্যাশিং ফাংশনটি মেইন রিকোয়েস্ট হ্যান্ডলারে কল করতে পারি। যদি আমরা ক্যাশে ডেটা পাই, আমরা সেটি সাথে সাথে রিটার্ন করি; অন্যথায়, আমরা আগের মত এপিআই থেকে পড়ি।
export const get: RequestHandler = async function ({ params }) {
const { id: rawId } = params;
// validate and sanitize the input
const id = parseInt(rawId);
if (isNaN(id)) {
return {
status: 400,
};
}
// add these lines
const { movie, credits } = await getMovieDetailsFromCache(id);
if (movie && credits) {
return {
body: adaptResponse(movie, credits),
};
}
// fallback to the API
const result = await getMovieDetailsFromApi(id);
এই এন্ডপয়েন্টের ফাইনাল কোড ডেমো রিপো-তে দেখতে পারেন।
এই চেঞ্জগুলোর সাথে, একটি মুভির পেজে নেভিগেট করার চেষ্টা করুন। পেজ রিফ্রেশ করলে, আপনাকে কনসোল লоге "Found id in cache" দেখতে मिलবে, ইন্ডিকেট করে যে আমরা সফলভাবে সেই মুভি স্টোর এবং রিট্রিভ করেছি ক্যাশ থেকে।
একটি র্যান্ডম মুভি রিট্রিভ করা
রেডিস শুধু এপিআই রেসপন্স ক্যাশ করার বাইরে আরও অনেক কিছু করতে পারে। চলুন দেখি কীভাবে আমরা একটি রুট বানাতে পারি যেটি ইউজারকে একটি র্যান্ডম মুভিতে রিডাইরেক্ট করবে।
এটি ততটা সিম্পল নয় যে ১ থেকে ৩০০০০০ এর মাঝপথে একটি র্যান্ডম নম্বর পিক করে সেটি মুভি আইডি হিসেবে ব্যবহার করা। সেই রেঞ্জের প্রতিটি নম্বর একটি মুভির সাথে কেরেসপন্ড করে না - উদাহরণস্বরূপ, ১ বা ১০০০ আইডির কোনো মুভি নেই। ম্যাক্সিমাম পসিবল আইডি কী তা ট্র্যাক রাখাও কঠিন, যেহেতু সেটি সবসময় চেঞ্জ হয় নতুন মুভি যোগ হওয়ার সাথে সাথে। বদলে, আমরা র্যান্ডম মুভি সিলেক্ট করব একটি টু-স্টেপ প্রসেস দিয়ে:
- যখন একটি সার্চ কোয়েরি পারফর্ম করা হয়, রিটার্ন হওয়া সকল আইডি একটি রেডিস সেট-এ রাখা হয়।
- যখন
/movie/randomরুট রিকোয়েস্ট করা হয়, সেই সেট থেকে একটি র্যান্ডম মেম্বার রিট্রিভ করা হয় এবং কেরেসপন্ডিং মুভি ডিটেইল পেজে রিডাইরেক্ট করা হয়।
পসিবল র্যান্ডম মুভিগুলো শুরুতে ছোট হবে, কিন্তু বড় হবে যত বেশি সার্চ পারফর্ম করা হবে।
/src/routes/search.json.ts আপডেট করুন নিচের মতো।
import type { RequestHandler } from "@sveltejs/kit";
import type { SearchResponse } from "$lib/types/tmdb";
import redis, { MOVIE_IDS_KEY } from "$lib/redis";
const VOTE_THRESHOLD = 20;
export const get: RequestHandler = async function ({ query }) {
const searchQuery = query.get("query");
const page = query.get("page") ?? 1;
const response = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key=${process.env["TMDB_API_KEY"]}&page=${page}&include_adult=false&query=${searchQuery}`
);
const parsed: SearchResponse = await response.json();
// add these lines
const filteredMovies = parsed.results.filter(
(movie) => movie.vote_count >= VOTE_THRESHOLD
);
if (filteredMovies.length > 0) {
try {
await redis.sadd(MOVIE_IDS_KEY, ...filteredMovies.map((r) => r.id));
} catch (e) {
console.log(e);
}
}
return {
body: parsed,
};
};
নোট করুন আমরা প্রতিটি মুভি সেটে যোগ করছি না। আমি সেগুলো ফিল্টার আউট করেছি যাদের বেশি ভোট নেই, যেহেতু সেগুলো কম সাইজে ইউজারদের দ্বারা ভেটেড হয়। আপনি VOTE_THRESHOLD আপনার পছন্দমতো অ্যাডজাস্ট করতে পারেন।
এই চেঞ্জের সাথে, মুভি সার্চ করলে মুভি আইডিগুলোর সেট পপুলেট হওয়া শুরু হবে। কিছু সার্চ পারফর্ম করুন আইডি সেটে যোগ করতে। উদাহরণস্বরূপ:
- Star Wars
- Lion King
- Spider Man
কয়েকটি সার্চ পারফর্ম করার পর, আপনার রেডিসে র্যান্ডম আইডিগুলোর একটি সেট থাকা উচিত। চলুন /movie/random এন্ডপয়েন্ট এবং পেজের জন্য রুট তৈরি করি।
src/routes/movie/random.json.ts
import type { RequestHandler } from "@sveltejs/kit";
import redis, { MOVIE_IDS_KEY } from "$lib/redis";
export const get: RequestHandler = async function () {
const randomId = await redis.srandmember(MOVIE_IDS_KEY);
return {
body: randomId,
};
};
এই সার্ভার এন্ডপয়েন্ট SRANDMEMBER ব্যবহার করে মুভি আইডি সেট থেকে একটি র্যান্ডম আইডি পিক করে।
src/routes/movie/random.svelte
কেরেসপন্ডিং Svelte পেজকে শুধু একটি লোড ফাংশন দরকার যেহেতু এটি কোনো UI রেন্ডার করে না। এটি আমরা যা এন্ডপয়েন্ট যোগ করলাম সেটি কল করে এবং কেরেসপন্ডিং মুভি পেজে রিডাইরেক্ট করে।
এটাই সব! এখন, https://localhost:3000/movie/random নেভিগেট করুন। আপনাকে আপনার পূর্বের সার্চগুলো থেকে একটি র্যান্ডম মুভিতে অটোমেটিক্যালি রিডাইরেক্ট করা হবে। এই রুট সহজ অ্যাক্সেসের জন্য, আপনি /src/routes/__layout.svelte-এ নেভিগেশনেও যোগ করতে পারেন।
এটি লাইভ ডেমো-তে কাজ করছে দেখতে পারেন।
শেষ করার আগে
রেডিস ব্যবহার করার আরও অনেক উপায় আছে, কিন্তু আশা করি এই পোস্ট আপনাকে SvelteKit অ্যাপে রেডিস ইন্টিগ্রেট করার বেসিক বুঝতে সাহায্য করল। ফাইনাল কোড GitHub-এ এবং লাইভ ডেমো Netlify-এ দেখতে পারেন।
কোনো প্রশ্ন আছে? টুইটার-এ রিচ আউট করুন। আপনি আমার স্ভেল্ট সম্পর্কিত অন্যান্য লেখা আমার ব্লগ-এ পাবেন।