পাঠ ২৩ · ৫৮-এর মধ্যে · মডিউল ৬
Home / Courses / Full-Stack Web Frameworks / রিসোর্স মডেলিং

REST রিসোর্স মডেলিং

REST resource modeling — nouns, not verbs
৯ মিনিট পড়া মধ্যবর্তী · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • কেন REST URL-এ verb না লিখে শুধু noun (রিসোর্সের নাম) লেখা হয়
  • রিসোর্স হায়ারার্কি কীভাবে সম্পর্ক (parent-child) প্রকাশ করে — /users/:id/posts
  • Python-এ একটি নেস্টেড dict "ডেটাবেস" মডেল করা এবং পাথ-ভিত্তিক অ্যাক্সেস ফাংশন লেখা
  • একই ডেটার উপর দুই ধরনের কুয়েরি — "সব পোস্ট" বনাম "একজন ইউজারের পোস্ট" — বাস্তবে চালিয়ে যাচাই করা

১ · Noun, verb নয় — URL-এ কী থাকা উচিত

REST API ডিজাইনের সবচেয়ে সাধারণ ভুল হলো URL-এ ক্রিয়া (verb) বসিয়ে দেওয়া — যেমন /getUser, /createPost, /deleteComment। এটি ভুল কারণ HTTP মেথডই ইতিমধ্যে বলে দেয় কী করা হচ্ছে (GET = পড়া, POST = তৈরি করা, DELETE = মুছে ফেলা) — URL-এর কাজ শুধু কোন রিসোর্স নিয়ে কাজ হচ্ছে তা প্রকাশ করা।

ভুল (verb-ভিত্তিক)
GET /getUser?id=5, POST /createUser, POST /deleteUser?id=5 — verb URL-এও আছে, মেথডেও আছে, অপ্রয়োজনীয় পুনরাবৃত্তি।
সঠিক (noun-ভিত্তিক)
GET /users/5, POST /users, DELETE /users/5 — URL শুধু রিসোর্স বলে, মেথড কাজ বলে।
নিয়ম মনে রাখার সহজ উপায়: রিসোর্স URL সবসময় বহুবচন noun দিয়ে শুরু হয় (/users, /posts, /orders) — কখনো ক্রিয়াপদ দিয়ে নয়।

২ · রিসোর্স হায়ারার্কি — সম্পর্ক প্রকাশ করা

বাস্তব ডেটায় রিসোর্সগুলোর মধ্যে সম্পর্ক থাকে — একজন ইউজারের একাধিক পোস্ট থাকতে পারে। এই সম্পর্ক URL পাথেই প্রকাশ করা যায়, নেস্টেড (nested) রিসোর্স হিসেবে।

/users সব ইউজারের কালেকশন /users/5 একজন নির্দিষ্ট ইউজার /users/5/posts /users/5/orders
প্যারেন্ট রিসোর্সের ভেতরে নেস্টেড পাথ চাইল্ড রিসোর্সের "মালিকানা" সম্পর্ক প্রকাশ করে — /users/5/posts মানে "ইউজার ৫-এর পোস্টসমূহ", পুরো সাইটের সব পোস্ট নয়।

লক্ষণীয়, /posts (ফ্ল্যাট, সব পোস্ট) এবং /users/:id/posts (নেস্টেড, শুধু একজনের পোস্ট) — দুটো সম্পূর্ণ ভিন্ন কুয়েরি, যদিও দুটোই "পোস্ট" রিসোর্স নিয়েই কথা বলছে। নিচের কোড সেলে দুটোই বাস্তবে বাস্তবায়ন করে দেখানো হচ্ছে।

৩ · Python-এ নেস্টেড রিসোর্স ডেটাবেস ও পাথ-ভিত্তিক অ্যাক্সেস

নিচের কোড সেলে একটি ছোট্ট ইন-মেমরি "ডেটাবেস" তৈরি করা হচ্ছে — users ডিকশনারিতে প্রতিটি ইউজারের নিজের পোস্টের আইডি তালিকা আছে, এবং posts ডিকশনারিতে সব পোস্টের পূর্ণ ডেটা। এরপর একটি ছোট্ট পাথ-ম্যাচার ফাংশন লেখা হচ্ছে যা /posts এবং /users/:id/posts — দুটো ভিন্ন পাথ প্যাটার্ন চিনে সঠিক ফাংশন কল করে (L01-এর Router ধারণার একটি ছোট সংস্করণ)।

