স্কেলেবিলিটি (scalability) ধরে ফুল-স্ট্যাক অ্যাপ্লিকেশন বিল্ড করা ভয়াবহ হতে পারে, বিশেষ করে যখন লেটেস্ট ভার্সন Vue এবং Vuex ব্যবহার করছেন যেগুলো ফুল টাইপস্ক্রিপ্ট (TypeScript) সাপোর্ট দেয়। এই আর্টিকেল পাঠকদের সিদ্ধান্ত দেবে স্কেলেবল ফুল-স্ট্যাক অ্যাপ্লিকেশন বিল্ড করার সমস্ত জ্ঞান—স্টেট ম্যানেজমেন্ট Vuex 4.0 দিয়ে, API রিকোয়েস্ট এবং ডেটাবেস ইন্টারঅ্যাকশন হ্যান্ডল করে, একটি CRUD অ্যাপ্লিকেশন এক্সপ্লোর করে যা অসুস্থ পশুর চিকিৎসা প্রেসক্রিপশন ম্যানেজ করে। ব্যাকএন্ড রেইলস দিয়ে বিল্ড হবে, যা বেসিক CRUD API এক্সপোজ করবে ফ্রন্টএন্ড ইন্টিগ্রেশনের জন্য।
বেশির ভাগ কোম্পানি API-বেসড ডেভেলপমেন্ট আর্কিটেকচার अपनाয় করেছে কারণ এটা ফ্রন্টএন্ড ডেভেলপমেন্ট টিমকে বেশি ফ্লেক্সিবিলিটি দেয় উপযুক্ত ফ্রন্টএন্ড টুল বেছে নেওয়ার জন্য। যদিও এটা সেরা অপশন নাও হতে পারে, এটা একটি विश्वস্ত অপশন, এবং অনেক কোম্পানি এই ডেভেলপমেন্ট আর্কিটেকচার अपनाয় করেছে কারণ এটা টিমকে আরও ফ্লেক্সিবিলিটি দেয়।
প্রেরিকুইসাইট (Prerequisites)
শুরু করার আগে, নিচের গুলো থাকলে আপনি এই টিউটোরিয়ালে সাথে থাকতে পারবেন:
- Rails V6.x
- Node.js V10.x
- Ruby on Rails-এ পূর্বের কাজের জ্ঞান
- TypeScript-এ পূর্বের কাজের জ্ঞান
- Vue.js-এ পূর্বের কাজের জ্ঞান
আমরা কী বানাব (What we will build)