Python
# নেস্টেড রিসোর্স "ডেটাবেস" -- users প্রতিটি নিজের post_ids রাখে
users = {
    1: {"id": 1, "name": "রহিম", "post_ids": [101, 102]},
    2: {"id": 2, "name": "করিম", "post_ids": [103]},
}

posts = {
    101: {"id": 101, "user_id": 1, "title": "আমার প্রথম পোস্ট"},
    102: {"id": 102, "user_id": 1, "title": "Python শেখার অভিজ্ঞতা"},
    103: {"id": 103, "user_id": 2, "title": "করিমের ভ্রমণকাহিনি"},
}

def list_all_posts():
    """GET /posts -- সব পোস্ট, ফ্ল্যাট কালেকশন"""
    return list(posts.values())

def list_user_posts(user_id):
    """GET /users/:id/posts -- শুধু নির্দিষ্ট একজন ইউজারের পোস্ট"""
    if user_id not in users:
        return None
    return [posts[pid] for pid in users[user_id]["post_ids"]]

def handle_request(path):
    """ছোট্ট পাথ-ম্যাচার -- /posts এবং /users/:id/posts দুটো প্যাটার্নই চেনে"""
    segments = [s for s in path.strip('/').split('/') if s]

    if segments == ['posts']:
        return 200, list_all_posts()

    if len(segments) == 3 and segments[0] == 'users' and segments[2] == 'posts':
        try:
            user_id = int(segments[1])
        except ValueError:
            return 400, {"error": "user id সংখ্যা হতে হবে"}
        result = list_user_posts(user_id)
        if result is None:
            return 404, {"error": "ইউজার পাওয়া যায়নি"}
        return 200, result

    return 404, {"error": "রুট পাওয়া যায়নি"}

# --- সব পোস্ট বনাম একজন ইউজারের পোস্ট ---
status_a, body_a = handle_request('/posts')
print(f"GET /posts             -> status {status_a}, মোট {len(body_a)}টি পোস্ট")
for p in body_a:
    print(f"   #{p['id']} (user {p['user_id']}): {p['title']}")

print()
status_b, body_b = handle_request('/users/1/posts')
print(f"GET /users/1/posts      -> status {status_b}, রহিমের {len(body_b)}টি পোস্ট")
for p in body_b:
    print(f"   #{p['id']}: {p['title']}")

print()
status_c, body_c = handle_request('/users/2/posts')
print(f"GET /users/2/posts      -> status {status_c}, করিমের {len(body_c)}টি পোস্ট")
for p in body_c:
    print(f"   #{p['id']}: {p['title']}")

print()
status_d, body_d = handle_request('/users/99/posts')
print(f"GET /users/99/posts     -> status {status_d}, বডি: {body_d}")

print(f"\nযাচাই: সব পোস্টের সংখ্যা ({len(body_a)}) == রহিমের ({len(body_b)}) + করিমের ({len(body_c)}) পোস্ট -> "
      f"{len(body_a) == len(body_b) + len(body_c)}")

    
/posts ৩টি পোস্ট রিটার্ন করে (৩টিই), /users/1/posts ঠিক ২টি (শুধু রহিমের ১০১, ১০২), আর /users/2/posts ঠিক ১টি (শুধু করিমের ১০৩) — একই posts ডেটা থেকে, শুধু হায়ারার্কি URL অনুযায়ী ভিন্নভাবে ফিল্টার হচ্ছে। /users/99/posts-এ ৯৯ নম্বর ইউজার নেই বলে 404 আসে — handle_request() কখনো crash করে না, প্রতিটি অবস্থার জন্য একটি সঠিক status code রিটার্ন করে।
মূল কথা · Key takeaway

REST resource modeling মানে দুটো সিদ্ধান্ত — URL-এ verb না বসিয়ে noun বসানো (মেথড কাজ বলে দেয়), এবং parent-child সম্পর্ক থাকলে নেস্টেড পাথ ব্যবহার করা (/users/:id/posts)। এই পাঠের handle_request() ফাংশনটিই M6-এর পরের পাঠগুলোর ভিত্তি — L24-এ এই একই ধরনের ডেটাবেসে সঠিক HTTP verb ও status code যোগ করা হবে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ /users/deleteUser/5-এর বদলে DELETE /users/5 ব্যবহার করা কেন ভালো?

প্রথমটিতে "delete" কাজটি URL-এর মধ্যে লেখা আছে, যা GET /users/deleteUser/5 দিয়ে ভুলবশত কল হলেও একই কাজ করবে বলে মনে হতে পারে (URL দেখেই বোঝা যায় না আসলে কোন HTTP মেথড ব্যবহার হচ্ছে)। দ্বিতীয়টিতে URL শুধু রিসোর্স (/users/5) বলে, আর DELETE মেথডই কাজটি নির্ধারণ করে — তাই ভুল মেথডে কল হলে সহজেই ধরা পড়ে (যেমন GET /users/5 শুধু পড়বে, মুছবে না)।

প্র ০২ একটি পোস্টের কমেন্টগুলো কি /comments?post_id=101 নাকি /posts/101/comments — কোনটি ভালো ডিজাইন?

/posts/101/comments ভালো, কারণ এটি হায়ারার্কি সম্পর্কটি URL structure-এই স্পষ্টভাবে প্রকাশ করে — যে কেউ পাথ দেখেই বুঝবে এটি "পোস্ট ১০১-এর কমেন্টসমূহ"। কোয়েরি প্যারামিটার (?post_id=101) ব্যবহার করা যেতে পারে ঐচ্ছিক ফিল্টারিং/সার্চের জন্য (যেমন L25-এর ফিল্টারিং), কিন্তু মূল parent-child সম্পর্ক প্রকাশের জন্য নেস্টেড পাথই প্রচলিত ও স্পষ্ট।

প্র ০৩ উপরের কোডে handle_request('/users/1/posts') কেন ঠিক ২টি পোস্ট রিটার্ন করে, ৩টি নয়?

কারণ list_user_posts(1) শুধুমাত্র users[1]["post_ids"]-এ থাকা আইডিগুলো ([101, 102]) থেকে পোস্ট বের করে আনে — posts ডিকশনারিতে থাকা ১০৩ নম্বর পোস্টটি ইউজার ২ (করিম)-এর, তাই সেটি এই তালিকায় নেই। এটাই হায়ারার্কি-ভিত্তিক ফিল্টারিং-এর মূল কাজ — শুধু সংশ্লিষ্ট চাইল্ড রিসোর্স ফেরত দেওয়া, পুরো কালেকশন নয়।

অনুশীলন

  1. চিন্তা করুন: একটি ই-কমার্স সাইটে একটি "অর্ডার"-এর "অর্ডার আইটেম"গুলোর জন্য আপনি কোন URL পাথ ডিজাইন করবেন, এবং কেন?

    /orders/:id/items — কারণ একটি অর্ডার-আইটেম সবসময় একটি নির্দিষ্ট অর্ডারের অংশ (parent-child সম্পর্ক), এবং এই নেস্টেড পাথ সেই মালিকানা সম্পর্কটি সরাসরি প্রকাশ করে, ঠিক যেমন এই পাঠে /users/:id/posts ইউজার-পোস্ট সম্পর্ক প্রকাশ করেছে।

  2. পরীক্ষা করুন: উপরের কোড সেলে users ডিকশনারিতে একটি নতুন ইউজার 3: {"id": 3, "name": "সালমা", "post_ids": []} যোগ করুন, তারপর handle_request('/users/3/posts') কল করে দেখুন এটি কী রিটার্ন করে।

    এটি status 200 এবং একটি খালি লিস্ট [] রিটার্ন করবে — 404 নয়, কারণ সালমা users-এ আছে (তাই "ইউজার পাওয়া যায়নি" ভুল হবে), শুধু তার কোনো পোস্ট নেই। এটি একটি গুরুত্বপূর্ণ পার্থক্য — "রিসোর্স আছে কিন্তু খালি" আর "রিসোর্স নেই" দুটো ভিন্ন অবস্থা, এবং সঠিক REST ডিজাইনে এই দুটোর জন্য ভিন্ন status code হওয়া উচিত (যা L24-এ বিস্তারিত শেখানো হবে)।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক ফান্ডামেন্টাল, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি ও ডিপ্লয়মেন্ট — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • Python Programming কোর্স সহোদর কোর্স এই পাঠের dict-ভিত্তিক ডেটা মডেলিং ও list comprehension-এর ভাষাগত ভিত্তি সেই কোর্সে তৈরি হয়েছে।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks — সব এক জায়গায়।
আগের পাঠ
এরর হ্যান্ডলিং ও সেন্ট্রালাইজড এক্সসেপশন মিডলওয়্যার