এই টিউটোরিয়ালে, আমরা একটি ফুল-স্ট্যাক CRUD অ্যাপ্লিকেশন বানাব যা অসুস্থ পশুর চিকিৎসা প্রেসক্রিপশন ম্যানেজ করে। ইউজাররা পশুর জন্য প্রেসক্রিপশন তৈরি, আপডেট, এবং ডিলিট করতে পারবে। CRUD API গুলো রেইলস দিয়ে বিল্ড হবে, যা CRUD API এক্সপোজ করবে ফ্রন্টএন্ড ইন্টিগ্রেশনের জন্য, যা Vue 3 দিয়ে বিল্ড হবে। এই স্ট্যাকগুলো আলাদা আলাদা পোর্টে ইনডিপেনডেন্ট অ্যাপ্লিকেশন হিসেবে রান করবে।
ফুল-স্ট্যাক অ্যাপের বেসিক আর্কিটেকচার
আমাদের ফুল-স্ট্যাক অ্যাপ্লিকেশন ক্লায়েন্ট এবং সার্ভার অ্যাপ্লিকেশন দুটো থেকে বने যেগুলো ইনডিপেনডেন্টলি রান করে, প্রতিটি ক্লায়েন্ট অ্যাপ্লিকেশনের কম্পোনেন্ট Vuex দিয়ে ইন্টারঅ্যাক্ট করে সার্ভার অ্যাপ্লিকেশন ইস্যু করা CRUD API গুলোর সাথে অ্যাপ্লিকেশন স্টেট সঠিকভাবে ম্যানেজ করার জন্য। ব্যাকএন্ড অ্যাপ্লিকেশন সকল প্রেসক্রিপশন ডেটা Sqlite 3 ডেটাবেসে স্টোর করে যখন FRONTEND অ্যাপ্লিকেশনের জন্য CRUD API এক্সপোজ করে।
ব্যাকএন্ড সার্ভিস সেটআপ
আপনার পছন্দের ফোল্ডারে, নিচের কমান্ড রান করে একটি রেইলস অ্যাপ তৈরি করুন:
rails new vet_clinic_api --api
এটা রেইলসকে বলবে এই প্রজেক্টটি একটি API হিসেবে তৈরি করতে, ফলে সব ফ্রন্টএন্ড ডিপেন্ডেন্সি (ভিউ ফাইল) রিমুভ হয়ে যাবে।
ডেটাবেস কনফিগারেশন
আমরা Sqlite 3 ব্যবহার করব, যা রেইলস অ্যাপ্লিকেশনের ডিফল্ট ডেটাবেস।
rails g scaffold prescriptions vet_prescription:text prescribed_by:text disease:text livestock:text completed:boolean
উপরোক্ত কমান্ড রান করলে, রেইলস আমাদের মাইগ্রেশন, টেস্ট, মডেল, কন্ট্রোলার, রাউটের জন্য একটি ইনিশিয়াল স্ট্রাকচার স্কাফোল্ড করবে:
rails db:migrate
এই কমান্ড আমাদের টেবিলগুলো ডেটাবেসে যোগ করবে।
ডেটাবেস সিড করা (Seeding the Database)
আমাদের ডেটাবেসকে কিছু প্রেসক্রিপশন ডেটা দিয়ে সিড করি। নিচের কোড স্নিপেট db/migrate/seed.rb-এ যোগ করুন
//db/migrate/seed.rb Prescription.destroy_all Prescription.create!([{ vet_prescription:"Achyranthes aspera", prescribed_by:"Dr Chucks", disease:"Rabbies", livestock:"goat", completed:false }, { vet_prescription:"Achyranthes aspera", prescribed_by:"Dr Rex", disease:"Rabbies", livestock:"Dog", completed:false }, { vet_prescription:"ethnovet", prescribed_by:"Dr Chucks", disease:"Pox", livestock:"Sheep", completed:false }]) p "Created #{Prescription.count} prescriptions"
এই ফাইলটি ডেটাবেস সিড করার জন্য ইনিশিয়াল ডেটা স্টোর করবে যাতে অ্যাপ স্টার্ট হলে, আমাদের কিছু প্রিডিফাইন্ড প্রেসক্রিপশন ডেটা থাকে।
নিচের কমান্ড রান করুন db/migrate/seed.rb-এ কোড এক্সিকিউট করতে, যা ডেটাবেসকে কিছু প্রিডিফাইন্ড প্রেসক্রিপশন ডেটা দিয়ে সিড করবে:
rails db:seed
এই কয়েকটি কমান্ড দিয়ে, আমরা রেইলস দিয়ে একটি ফাংশনাল CRUD API তৈরি করলাম। কতটা সহজ ছিল? (smiles)
CORS কনফিগারেশন
যেহেতু আমরা আমাদের CRUD API এক্সপোজ করব ফ্রন্টএন্ডে, এবং ফ্রন্টএন্ড ও ব্যাকএন্ড দুটো সার্ভার আলাদা PORT-এ রান করবে, আমাদের রেইলস ব্যাকএন্ডে একটি CORS কনফিগারেশন সেটআপ করতে হবে ডেটা শেয়ারিং অনুমোদন করার জন্য ফ্রন্টএন্ড এবং ব্যাকএন্ডের মাঝপথে।
প্রজেক্ট রুটে Gemfile লোকেট করুন এবং নিচের লাইনটি আনকমেন্ট করুন:
# gem 'rack-cors'
config/environments/initializers/cors.rb-এ নিচের কোড যোগ করুন:
//config/environments/initializers/cors.rb Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins '*' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end end
উপরোক্ত স্নিপেট রেইলস CRUD API কে যেকোনো PORT-এ রান হওয়া ফ্রন্টএন্ড অ্যাপ থেকে অ্যাক্সেসযোগ্য করে।
রেইলস সার্ভার শুরু করতে নিচের কমান্ড রান করুন:
rails s
localhost:3000/prescriptions এ নেভিগেট করুন সব প্রেসক্রিপশনের জন্য JSON রেসপন্স পেতে।
রেইলস বিবেচনা করুন যদি আপনি এমন ক্লায়েন্টদের সাথে কাজ করেন যারা ঘন ঘন প্রজেক্টের ওয়ার্কফ্লো পরিবর্তন করে। রেইলস দিয়ে কয়েকটি কমান্ড এবং লাইন কোড দিয়ে ফিচার ইমপ্লিমেন্ট করা সহজ। এটা আমার ব্যক্তিগত মতামত।
Vue পরিচিতি (Introducing Vue)
Vue হলো একটি প্রগ্রেসিভ ফ্রেমওয়ার্ক ইউজার ইন্টারফেস বিল্ড করার জন্য। Vue Virtual DOM, রেন্ডার ফাংশন, এবং সার্ভার-सাইড রেন্ডারিং ক্যাপাবিলিটি কনসেপ্ট আনে যাতে ওয়েব অ্যাপ্লিকেশনের জন্য সিগনিফিক্যান্ট পারফরম্যান্স অপ্টিমাইজেশন হয়।
Vue 3 ডেভেলপারদের জন্য অনেক নতুন ফিচার এবং পরিবর্তন নিয়ে এসেছে। এই ফিচারগুলো ফ্রেমওয়ার্কের সামগ্রিক স্থিতিশীলতা (stability), গতি (speed), এবং মেইনটেইনেবিলিটি (maintainability) উন্নত করার জন্য ডিজাইন করা হয়েছে।
আমরা কম্পোজিশন API (Composition API) ব্যবহার করব, যা Vue 3-র সবচেয়ে প্রত্যাশিত ফিচারগুলোর একটি। এটা Vue কম্পোনেন্ট তৈরি করার একটি নতুন পদ্ধতি, আরও organisé এবং ইফিসিয়েন্ট কোড লিখতে, এবং ফুল টাইপস্ক্রিপ্ট টাইপ-চেকিং সাপোর্টের সাথে।
Vuex পরিচিতি (Introducing Vuex)
Vuex হলো Vue টিম তৈরি করা একটি স্টেট ম্যানেজমেন্ট লাইব্রেরি, এবং এটা Redux-এর মতো ফ্লাক্স আর্কিটেকচার (flux architecture) বেসড। এটা স্পেসিফিক্যালি Vue-র জন্য ডিজাইন করা এবং আপনার স্টোর আরও ভালোভাবে অর্গানাইজ করতে দেয়। Vuex অত্যন্ত গুরুত্বপূর্ণ হয়ে যাবে যদি আপনার Vue অ্যাপ্লিকেশন স্টেট জটিল হয়ে ওঠে время সাথে। Vuex-এর লেটেস্ট স্টেবল রিলিজ, v4.0.0, Vue 3-এ معرفی করা কম্পোজিশন API সাপোর্ট করে, সেইসাথে টাইপস্ক্রিপ্টের জন্য আরও রবাস্ট ইনফারেন্স (robust inference)।
ফ্রন্টএন্ড অ্যাপ্লিকেশন সেটআপ
ফ্রন্টএন্ড Vue 3 এবং TypeScript-এ সেটআপ হবে, যখন Vuex অ্যাপ্লিকেশন স্টেট ম্যানেজমেন্টের জন্য ব্যবহার হবে।
Vue-CLI টুল ব্যবহার করে একটি Vue 3 অ্যাপ তৈরি করি টাইপস্ক্রিপ্ট এবং Vuex সাপোর্টসহ।
Vue-CLI টুল গ্লোবালি ইনস্টল করুন নিচের কমান্ড দিয়ে:
npm install --global @vue/cli
নিচের কমান্ড দিয়ে একটি নতুন Vue 3 অ্যাপ তৈরি করুন টাইপস্ক্রিপ্ট এবং Vuex সাপোর্টসহ:
vue create vet_clinic_frontend
ম্যানুয়ালি সিলেক্ট ফিচার অপশন বেছে নিন এবং স্পেস কী চাপায় নিচের অপশনগুলো সিলেক্ট করুন:
- Choose Vue version
- Babel
- TypeScript
- Linter / Formatter
পরবর্তী, Vue 3.x (Preview) বেছে নিন প্রজেক্টের ভার্সন হিসেবে।
- ইন্টার করুন ক্লাস-স্টাইল কম্পোনেন্ট সিনট্যাক্স ব্যবহার করার জন্য।
- ইন্টার করুন Babel টাইপস্ক্রিপ্টর সাথে ব্যবহার করার জন্য।
- আপনার পছন্দের যেকোনো লিন্টার সিলেক্ট করুন।
Vue 3 অ্যাপ সফলভাবে জেনারেট হলে, আমাদের Vuex (V4.x) এবং ফুল টাইপস্ক্রিপ্ট সাপোর্টসহ একটি Vue 3 প্রজেক্ট সেটআপ হবে।
আমরা টাইপ সেফটি অ্যাপ্লিকেশনে যোগ করব টাইপস্ক্রিপ্ট ব্যবহার করে। ডেভেলপমেন্ট সার্ভার শুরু করতে নিচের কমান্ড আপনার টার্মিনালে রান করুন এবং https://localhost:8080-এ যান ব্রাউজারে আপনার প্রজেক্ট প্রিভিউ করতে।
আমরা Bulma CSS ফ্রেমওয়ার্ক ব্যবহার করব আমাদের অ্যাপ্লিকেশন স্টাইল করার জন্য। Bulma CSS ইনস্টল করতে নিচের কমান্ড রান করুন:
npm install bulma
Bulma CSS ইম্পোর্ট করতে, App.vue-এ নিচের কোড যোগ করুন:
//App.vue <style lang="scss"> @import "~bulma/css/bulma.css"; </style>
অ্যাপ্লিকেশন স্টোর সেটআপ
অ্যাপ্লিকেশন স্টোর Vuex-এ সেটআপ হবে। স্টোরে পরিবর্তন আনার জন্য, অ্যাকশন ডিসপ্যাচ হবে কম্পোনেন্ট থেকে, যা মিউটেশন ট্রিগার করবে, फलস্বরূপ স্টোর আপডেট হবে।
অ্যাপ্লিকেশন স্টোর সেটআপ করার জন্য নিচের ধাপগুলো নিন:
- স্টেট অবজেক্ট তৈরি করুন।
- আমাদের অ্যাপ্লিকেশনে যে মিউটেশনগুলো হবে সেটআপ করুন।
- এই পরপর মিউটেশনে কমিট করার অ্যাকশন তৈরি করুন।
- কম্পোনেন্টের জন্য গেটার (getters) তৈরি করুন যাতে সেটা সরাসরি স্টেট ডেটা কম্পিউট করতে পারে।
State
স্টেট হলো একটি স্টোর অবজেক্ট যেটা অ্যাপ্লিকেশন-লেভেল ডেটা ধরে যেটা সব কম্পোনেন্টকে অ্যাক্সেস করতে হবে।
state.ts ফাইল তৈরি করুন store ডিরেক্টরিতে নিচের কোড স্নিপেটসহ:
//src/store/state.ts export type Prescription = { id: number; vet_prescription: string; prescribed_by: string; disease: string; livestock: string; completed: boolean; editing: boolean; }; export type Data = { vet_prescription: string; prescribed_by: string; disease: string; livestock: string; }; export type State = { loading: boolean; prescriptions: Prescription[]; data: Data | null; showCreateModal: boolean; showEditModal: boolean; showPrescriptionModal: boolean; editModalPrescriptionId: number | undefined; showPrescriptionId: number | undefined; }; export const state: State = { loading: false, prescriptions: [], data: null, showCreateModal: false, showEditModal: false, showPrescriptionModal: false, editModalPrescriptionId: undefined, showPrescriptionId: undefined, };
এখানে, আমরা Prescription এবং Data-এ কিছু টাইপ সেফটি যোগ করলাম। আমরা টাইপসহ এক্সপোর্ট করলাম কারণ সেগুলো গেটার, মিউটেশন, এবং অ্যাকশনের ডেফিনিশনে ব্যবহার হবে। अन्तত, আমরা State টাইপকে state-এ কাস্ট করলাম।
Mutations
মিউটেশন হলো মেথডগুলো যা ট্রিগার হলে স্টোর মডিফাই করে। সেগুলো স্টেট ফার্স্ট আর্গুমেন্ট হিসেবে পায় এবং পেলোড সেকেন্ড আর্গুমেন্ট হিসেবে, শেষে অ্যাপ্লিকেশন স্টেটকে পেলোড দিয়ে মডিফাই করে। মিউটেশন তৈরি করার জন্য, Vuex ডকস রিকমেন্ড করে কনস্ট্যান্ট ব্যবহার করা মিউটেশন টাইপের জন্য।
mutations.ts ফাইল তৈরি করুন store ডিরেক্টরিতে নিচের কোড স্নিপেটসহ:
//src/store/mutations.ts import { MutationTree } from "vuex"; import { State, Prescription, Data } from "./state"; export enum MutationType { CreatePrescription = "CREATE_PRESCRIPTION", SetPrescriptions = "SET_PRESCRIPTIONS", CompletePrescription = "COMPLETE_PRESCRIPTION", RemovePrescription = "REMOVE_PRESCRIPTION", EditPrescription = "EDIT_PRESCRIPTION", UpdatePrescription = `UPDATE_PRESCRIPTION`, SetLoading = "SET_LOADING", SetCreateModal = "SET_CREATE_MODAL", SetEditModal = "SET_EDIT_MODAL", SetPrescriptionModal = "SET_PRESCRIPTION_MODAL", }
উপরোক্ত স্নিপেট enum সিম্বল ব্যবহার করে আমাদের অ্যাপের সব সম্ভাব্য মিউটেশন নাম ধরে রাখে।
পরবর্তী, আমরা প্রতিটি MutationType-এর জন্য একটি কন্ট্রাক্ট (টাইপ) ডিক্লেয়ার করব, নিচের মতো:
//src/store/mutation.ts export type Mutations = { [MutationType.CreatePrescription](state: State, prescription: Data): void; [MutationType.SetPrescriptions](state: State, prescription: Prescription[]): void; [MutationType.CompletePrescription](state: State, prescription: Partial<Prescription> & { id: number }): void; [MutationType.RemovePrescription](state: State, prescription: Partial<Prescription> & { id: number }): void; [MutationType.EditPrescription](state: State, prescription: Partial<Prescription> & { id: number }): void; [MutationType.UpdatePrescription](state: State, prescription: Partial<Prescription> & { id: number }): void; [MutationType.SetLoading](state: State, value: boolean): void; [MutationType.SetCreateModal](state: State, value: boolean): void; [MutationType.SetEditModal](state: State, value: { showModal: boolean; prescriptionId: number | undefined }): void; [MutationType.SetPrescriptionModal](state: State, value: { showModal: boolean; prescriptionId: number | undefined }): void; };
তারপর, আমরা প্রতিটি MutationType-এর জন্য ডিক্লেয়ার করা কন্ট্রাক্ট ইমপ্লিমেন্ট করব, নিচের মতো:
//src/store/mutation.ts export const mutations: MutationTree<State> & Mutations = { [MutationType.CreatePrescription](state, prescription) { state.data == prescription; }, [MutationType.SetPrescriptions](state, prescriptions) { state.prescriptions = prescriptions; }, [MutationType.CompletePrescription](state, newPrescription) { const prescription = state.prescriptions.findIndex((prescription) => prescription.id === newPrescription.id); if (prescription === -1) return; state.prescriptions[prescription] = { ...state.prescriptions[prescription], ...newPrescription }; }, [MutationType.RemovePrescription](state, Prescription) { const prescription = state.prescriptions.findIndex((prescription) => prescription.id === Prescription.id); if (prescription === -1) return; //If prescription exist in the state, remove it state.prescriptions.splice(prescription, 1); }, [MutationType.EditPrescription](state, Prescription) { const prescription = state.prescriptions.findIndex((prescription) => prescription.id === Prescription.id); if (prescription === -1) return; //If prescription exist in the state, toggle the editing property state.prescriptions[prescription] = { ...state.prescriptions[prescription], editing: !state.prescriptions[prescription].editing }; console.log("prescription", state.prescriptions[prescription]); }, [MutationType.UpdatePrescription](state, Prescription) { state.prescriptions = state.prescriptions.map((prescription) => { if (prescription.id === Prescription.id) { return { ...prescription, ...Prescription }; } return prescription; }); }, [MutationType.SetLoading](state, value) { state.loading = value; }, [MutationType.SetCreateModal](state, value) { state.showCreateModal = value; }, [MutationType.SetEditModal](state, value) { state.showEditModal = value.showModal; state.editModalPrescriptionId = value.prescriptionId; }, [MutationType.SetPrescriptionModal](state, { showModal, prescriptionId }) { state.showPrescriptionModal = showModal; state.showPrescriptionId = prescriptionId; }, };
MutationTree হলো একটি জেনেরিক টাইপ যা Vuex প্যাকেজের সাথে শিপ করা হয়েছে। আমরা উপরের স্নিপেটে এটা ব্যবহার করলাম মিউটেশন ট্রির টাইপ ডিক্লেয়ার করতে। মিউটেশন ট্রি এবং মিউটেশন নিশ্চিত করে কন্ট্রাক্ট সঠিকভাবে ইমপ্লিমেন্ট হয়েছে; অন্যথায় টাইপস্ক্রিপ্ট এরর ট্রিগার করবে।
Actions
অ্যাকশন হলো মেথডগুলো যা মিউটেশন ট্রিগার করে। অ্যাসিনক্রোনাস টাস্ক হ্যান্ডেল করার সময়, যেমন API-এ রিকোয়েস্ট করা, অ্যাকশন ব্যবহার করা হয় আগে সংশ্লিষ্ট মিউটেশন কল করার জন্য API রেসপন্সকে পেলোড হিসেবে। আমরা এই সিন্যারিওর জন্য একদম স্পষ্ট ইমপ্লিমেন্টেশন পাবো আমাদের অ্যাকশন তৈরি করার সময়।
অ্যাকশন তৈরি করার আগে, আমরা Axios ইনস্টল করব আমাদের সব HTTP রিকোয়েস্ট রেইলস সার্ভারের জন্য হ্যান্ডেল করার জন্য, নিচের মতো:
npm install axios --save
actions.ts ফাইল তৈরি করুন store ডিরেক্টরিতে নিচের কোড স্নিপেটসহ:
//src/store/actions.ts import { ActionContext, ActionTree } from "vuex"; import { Mutations, MutationType } from "./mutations"; import { State, Prescription, Data } from "./state"; import axios from "axios"; const apiUrl = "https://localhost:3000/prescriptions"; export enum ActionTypes { GetPrescriptions = "GET_PRESCRIPTIONS", SetCreateModal = "SET_CREATE_MODAL", SetEditModal = "SET_EDIT_MODAL", RemovePrescription = "REMOVE_PRESCRIPTION", CreatePrescription = "CREATE_PRESCRIPTION", UpdatePrescription = "UPDATE_PRESCRIPTION", }
একইভাবে, উপরের স্নিপেট enum সিম্বল ব্যবহার করে আমাদের অ্যাপের সব সম্ভাব্য অ্যাকশন নাম ধরে রাখে।
পরবর্তী, আমরা প্রতিটি ActionType-র জন্য একটি কন্ট্রাক্ট (টাইপ) ডিক্লেয়ার করব, নিচের মতো:
//src/store/actions.ts type ActionAugments = Omit<ActionContext<State, State>, "commit"> & { commit<K extends keyof Mutations>(key: K, payload: Parameters<Mutations[K]>[1]): ReturnType<Mutations[K]>; }; export type Actions = { [ActionTypes.GetPrescriptions](context: ActionAugments): void; [ActionTypes.SetCreateModal](context: ActionAugments): void; [ActionTypes.SetEditModal](context: ActionAugments): void; [ActionTypes.RemovePrescription](context: ActionAugments, Prescription: { id: number }): void; [ActionTypes.CreatePrescription](context: ActionAugments, data: Data): void; [ActionTypes.UpdatePrescription](context: ActionAugments, prescription: Prescription): void; };
ActionAugments টাইপ সব কমিটকে শুধু তাদের ডিক্লেয়ার করা মিউটেশনে সীমাবদ্ধ করে এবং পেলোড টাইপ চেক করেও।
পরবর্তী, আমরা প্রতিটি ActionType-র জন্য ডিক্লেয়ার করা কন্ট্রাক্ট (টাইপ) ইমপ্লিমেন্ট করব। নিচের কোড action.ts ফাইলে যোগ করুন:
//src/store/actions.ts export const actions: ActionTree<State, State> & Actions = { async [ActionTypes.GetPrescriptions]({ commit }) { commit(MutationType.SetLoading, true); const response = await axios.get(apiUrl); commit(MutationType.SetLoading, false); commit(MutationType.SetPrescriptions, response.data); }, async [ActionTypes.SetCreateModal]({ commit }) { commit(MutationType.SetCreateModal, true); }, async [ActionTypes.SetEditModal]({ commit }) { commit(MutationType.SetEditModal, { showModal: true, prescriptionId: 1 }); }, //Optimistic update async [ActionTypes.RemovePrescription]({ commit }, Prescription) { if (Prescription != undefined) { commit(MutationType.RemovePrescription, Prescription); } const response = await axios.delete(`${apiUrl}/${Prescription.id}`); }, async [ActionTypes.CreatePrescription]({ commit, dispatch }, Prescription) { const response = await axios.post(apiUrl, Prescription); dispatch(ActionTypes.GetPrescriptions); }, async [ActionTypes.UpdatePrescription]({ commit, dispatch }, Prescription) { if (Prescription != undefined) { commit(MutationType.UpdatePrescription, Prescription); const response = await axios.patch(`${apiUrl}/${Prescription.id}`, Prescription); dispatch(ActionTypes.GetPrescriptions); } }, };
এখানে, আমরা একটি actions ভেরিয়েবল তৈরি করলাম যেটা সব ইমপ্লিমেন্টেড অ্যাকশন স্টোর করে। একইভাবে, ActionTree<State> & Actions নিশ্চিত করে কন্ট্রাক্ট (type Actions) সঠিকভাবে ইমপ্লিমেন্ট হয়েছে; অন্যথায় টাইপস্ক্রিপ্ট এরর ট্রিগার করবে।
আমরা আমাদের অ্যাসিনক্রোনাস কল রেইলস API এন্ডপয়েন্টে GetPrescriptions অ্যাকশনে সেটআপ করলাম এবং SetPrescriptions মিউটেশন টাইপ ট্রিগার করলাম রেসপন্স ডেটা পেলোড হিসেবে। আমরা SetCreateModal, SetEditModal, CreatePrescription, UpdatePrescription, এবং RemovePrescription অ্যাকশনও সেটআপ করলাম।
Getters
গেটার হলো মেথডগুলো যা স্টেটকে ফার্স্ট প্যারামিটার হিসেবে পায় এবং স্টোর স্টেট থেকে কম্পিউটেড ইনফরমেশন রিটার্ন করে।
getters.ts ফাইল তৈরি করুন store ডিরেক্টরিতে নিচের কোড স্নিপেটসহ:
//src/store/getters.ts import { GetterTree } from "vuex"; import { State, Prescription } from "./state"; export type Getters = { completedPrescriptionCount(state: State): number; totalPrescriptionCount(state: State): number; getPrescriptionById(state: State): (id: number) => Prescription | undefined; }; export const getters: GetterTree<State, State> & Getters = { completedPrescriptionCount(state) { return state.prescriptions.filter((prescription) => prescription.completed).length; }, totalPrescriptionCount(state) { return state.prescriptions.length; }, getPrescriptionById: (state) => (id: number) => { return state.prescriptions.find((prescription) => prescription.id === id); }, };
উপরের কোড স্নিপেট নিচের গেটারগুলো ডিফাইন করে:
completedPrescriptionCount– আমাদের স্টেটে টোটাল কতগুলো কমপ্লিটেড প্রেসক্রিপশন আছে সেটা পাওয়ার ফাংশন।totalPrescriptionCount– আমাদের স্টেটে টোটাল কতগুলো প্রেসক্রিপশন আছে সেটা পাওয়ার ফাংশন।getPrescriptionById– আইডি দিয়ে একটি প্রেসক্রিপশন পাওয়ার ফাংশন।
আমরা গেটারগুলোর টাইপ সেফটিও যোগ করলাম।
Store
আমাদের state, mutations, actions, এবং getters গ্লোবাল Vuex স্টোরে কানেক্ট করি। store/index.ts আপডেট করি নিচের মতো:
//src/store/index.ts import { createStore, Store as VuexStore, CommitOptions, DispatchOptions, createLogger } from "vuex"; import { State, state } from "./state"; import { Mutations, mutations } from "./mutations"; import { Actions, actions } from "./actions"; import { Getters, getters } from "./getters"; export const store = createStore<State>({ plugins: process.env.NODE_ENV === "development" ? [createLogger()] : [], state, mutations, actions, getters, }); export function useStore() { return store as Store; } export type Store = Omit<VuexStore<State>, "getters" | "commit" | "dispatch"> & { commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(key: K, payload: P, options?: CommitOptions): ReturnType<Mutations[K]>; } & { dispatch<K extends keyof Actions>(key: K, payload?: Parameters<Actions[K]>[1], options?: DispatchOptions): ReturnType<Actions[K]>; } & { getters: { [K in keyof Getters]: ReturnType<Getters[K]>; }; };
state, mutations, actions, এবং getters স্টোর তৈরি করার জন্য রিকোয়ার্ড, createStore মেথডে একটা অবজেক্ট পাস করলে যেটা সেগুলো ধরে। ডেভেলপমেন্টের সময়, createLogger প্লাগইন স্টেট (প্রিভিয়াস স্টেট এবং নেক্সট স্টেট) এবং মিউটেশন কনসোলে লগ করে। পুরো অ্যাপ্লিকেশনের কম্পোনেন্টে স্টোর অ্যাক্সেসযোগ্য করতে, সেটা পুরো অ্যাপ্লিকেশনে ইনজেক্ট করতে হবে। সৌভাগ্যে, Vue-CLI টুল ইতিমধ্যে পুরো স্টোর ইম্পোর্ট করে অ্যাপ্লিকেশনের Vue ইনস্ট্যান্সের মধ্যে পাস করে দিয়েছে।
Vuex স্টোর কম্পোনেন্টে ইন্টিগ্রেশন
এই টিউটোরিয়ালে, Vue 3 কম্পোজিশন API ব্যবহার করা হবে আমাদের ফ্রন্টএন্ড অ্যাপ্লিকেশনের সব কম্পোনেন্ট তৈরি করতে।
App Component
আমাদের ফ্রন্টএন্ড লঞ্চ होते সাথে প্রেসক্রিপশন ডেটার লিস্ট রেন্ডার করা উচিত। আমরা GetPrescription অ্যাকশন ডিসপ্যাচ করব কম্পোনেন্টের mounted() লাইফসাইকেল হুকে। স্টোর এক্সেস করার জন্য কম্পোনেন্টে, useStore হুক, যা আমাদের স্টোর রিটার্ন করে, এক্সিকিউট করা হবে।
//src/App.vue <script lang="ts"> import { computed, defineComponent, onMounted } from "vue"; import PrescriptionList from "./components/PrescriptionList.vue"; import { useStore } from "./store"; import { ActionTypes } from "./store/actions"; export default defineComponent({ components: { PrescriptionList }, setup() { const store = useStore(); const loading = computed(() => store.state.loading); onMounted(() => store.dispatch(ActionTypes.GetPrescriptions)); const completedCount = computed(() => store.getters.completedPrescriptionCount); const totalCount = computed(() => store.getters.totalPrescriptionCount); return { loading, completedCount, totalCount }; }, }); </script> <template> <nav class="navbar" role="navigation" aria-label="main navigation"> <div class="navbar-brand"> <a class="navbar-item" href="https://bulma.io"> <img src="https://bulma.io/images/bulma-logo.png" width="112" height="28" /> </a> </div> <div id="navbarBasicExample" class="navbar-menu"> <div class="navbar-start"> <a class="navbar-item"> Home </a> <a class="navbar-item"> About </a> </div> </div> </nav> <div class="container mx-auto mt-4"> <h1 class="is-size-3 has-text-centered p-2 has-text-weight-bold is-success">Vet clinic Frontend</h1> <h3 class="has-text-centered p-2">Manage records of treated livestock in your farm</h3> <div v-if="loading"> <h3 class="has-text-centered mt-4">Loading...</h3> </div> <div v-else> <p class="has-text-centered mt-2">{{ completedCount }} of {{ totalCount }} treated.</p> <PrescriptionList /> </div> </div> </template> <style> @import "~bulma/css/bulma.css"; </style>
এখানে, আমরা তিনটি কম্পিউটেড প্রোপার্টি তৈরি করলাম:
completedCount, যাcompletedPrescriptionCountগেটার মেথড কল করে টোটাল কমপ্লিটেড ট্রিটমেন্টের সংখ্যা পাওয়ার জন্য।totalCount, যাtotalPrescriptionCountগেটার মেথড কল করে টোটাল প্রেসক্রিপশনের সংখ্যা পাওয়ার জন্য।loading, যা স্টেটের loading প্রোপার্টি পায়।
Vue 3 কম্পোজিশন API-তে, টেমপ্লেটের জন্য যেসব মেথড এবং প্রোপার্টি রিকোয়ার্ড, সেগুলো রিটার্ন করতে হয় টেমপ্লেটে অ্যাক্সেসযোগ্য করতে। লক্ষ্য করুন কিভাবে আমরা loading, completedCount, and totalCount রিটার্ন করলাম।
PrescriptionList Component
এই কম্পোনেন্টটি ব্যাকএন্ড থেকে প্রেসক্রিপশনের লিস্ট রিট্রিভ করার জন্য এবং প্রেসক্রিপশন ডেটা এর চাইল্ড কম্পোনেন্টে পাস করার জন্য দায়বদ্ধ।
PrescriptionList.vue তৈরি করুন components ফোল্ডারে নিচের কোডসহ:
//src/components/PrescriptionList.vue <template> <table class="table is-hoverable is-striped"> <thead> <tr> <th><abbr title="Position">Prescription Id</abbr></th> <th>Treated</th> <th>Prescription</th> <th><abbr title="Won">Prescribed By</abbr></th> <th><abbr title="Drawn">Disease</abbr></th> <th><abbr title="Drawn">Livestock</abbr></th> <th><abbr title="Lost">Actions</abbr></th> </tr> </thead> <tbody v-if="prescriptions"> <tr v-for="prescription in prescriptions" :key="prescription.id"> <PrescriptionListItem v-bind="prescription" /> </tr> </tbody> <tfoot> <CreateModal v-show="showCreateModal"></CreateModal> <button class="button is-success" @click="setModal">Create Prescription</button> </tfoot> </table> <EditModal v-if="showEditModal" :id="editModalPrescriptionId"></EditModal> <Prescription v-if="showPrescriptionModal" :id="showPrescriptionId"></Prescription> </template> <script> import CreateModal from "./CreateModal"; import EditModal from "./EditModal"; import Prescription from "./Prescription"; import PrescriptionListItem from "./PrescriptionListItem"; import { defineComponent, computed } from "vue"; import { useStore } from "@/store"; import { MutationType } from "@/store/mutations"; export default defineComponent({ name: "Table", components: { CreateModal, PrescriptionListItem, Prescription, EditModal, }, setup() { const store = useStore(); const setModal = () => { store.commit(MutationType.SetCreateModal, true); }; const showCreateModal = computed(() => store.state.showCreateModal); const showEditModal = computed(() => store.state.showEditModal); const editModalPrescriptionId = computed(() => store.state.editModalPrescriptionId); const showPrescriptionModal = computed(() => store.state.showPrescriptionModal); const showPrescriptionId = computed(() => store.state.showPrescriptionId); const prescriptions = computed(() => store.state.prescriptions); return { showCreateModal, setModal, prescriptions, showEditModal, showPrescriptionModal, editModalPrescriptionId, showPrescriptionId }; }, }); </script> <style scoped> table { width: 100%; } .fa { font-size: 1.2rem; margin-left: 15px; } .fa:hover { font-size: 1.4rem; } </style>
setModal মেথড মিউটেশন ইনভোক করে যা স্টেটে showCreateModal কে true সেট করে, ফলে প্রেসক্রিপশন তৈরি করার মডাল লঞ্চ হয়।
আমরা নিচের কম্পিউটেড প্রোপার্টিগুলো তৈরি করলাম:
showCreateModal, যা স্টেটেরshowCreateModalপ্রোপার্টি পায়।showEditModal, যা স্টেটেরshowEditModalপ্রোপার্টি পায়।showPrescriptionModal, যা স্টেটেরshowPrescriptionModalপ্রোপার্টি পায়।prescription, যা স্টেট থেকে প্রেসক্রিপশনের লিস্ট পায়।showPrescriptionId, যা স্টেটেরshowPrescriptiondপ্রোপার্টি পায়।editPrescriptionId, যা স্টেটেরeditPrescriptionIdপ্রোপার্টি পায়।
Prescription Component
এই কম্পোনেন্টটি প্রেসক্রিপশন আইডি প্রপ হিসেবে পাবে PrescriptionList কম্পোনেন্ট থেকে। আইডি প্রপ ব্যবহার করা হবে একটি প্রেসক্রিপশন ফেচ করার জন্য відпов derrotado আইডির মাধ্যমে getPrescriptionById গেটার মেথড এবং ব্রাউজারে প্রেসক্রিপশন প্রোপার্টি রেন্ডার করতে।
components ফোল্ডারে Prescription.vue তৈরি করুন নিচের কোডসহ:
//src/components/Prescription.vue <template> <div class="modal is-active"> <div class="modal-background"></div> <div class="modal-content"> <h1>VIEW PRESCRIPTION</h1> <div class="card"> <div class="card-content"> <div class="media"> <div class="media-content"> <p class="title is-4">Livestock: {{ prescription.livestock }}</p> <p class="subtitle is-6"><b>Prescribed by:</b> {{ prescription.prescribed_by }}</p> <p class="subtitle is-6"><b>Disease:</b> {{ prescription.disease }}</p> </div> </div> <div class="content"> <p class="subtitle is-6">Prescription: {{ prescription.vet_prescription }}</p> </div> </div> </div> <button class="modal-close is-large" @click="closeModal" aria-label="close"></button> </div> </template> <script lang="ts"> import { computed } from "vue"; import { useStore } from "@/store"; import { MutationType } from "@/store/mutations"; export default { name: "PrescriptionModal", props: { id: { type: Number, required: true }, }, setup(props: any) { const store = useStore(); const prescription = computed(() => store.getters.getPrescriptionById(Number(props.id))); const closeModal = () => { store.commit(MutationType.SetPrescriptionModal, { showModal: false, prescriptionId: undefined, }); }; return { closeModal, prescription }; }, }; </script> <style scoped> h1 { color: #ffffff; text-align: center; font-size: 2rem; margin-bottom: 3rem; } </style>
closeModal মেথড SetPrescriptionModal মিউটেশন কমিট করে, যা স্টেটে showModal প্রোপার্টি কে false সেট করে, যখন prescription কম্পিউটেড প্রোপার্টি getPrescriptionById গেটার মেথড কল করে একটি প্রেসক্রিপশন রিট্রিভ করে তার আইডি দিয়ে।
CreateModal Component
এই কম্পোনেন্টটি প্রেসক্রিপশন তৈরি করার জন্য দায়বদ্ধ।
components ফোল্ডারে CreateModal.vue তৈরি করুন নিচের কোডসহ:
//src/components/CreateModal.vue <template> <div class="modal is-active"> <div class="modal-background"></div> <div class="modal-content"> <form @submit.prevent="createPrescription"> <div class="field"> <label class="label">Prescribed By</label> <div class="control"> <input v-model="prescribedBy" class="input" type="text" placeholder="Enter prescriber's name" /> </div> </div> <div class="field"> <label class="label">Prescription</label> <div class="control"> <textarea v-model="prescription" class="textarea" placeholder="Enter prescription"></textarea> </div> </div> <div class="field"> <label class="label">Disease</label> <div class="control"> <input v-model="disease" class="input" type="text" placeholder="Enter name of disease" /> </div> </div> <div class="field"> <label class="label">Livestock</label> <div class="control"> <input v-model="livestock" class="input" type="text" placeholder="Enter livestock" /> </div> </div> <div class="field is-grouped"> <div class="control"> <button type="submit" class="button is-link">Submit</button> </div> <div class="control" @click="closeModal"> <button class="button is-link is-light">Cancel</button> </div> </div> </form> </div> <button class="modal-close is-large" @click="closeModal" aria-label="close"></button> </div> </template> <script lang="ts"> import { defineComponent, reactive, toRefs } from "vue"; import { useStore } from "@/store"; import { Data } from "@/store/state"; import { MutationType } from "@/store/mutations"; import { ActionTypes } from "@/store/actions"; export default { name: "CreateModal", setup() { const state = reactive({ prescription: "", prescribedBy: "", disease: "", livestock: "", }); const store = useStore(); const createPrescription = () => { if (state.prescription === "" || state.prescribedBy === "" || state.disease === "" || state.livestock === "") return; const prescription: Data = { vet_prescription: state.prescription, prescribed_by: state.prescribedBy, disease: state.disease, livestock: state.livestock, }; store.dispatch(ActionTypes.CreatePrescription, prescription); state.prescription = ""; state.prescribedBy = ""; state.disease = ""; state.livestock = ""; }; const closeModal = () => { store.commit(MutationType.SetCreateModal, false); }; return { closeModal, ...toRefs(state), createPrescription }; }, }; </script>
createPrescription মেথড একটি অ্যাকশন ডিসপ্যাচ করে যা সার্ভারে পোস্ট রিকোয়েস্ট করে, फलস্বরূপ একটি নতুন প্রেসক্রিপশন তৈরি হয়, যখন closeModal মেথড SetPrescriptionModal মিউটেশন কমিট করে (যা স্টেটে showModal প্রোপার্টি কে false সেট করে)।
ফর্ম এবং ইনপুট এলিমেন্টের সাথে কাজ করার জন্য টু-ওয়ে ডেটা বাইন্ডিং (two-way data binding) লাগে, এবং তাই আমরা Vue 3-এর reactive মেথড ব্যবহার করলাম ইনপুট ফিল্ডে ব্যবহৃত মানগুলো স্টোর করতে।
নোট: reactive ব্যবহার করার সময়, আমাদের toRefs ব্যবহার করতে হয় রিয়েক্টিভ অবজেক্টকে একটি প্লেইন অবজেক্টে কনভার্ট করতে, যেখানে রেজাল্টিং অবজেক্টের প্রতিটি প্রোপার্টি একটি রেফ (ref) যা ओरিজিনাল অবজেক্টের সংশ্লিষ্ট প্রোপার্টি পয়েন্ট করে।
EditModal Component
এই কম্পোনেন্টটি প্রেসক্রিপশন আপডেট করার জন্য দায়বদ্ধ। এর লজিক পূর্বের সেকশনে আলোচনা করা CreatePrescription কম্পোনেন্টের মতো।
components ফোল্ডারে EditModal.vue তৈরি করুন নিচের কোডসহ:
//src/components/EditModal.vue <template> <div class="modal is-active"> <div class="modal-background"></div> <div class="modal-content"> <form @submit.prevent="updatePrescription"> <h1>Edit Modal</h1> <div class="field"> <label class="label">Prescribed By</label> <div class="control"> <input v-model="prescribedBy" class="input" type="text" placeholder="Enter prescriber's name" /> </div> </div> <div class="field"> <label class="label">Prescription</label> <div class="control"> <textarea v-model="prescription" class="textarea" placeholder="Enter Prescription"></textarea> </div> </div> <div class="field"> <label class="label">Disease</label> <div class="control"> <input v-model="disease" class="input" type="text" placeholder="Enter name of disease" /> </div> </div> <div class="field"> <label class="label">Livestock</label> <div class="control"> <input v-model="livestock" class="input" type="text" placeholder="Enter livestock" /> </div> </div> <div class="field is-grouped"> <div class="control"> <button type="submit" class="button is-link">Submit</button> </div> <div class="control" @click="closeModal"> <button class="button is-link is-light">Cancel</button> </div> </div> </form> </div> <button class="modal-close is-large" @click="closeModal" aria-label="close"></button> </div> </template> <script lang="ts"> import { defineComponent, reactive, toRefs, computed, onMounted } from "vue"; import { useStore } from "@/store"; import { Prescription } from "@/store/state"; import { MutationType } from "@/store/mutations"; import { ActionTypes } from "@/store/actions"; export default { name: "EditModal", props: { id: { type: Number, required: true }, }, setup(props: any) { const state = reactive({ prescription: "", prescribedBy: "", disease: "", livestock: "", }); const store = useStore(); const setFields = () => { const prescription = store.getters.getPrescriptionById(Number(props.id)); if (prescription) { console.log("prescription si kolo", prescription); state.prescription = prescription.vet_prescription; state.prescribedBy = prescription.prescribed_by; state.disease = prescription.disease; state.livestock = prescription.livestock; } }; onMounted(() => { setFields(); }); const updatePrescription = () => { if (state.prescription === "" || state.prescribedBy === "" || state.disease === "" || state.livestock === "") return; const prescription: Prescription = { id: props.id, vet_prescription: state.prescription, prescribed_by: state.prescribedBy, disease: state.disease, livestock: state.livestock, completed: false, editing: false, }; store.dispatch(ActionTypes.UpdatePrescription, prescription); state.prescription = ""; state.prescribedBy = ""; state.disease = ""; state.livestock = ""; }; const closeModal = () => { store.commit(MutationType.SetEditModal, { showModal: false, prescriptionId: undefined }); }; return { closeModal, ...toRefs(state), updatePrescription }; }, }; </script> <style scoped> label { color: #ffffff; } h1 { color: #ffffff; text-align: center; font-size: 2rem; margin-bottom: 3rem; } </style>
createPrescription মেথড একটি অ্যাকশন ডিসপ্যাচ করে যা সার্ভারে পুট (put) রিকোয়েস্ট করে, ফলে একটি এক্সিস্টিং প্রেসক্রিপশন আপডেট হয় তার আইডি দিয়ে, যখন closeModal মেথড SetPrescriptionModal মিউটেশন কমিট করে যা স্টেটে showModal প্রোপার্টি কে false সেট করে। onMounted লাইফসাইকেল হুকে setFields মেথড কল করলে getPrescriptionById গেটার মেথড ট্রিগার হয় স্টোর থেকে একটি প্রেসক্রিপশন ফেচ করতে তার আইডি দিয়ে এবং তারপর রিয়েক্টিভ অবজেক্টের প্রোপার্টিগুলো আপডেট হয় ফেচ করা প্রেসক্রিপশন প্রোপার্টিগুলোর সাথে যতক্ষণ না কম্পোনেন্ট DOM-এ মাউন্ট হয়।
ফাইনাল প্রজেক্ট লঞ্চ করা
আপনার রেইলস API সার্ভারের রুট ডিরেক্টরিতে, নিচের কমান্ড রান করুন সার্ভার শুরু করতে:
rails server
এখন, ফ্রন্টএন্ড অ্যাপ্লিকেশন চালাতে নিচের কমান্ড রান করুন:
npm run serve
শেষে, আপনার অ্যাপ্লিকেশন এরকম হওয়া উচিত:

উপসংহার (Conclusion)
আমরা রেইলস দিয়ে একটি CRUD API সার্ভার এবং Vue 3 কম্পোজিশন API এবং Vuex দিয়ে আমাদের ফ্রন্টএন্ড অ্যাপ্লিকেশন বানালাম, সব আলাদা সার্ভারে রান করছে যখন দুটি স্ট্যাক ইন্টিগ্রেট করা হয়েছে একটি ফুল-স্ট্যাক ওয়েব অ্যাপ্লিকেশন বানাতে। আশা করি এই টিউটোরিয়াল থেকে আপনি অনেক কিছু শিখেছেন। যদি কোনো প্রশ্ন বা সুপারিশ থাকে তবে নিচে কমেন্ট সেকশনে জানাতে পারেন। এই টিউটোরিয়ালে তৈরি ফুল-স্ট্যাক অ্যাপ্লিকেশনের GitHub রেপো এখানে।