সরাসরি পর্বে যান
পর্ব ৩২ · ৯৩-এর মধ্যে · মডিউল ৫

REST resource নকশা — URL, method আর সম্পর্ক

REST Resource Design — URLs, Methods and Relations

Taught in Bangla, this lesson treats an API as a contract, explains what REST really is versus everyday usage, names resources with plural nouns rather than verbs, the safe, idempotent and cacheable meaning of each method, PUT versus PATCH and JSON Merge Patch, nested versus flat URLs for relations, actions that do not fit CRUD such as cancelling an order, representations for money, dates, ids and envelopes, and designs the full Bazarghar API in Express 5.

এই পর্বে যা শিখবেন

  • API চুক্তি কী, আর Fielding-এর REST ও প্রচলিত “REST”-এর পার্থক্য
  • Resource চেনা, URL-এর নাম রাখা, আর method-এর safe, idempotent, cacheable ধর্ম
  • PUT, PATCH আর JSON Merge Patch; nested বনাম flat URL; CRUD-এ না পড়া কাজ
  • Field, তারিখ, টাকা, ID আর envelope-এর সিদ্ধান্ত; Location আর bulk কাজ
  • “বাজারঘর”-এর পুরো API কাগজে নকশা করে Express 5-এ চালানো, আর একটি খারাপ API সারানো

১ · API একটি চুক্তি — কে ডাকে, কী প্রতিশ্রুতি

পর্ব ২৫-এ আপনি Express-এ route বানিয়েছেন, 201 আর 404 ফেরত দিয়েছেন। কোড চলেছে। কিন্তু একটি প্রশ্ন তখন ওঠেনি: ঠিকানাটি /api/orders হবে, নাকি /placeOrder? দুটিই চলে। মডিউল ৪ শিখিয়েছে কীভাবে বানাতে হয়। মডিউল ৫-এর প্রশ্ন হলো কী বানাবেন।

Server যে ঠিকানা, ডেটার আকার আর আচরণের কথা দেয়, তার পুরোটাই একটি API contract, বাংলায় চুক্তি। কাগজে লেখা না থাকলেও চুক্তি থাকে, কারণ client-এর কোড সেই আচরণের ওপরই দাঁড়িয়ে আছে।

রেস্তোরাঁর মেনু ভাবুন। ওয়েটার আর ক্রেতার মাঝে মেনুই চুক্তি। রান্নাঘরে নতুন চুলা এলে ক্রেতার কিছু যায় আসে না। কিন্তু “কাচ্চি”-র নাম বদলে “আইটেম ৭” করলে নিয়মিত ক্রেতারা বিভ্রান্ত হবেন। API-তেও ভেতরের কোড যত খুশি বদলানো যায়, মেনু বদলানো কঠিন।

চুক্তি কঠিন হয় কারণ client আপনার হাতে থাকে না। “বাজারঘর”-এর client কারা?

Clientকী চায়বদলানো কতটা কঠিন
React ওয়েবসাইটপণ্য, কার্ট, অর্ডারসহজ: deploy করলেই নতুন কোড
Mobile appএকই ডেটাকঠিন: পুরোনো version মাসের পর মাস চলে
দোকানের admin panelদাম, stock, shipmentমাঝারি: নিজেদের টিম
বাইরের বিক্রেতা বা courier-এর সিস্টেমঅর্ডারের অবস্থাখুব কঠিন: অন্যের কোড

তাই ভালো API নকশার লক্ষ্য তিনটি। ব্যবহারকারী অনুমান করে ঠিকানা বলতে পারবেন, সব জায়গায় একই নিয়ম চলবে, আর ভুল ব্যবহার কঠিন হবে। সুন্দর URL নিজে লক্ষ্য নয়, লক্ষ্য হলো কম বিস্ময়।

উচ্চ স্তরের নকশা, যেমন retry, cache আর সিস্টেমজুড়ে সিদ্ধান্ত, আছে সিস্টেম ডিজাইন কোর্সের REST API নকশা পর্বে। সেগুলো আবার শেখানো হবে না। এখানে আমরা Express-এ চলা একটি আসল API গড়ব। Status code আর error format আসবে পর্ব ৩৩ (Status code আর error format)-এ, pagination পর্ব ৩৪-এ, versioning পর্ব ৩৫-এ।

২ · REST আসলে কী, আর সবাই যাকে REST বলে

REST কোনো library বা standard নয়। ২০০০ সালে Roy Fielding তাঁর PhD গবেষণাপত্রে web-এর স্থাপত্য ব্যাখ্যা করতে শব্দটি ব্যবহার করেন: Representational State Transfer. বিষয়টি হলো কয়েকটি শর্ত, যেগুলো মানলে web-এর মতো বিশাল সিস্টেম টিকে থাকে। সাধারণ ভাষায়:

শর্তসহজ অর্থবাজারঘরে
Client-serverUI আর ডেটার দায়িত্ব আলাদাReact আর Express
Statelessপ্রতিটি request নিজেই সম্পূর্ণ“আগের পাতা” server মনে রাখে না
Cacheableউত্তর বলে দেয় cache করা যাবে কি নাপণ্যের তালিকা
Uniform interfaceসব resource-এ একই ধরনের কথা বলাএকই method, একই নিয়ম
Layered systemমাঝে স্তর থাকলেও client জানে নাCDN, reverse proxy
Code on demand (ঐচ্ছিক)Server কোড পাঠাতে পারেBrowser-এ JavaScript

Uniform interface-এর ভেতরে আছে আরেকটি শর্ত: উত্তরের ভেতরের link ধরেই client পরের কাজ খুঁজে পাবে। আজ মানুষ “REST API” বলতে সাধারণত এর চেয়ে অনেক কম বোঝেন: URL-এ resource, HTTP method, JSON. ২০০৮ সালের অক্টোবরে Fielding নিজেই ব্লগে লেখেন, যেকোনো HTTP interface-কে REST API বলায় তিনি বিরক্ত। “বাজারঘর” API-ও কঠোর অর্থে REST নয়, HTTP-ভিত্তিক resource API. এই পর্বের নামে “REST” শব্দটি প্রচলিত অর্থেই ব্যবহার করা হয়েছে, ভুল ধারণা হিসেবে নয়।

Leonard Richardson-এর একটি মডেল, যা সাধারণত ২০০৮ সালের দিকের একটি বক্তৃতা থেকে ধরা হয় আর পরে Martin Fowler-এর লেখায় ছড়িয়েছে, এই দূরত্ব মাপে। স্তর ০: এক URL-এ সব। স্তর ১: আলাদা resource. স্তর ২: HTTP method-এর সঠিক ব্যবহার। স্তর ৩: উত্তরে link. বেশিরভাগ বাস্তব API থাকে স্তর ২-এ, আর “বাজারঘর”-ও সেখানেই থাকবে।

HATEOAS কী, আর কেন বিরল: পুরো নাম Hypermedia As The Engine Of Application State. উত্তরেই থাকবে পরের কাজের link, যেমন "links": {"cancel": "/api/orders/1/cancellation"}. Browser-এর জন্য এটি দারুণ কাজ করে, কারণ পেছনে একজন মানুষ link পড়ে সিদ্ধান্ত নেন। App-এর কোডে “cancel” কী তা আগে থেকেই লেখা থাকতে হয়, তাই লাভ কমে, অথচ উত্তর বড় হয়। ফলে সম্পূর্ণ রূপে এটি কম দেখা যায়।

REST-ধাঁচ বনাম RPC-ধাঁচ

RPC (Remote Procedure Call) মানে দূরের ফাংশন ডাকা। এখানে কেন্দ্রে থাকে কাজ, জিনিস নয়। দুটিই বৈধ নকশা। পার্থক্য দেখুন:

Resource-ধাঁচRPC-ধাঁচ
কেন্দ্রেবিশেষ্য: orderক্রিয়া: placeOrder
অর্ডার তৈরিPOST /api/ordersPOST /rpc/placeOrder
অর্ডার পড়াGET /api/orders/1POST /rpc/getOrder
HTTP cacheGET-এ বিনামূল্যে পাওয়া যায়সব POST হলে নিজে বানাতে হয়
ভালো কোথায়বাইরের client, ব্রাউজ করা ডেটাভেতরের service, কাজের মতো ডাক

একই টিমের TypeScript client আর server হলে RPC-ধাঁচ খুবই সুবিধাজনক। এটি পর্ব ৩৭ (Type-safe API)-এ দেখবেন। আর ক্লায়েন্ট নিজে field বেছে নিতে চাইলে GraphQL, পর্ব ৩৬-এ।

৩ · Resource আর action — URL-এর নাম

নকশা শুরু হয় একটি সহজ অনুশীলনে। ক্রেতার গল্প লিখে বিশেষ্যগুলো দাগ দিন: “ক্রেতা পণ্য দেখেন, কার্ট-এ রাখেন, অর্ডার করেন, পরে review লেখেন।” এই বিশেষ্যগুলোই resource. ক্রিয়াগুলো (দেখা, রাখা, করা) আলাদা করে URL-এ লিখতে হয় না, কারণ HTTP-তে ক্রিয়ার জায়গা আগে থেকেই আছে: method.

ডাকঘরের কথা ভাবুন। খামে লেখা থাকে কার কাছে যাবে: “ধানমন্ডি, বাড়ি ৭”। “দয়া করে পৌঁছে দেওয়ার কাজ” ঠিকানায় লেখা হয় না। সেটি ডাকঘরের ফর্মের ঘরে থাকে: রেজিস্টার্ড, পার্সেল, ফেরত। URL হলো ঠিকানা, method হলো সেই ফর্মের ঘর।

নিয়মএড়িয়ে চলুনলিখুন
বিশেষ্য, ক্রিয়া নয়/getProducts/api/products
Collection বহুবচন/api/product/2/api/products/2
ছোট হাতের অক্ষর, hyphen/api/Price_Updates/api/price-updates
ফাইলের extension নয়/api/products.json/api/products
শেষে / নয়, এক রূপ বেছে নিন/api/orders//api/orders
Filter query-তে/api/products/cheap/api/products?maxPrice=300

URL-এর path সাধারণভাবে case-sensitive ধরা হয়। Express ডিফল্টে উদার: আমাদের run-এ /API/Products আর /api/products/ দুটিই /api/products-এর route-এ মিলে 200 দিয়েছে। কিন্তু cache বা proxy এগুলোকে আলাদা ঠিকানা ধরতে পারে। তাই ছোট হাতের একটিই রূপ রাখুন। Hyphen-এর লিখনরীতিকে বলে kebab-case. একটি ব্যতিক্রম: ক্রেতার নিজের কার্ট একটিই, তাই /api/cart একবচন। একে বলে singleton resource. বহুবচনের নিয়ম collection-এর জন্য, একক জিনিসের জন্য নয়।

নিচে একটি খারাপ URL ধাপে ধাপে সারানো হলো। প্রতিটি ধাপে একটি করে ভুল কমছে।

শুরুদাম বদলের পুরোনো ডাক
GET /updateProductPrice?id=2&price=185.50
ধাপ ২Resource বের করা
কোন জিনিস? product, id 2
GET /products/2?price=185.50 (updateProductPrice বাদ)
ধাপ ৩API-র নিয়মে ঠিকানা
GET /api/products/2?price=185.50
ধাপ ৪ক্রিয়া গেল method-এ
PATCH /api/products/2?price=185.50
ধাপ ৫নতুন মান গেল body-তে, পয়সায়
PATCH /api/products/2
Content-Type: application/merge-patch+json

{"pricePaisa":18550}
ফলআগে আর পরে
আগে: GET /updateProductPrice?id=2&price=185.50
পরে: PATCH /api/products/2  {"pricePaisa":18550}
  1. চারটি ভুল এক লাইনে। GET দিয়ে ডেটা বদলাচ্ছে, URL-এ ক্রিয়া, id query-তে, আর দাম দশমিকে। Log, browser history আর crawler সবাই এই URL দেখতে ও ডাকতে পারে।
  2. বিশেষ্য খোঁজা। কাজটি কোন জিনিসের ওপর? পণ্য ২। ক্রিয়াটি মুছে পথটি হলো /products/2, আর id পথে চলে গেল।
  3. Prefix আর বহুবচন। সব API এক /api-এর নিচে, collection বহুবচনে। ঠিকানা এখন অনুমানযোগ্য।
  4. ক্রিয়া method-এ। অংশ বদলানো মানে PATCH. এটি আর safe method-এর ভান করছে না।
  5. মান body-তে। বদলানো ডেটা URL-এ নয়, body-তে থাকে। ৳১৮৫.৫০ হলো 18550 পয়সা, পূর্ণসংখ্যা। Field-এর নামেই একক লেখা: pricePaisa.
  6. পাশাপাশি দেখুন। URL ছোট হলো, অর্থ পরিষ্কার হলো। PATCH-এর ঠিক নিয়ম সেকশন ৫-এ আসছে।

এই নিয়মগুলো যন্ত্রকেও শেখানো যায়। নিচের ছোট lint একটি route তালিকা পরীক্ষা করে। বড় টিমে এমন নিয়ম CI-তে চলে, যাতে নকশার ভুল review-এর আগেই ধরা পড়ে।

// শেখার মডেল: একটি ছোট URL lint, নিয়মগুলো এই পর্বের নকশা থেকে নেওয়া
const VERBS = /^(get|create|add|update|delete|remove|fetch|list)([A-Z_-]|$)/;
const SINGULAR = /^(product|order|review|user)$/;

function lint(route) {
  const [method, path] = route.split(" ");
  const parts = path.replace(/^\/api\//, "").split("/");
  const names = parts.filter((p) => !p.startsWith(":"));
  const issues = [];
  if (names.some((n) => VERBS.test(n))) issues.push("verb in URL");
  if (names.some((n) => /[A-Z_]/.test(n))) issues.push("use lowercase-hyphen");
  if (SINGULAR.test(names[0])) issues.push("collection should be plural");
  if (names.length > 3) issues.push("nested too deep");
  if (method === "GET" && /\/(pay|cancel|logout)$/.test(path)) issues.push("GET must be safe");
  return issues;
}

const routes = [
  "POST /api/createOrder",
  "GET /api/product/:id",
  "GET /api/orders/:id/cancel",
  "POST /api/Order_Items",
  "GET /api/shops/:id/products/:id/reviews/:id/replies",
  "GET /api/products/:id/reviews",
  "PUT /api/cart/items/:productId",
  "POST /api/orders/:id/cancellation",
];
for (const r of routes) {
  const issues = lint(r);
  console.log(issues.length ? "BAD " : "OK  ", r, issues.join("; "));
}

প্রথম সারিতে দুটি ভুল একসাথে: BAD POST /api/createOrder verb in URL; use lowercase-hyphen. তিন নম্বর সারি GET must be safe দেখায়, কারণ GET-এ অর্ডার বাতিল হচ্ছে। শেষ তিনটি OK, এবং POST /api/orders/:id/cancellation-ও পাশ করেছে, যদিও তাতে “বাতিল” কাজটি আছে, কারণ এটি একটি বিশেষ্য। Lint শুধু একটি অনুমাননির্ভর ছাঁকনি। সে নাম দেখে, অর্থ নয়। অর্থ যাচাই করেন মানুষ।

৪ · Method-এর অর্থ — safe, idempotent, cacheable

Method শুধু লেবেল নয়। HTTP-এর standard RFC 9110 (২০২২) প্রতিটি method-এর কিছু ধর্ম ঠিক করে দিয়েছে। Browser, proxy, CDN আর crawler এই ধর্মের ওপর ভরসা করে আপনার অনুমতি ছাড়াই কাজ করে।

  • Safe: client কোনো বদল চাইছে না, কাজটি মূলত শুধু পড়া। তাই যে কেউ আগেভাগে ডাকতে পারে।
  • Idempotent: একই request একবার পাঠালে server-এ যে উদ্দেশ্যমূলক প্রভাব, বহুবার পাঠালেও তাই। উত্তর আলাদা হতে পারে, প্রভাব নয়।
  • Cacheable: উত্তর জমিয়ে রেখে পরের request-এ দেওয়া যায়।
MethodকাজSafeIdempotentCacheable
GETপড়াহ্যাঁহ্যাঁহ্যাঁ
HEADশুধু header পড়াহ্যাঁহ্যাঁহ্যাঁ
OPTIONSকী কী করা যায়হ্যাঁহ্যাঁনা
POSTতৈরি বা প্রক্রিয়ানানাকাগজে শর্তসাপেক্ষ, বাস্তবে প্রায় না
PUTপুরো বসানো বা বদলনাহ্যাঁনা
PATCHঅংশ বদলনানিশ্চয়তা নেইকাগজে শর্তসাপেক্ষ, বাস্তবে প্রায় না
DELETEমুছে ফেলানাহ্যাঁনা

GET দিয়ে ডেটা বদলানো শুধু “অশুদ্ধ” নয়, বিপজ্জনক। ২০০৫ সালে Google Web Accelerator নামের একটি টুল পাতার link আগেভাগে ডেকে রাখত। তখনকার রিপোর্ট অনুযায়ী কিছু web app-এ “Delete” লেখা link-ও GET ছিল, ফলে কারও ক্লিক ছাড়াই ডেটা মুছে যায়। JavaScript-এর “আপনি কি নিশ্চিত?” বার্তাও তাকে থামায়নি, কারণ সে JavaScript না চালিয়েই link ধরে এগোচ্ছিল। আজও crawler, link preview আর browser prefetch GET ডাকে।

Idempotency-র আসল মূল্য বোঝা যায় নেটওয়ার্ক ভাঙলে। মিরপুরে এক ক্রেতা বোতাম চাপলেন, Wi-Fi কেটে গেল, উত্তর এল না। অনুরোধটি server-এ পৌঁছেছিল কি? Client জানে না। আবার পাঠানোই স্বাভাবিক। নিচে তিনবার retry করে PUT আর POST-এর ফল পাশাপাশি দেখুন।

// শেখার মডেল: node:http-এ হাতে লেখা, আসল বাজারঘর server নয়
const http = require("node:http");
const cart = new Map();
const orders = [];

const server = http.createServer((req, res) => {
  let out = { error: "no route" };
  if (req.method === "PUT" && req.url === "/api/cart/items/1") {
    cart.set(1, 2); // qty 2 বসানো: যতবারই হোক, ফল একই
    out = { productId: 1, qty: cart.get(1) };
  } else if (req.method === "POST" && req.url === "/api/orders") {
    orders.push({ id: orders.length + 1, totalPaisa: 84000 }); // প্রতিবার নতুন
    out = orders.at(-1);
  }
  res.writeHead(200, { "Content-Type": "application/json" });
  res.end(JSON.stringify(out));
});

server.listen(0, "127.0.0.1", async () => {
  const base = `http://127.0.0.1:${server.address().port}/api`;
  for (let i = 1; i <= 3; i++) {
    const put = await fetch(`${base}/cart/items/1`, { method: "PUT" });
    const post = await fetch(`${base}/orders`, { method: "POST" });
    console.log(`try ${i}`, "PUT", await put.text(), "POST", await post.text());
  }
  console.log("cart lines:", cart.size, "| orders:", orders.length);
  console.log("to pay (paisa):", orders.length * 84000);
  server.close();
});

PUT-এর উত্তর প্রতিবার {"productId":1,"qty":2}, আর শেষে cart lines: 1. POST-এর id বাড়ছে: 1, 2, 3, তাই orders: 3. হিসাব: ৮৪,০০০ × ৩ = 252000 পয়সা, অর্থাৎ ৳৮৪০-এর জিনিসের জন্য ৳২,৫২০। PUT idempotent, কারণ সে একটি মান বসায়, “২টি যোগ করুন” বলে না। ঠিক এই কারণে কার্টে PUT /api/cart/items/1 {"qty":2} নিরাপদ, আর POST /api/cart/add বিপজ্জনক। POST-কে নিরাপদে retry করার কৌশল, idempotency key, পর্ব ৩৩-এর বিষয়।

৫ · PUT বনাম PATCH, আর JSON Merge Patch

পর্ব ২৫-এ দুটি method-ই এসেছিল, পার্থক্যটি হালকাভাবে। এখন ঠিকভাবে। PUT বলে: “এই ঠিকানায় এখন থেকে ঠিক এই জিনিসটি থাকবে।” যা পাঠাননি, তা থাকবে না। PATCH (RFC 5789, ২০১০) বলে: “বর্তমান জিনিসে এই বদলগুলো করুন।”

দর্জির মাপ ভাবুন। PUT মানে নতুন মাপের পুরো কার্ড জমা দেওয়া: হাতার ঘর ফাঁকা রাখলে হাতার মাপ আর নেই। PATCH মানে একটি চিরকুট: “শুধু কোমর এক ইঞ্চি বাড়ান।” বাকি মাপ আগের মতো থাকে।

PATCH-এর body কী রকম হবে, সেটি method ঠিক করে না। তাই একটি format লাগে। সবচেয়ে সহজটি JSON Merge Patch (RFC 7396, ২০১৪), যার media type application/merge-patch+json. তিনটি নিয়ম মনে রাখলেই হয়: উল্লেখ করা key বসে যায়, null মানে key মুছে ফেলা, আর array পুরোটাই বদলে যায়, তার এক টুকরো বদলানো যায় না।

// শেখার মডেল: PUT পুরো বদলায়, PATCH (RFC 7396) শুধু উল্লেখ করা অংশ
const http = require("node:http");
let product = { id: 2, name: "Soybean oil 1L", pricePaisa: 19000, stock: 3, tags: ["oil", "sale"] };

function mergePatch(target, patch) {
  const out = { ...target };
  for (const [key, value] of Object.entries(patch)) {
    if (value === null) delete out[key]; // null মানে key মুছে ফেলা
    else out[key] = value; // array হলে পুরোটাই বদলে যায়
  }
  return out;
}
const server = http.createServer(async (req, res) => {
  let raw = "";
  for await (const chunk of req) raw += chunk;
  const body = JSON.parse(raw);
  product = req.method === "PUT" ? { id: 2, ...body } : mergePatch(product, body);
  res.writeHead(200, { "Content-Type": "application/json" });
  res.end(JSON.stringify(product));
});
server.listen(0, "127.0.0.1", async () => {
  const url = `http://127.0.0.1:${server.address().port}/api/products/2`;
  const send = async (method, body) => {
    const r = await fetch(url, { method, body: JSON.stringify(body) });
    console.log(method.padEnd(5), JSON.stringify(body), "\n  ->", await r.text());
  };
  await send("PATCH", { pricePaisa: 18500 });
  await send("PATCH", { tags: ["oil"], stock: null });
  await send("PUT", { name: "Oil", pricePaisa: 18000 });
  server.close();
});

প্রথম PATCH-এ শুধু দাম বদলেছে, বাকি সব অক্ষত। দ্বিতীয়টিতে "tags":["oil","sale"] হয়ে গেল ["oil"], “sale” মোছার আলাদা নির্দেশ লাগেনি, আর stock key-টিই উধাও। শেষ PUT-এর ফল {"id":2,"name":"Oil","pricePaisa":18000}: tags নেই, কারণ PUT-এ পাঠানো হয়নি। এই মডেলটি ছোট রাখতে body যাচাই আর object-এর ভেতরের স্তরে merge বাদ দেওয়া হয়েছে। আসল RFC-তে nested object-ও স্তরে স্তরে merge হয়।

বিষয়PUTPATCH (Merge Patch)
Bodyপুরো representationশুধু বদল
না পাঠানো fieldথাকে না, বা নকশা অনুযায়ী errorঅক্ষত
Idempotentহ্যাঁMerge Patch সাধারণত হ্যাঁ, PATCH নিজে নিশ্চয়তা দেয় না
নতুন তৈরিপারে, ঠিকানা client জানলে (201)সাধারণত না
দুর্বলতাপুরোনো client নতুন field মুছে দিতে পারেnull মান রাখা যায় না
ভালো কোথায়কনফিগ, কার্টের লাইনফর্মের কয়েকটি ঘর

“PATCH সবসময় idempotent নয়” কেন? {"stock":5} বসালে যতবারই হোক ৫। কিন্তু কেউ যদি নিজের format বানায়, যেমন {"increment":5}, প্রতিবার ৫ বাড়বে। RFC-ও তাই বলে: PATCH নিজে safe বা idempotent নয়, তবে এমনভাবে পাঠানো সম্ভব যাতে idempotent হয়। আরেকটি নিয়ম: হয় পুরো patch বসবে, নয়তো কিছুই না। আধাআধি বদল চলবে না।

Express 5-এর একটি সত্যিকারের ফাঁদ

Merge Patch-এর media type application/json নয়। express.json() ডিফল্টে শুধু application/json পড়ে। ফলে কোনো error ছাড়াই req.body খালি থাকে। এটি সত্যিকারের run, Express 5.3.0-তে, handler শুধু req.body ফেরত দেয়:

application/json               200 {"body":{"stock":1}}
application/merge-patch+json   200 {"body":"undefined"}

দ্বিতীয় সারিতে status 200, অথচ কিছুই আসেনি। এমন handler PATCH-এ কিছু না বদলেও “সফল” বলবে। সমাধান এক লাইনের:

app.use(express.json({
  type: ["application/json", "application/merge-patch+json"],
}));

৬ · সম্পর্ক — nested বনাম flat URL

একটি পণ্যের অনেক review. URL-এ এই সম্পর্ক কীভাবে দেখাবেন? দুটি পথ: nested URL, যেমন /api/products/1/reviews/1, আর flat URL, যেমন /api/reviews/1. ডেটাবেসে সম্পর্ক কীভাবে থাকে, সেটি পর্ব ৪১ (সম্পর্ক)-এ আসবে। URL-কে ডেটাবেসের ছবি হতে হবে না, তাকে client-এর কাজের ছবি হতে হবে।

আমাদের real run-এ পণ্য ১-এ review 1 আছে, পণ্য ২-এ review 2. তিনটি অনুরোধের পথ ধাপে ধাপে দেখুন।

  1. ClientGETRequest
  2. Product আছে?:idশুধু nested-এ
  3. Review আছে?:reviewIdদুই পথেই
  4. মিল আছে?productIdশুধু nested-এ
ডেটাserver-এ যা আছে
review 1: {"id":1,"productId":1,"rating":5,"comment":"Good rice"}
review 2: {"id":2,"productId":2,"rating":3,"comment":"Leaky cap"}
Flatএকটি খোঁজ
$ curl localhost:3000/api/reviews/1
{"id":1,"productId":1,"rating":5,"comment":"Good rice",...}
Nestedদুটি খোঁজ, একটি তুলনা, একই উত্তর
$ curl -i localhost:3000/api/products/1/reviews/1
HTTP/1.1 200 OK
{"id":1,"productId":1,"rating":5,"comment":"Good rice",...}
Nested, ভুল জোড়াদুটিই আছে, কিন্তু সম্পর্ক নেই
$ curl -i localhost:3000/api/products/2/reviews/1
HTTP/1.1 404 Not Found
{"error":"not_found"}
Collectionএখানে nested-ই স্বাভাবিক
$ curl localhost:3000/api/products/1/reviews
{"items":[{"id":1,"productId":1,...}],"count":1}
  1. দুটি review, দুটি পণ্য। Review 1 পণ্য ১-এর, review 2 পণ্য ২-এর। প্রতিটি review-এর নিজের id পুরো সিস্টেমে অনন্য।
  2. Flat: GET /api/reviews/1. Review-এর id-ই যথেষ্ট, একটি খোঁজ। Client-কে জানতে হয় না সেটি কোন পণ্যের।
  3. Nested: GET /api/products/1/reviews/1. আগে পণ্য ১ খোঁজা, তারপর review 1, তারপর মিল যাচাই। উত্তর হুবহু একই, কাজ বেশি।
  4. ভুল জোড়া। /api/products/2/reviews/1: পণ্য ২ আছে, review 1-ও আছে, কিন্তু review 1 পণ্য ২-এর নয়। মিলের যাচাই না থাকলে ভুল ঠিকানাতেও উত্তর আসত, তাই 404. Nested URL মানেই এই বাড়তি যাচাইয়ের দায়িত্ব।
  5. তালিকায় nested দারুণ। /api/products/1/reviews মানে “এই পণ্যের review”, একটি স্বাভাবিক prefix. উত্তরে "count":1. নিয়ম: তালিকা nested, একক জিনিস flat.
কতটা গভীরে

সাধারণ পরামর্শ: collection/id/collection, এর বেশি নয়। /api/shops/1/products/2/reviews/3/replies/4-এ client-কে চারটি id মুখস্থ রাখতে হয়, আর server-কে চারটি মিল যাচাই করতে হয়। Reply-এর নিজের id থাকলে /api/replies/4 যথেষ্ট, সঙ্গে /api/reviews/3/replies তালিকার জন্য। অন্যের অধীনে ছাড়া যে জিনিসের অস্তিত্ব নেই, যেমন কার্টের লাইন, সেখানে nested স্বাভাবিক: /api/cart/items/1, কারণ এখানে পণ্যের id লাইনটিকে চেনায় কার্টের ভেতরেই।

Filter দিয়েও সম্পর্ক বলা যায়: /api/reviews?productId=1. দুটি রূপ একই ডেটা দিলে একটিকে মূল ঠিকানা ধরুন। Review-এর Location-এ আমরা /api/reviews/1 দিয়েছি, একটি মূল ঠিকানা। URL-এ পণ্যের id থাকলেও ঠিকানা জানা মানে দেখার অনুমতি নয়। প্রতিটি route-এ মালিকানা যাচাই লাগে, নইলে broken access control. বাস্তব প্রয়োগ পর্ব ৪৮ (Permission)-এ।

৭ · যে কাজ CRUD-এ পড়ে না — অর্ডার বাতিল আর bulk

অর্ডার বাতিল কি update? আংশিক। এতে stock ফেরত আসে, কারণ জমা হয়, হয়তো টাকা ফেরত যায়। শুধু একটি field বদল নয়, একটি ব্যবসায়িক ঘটনা। তিনটি নকশা প্রচলিত:

নকশাসুবিধাঅসুবিধা
PATCH /api/orders/1
{"status":"cancelled"}
শুদ্ধ REST-এর মতো দেখায়, নতুন route লাগে নাClient যেন যেকোনো status লিখতে পারে; এক PATCH-এ নানা নিয়ম জমে
POST /api/orders/1/cancellationঘটনাটি নিজেই sub-resource: কারণ, সময় রাখা যায়, পরে GET করা যায়নাম ভাবতে হয়, কিছু কাজে বিশেষ্য জোর করে বসানো মনে হয়
POST /api/orders/1/cancelসহজ, সবাই বোঝেURL-এ ক্রিয়া, ফেরত পড়ার ঠিকানা নেই

“বাজারঘর” দ্বিতীয়টি নিচ্ছে, কারণ বাতিলের নিজের ডেটা আছে। তবে এটি নৈতিক প্রশ্ন নয়। তৃতীয়টিও বহু বাস্তব API-তে আছে, আর কিছু প্রকাশ্য API গাইড :cancel-এর মতো আলাদা custom method-ই সুপারিশ করে। আসল নিয়ম দুটি: এই কাজ POST হবে, GET নয়, আর একটি API-তে একই রীতি সর্বত্র চলবে।

পেছনে থাকে একটি state machine. আমাদের অর্ডার: placed থেকে shipped বা cancelled. status field client লেখে না, server ঘটনা দেখে বদলায়। নিচে real run-এর পুরো গল্প।

1PUT /cart/items/1 {"qty":2}2PUT /cart/items/2 {"qty":1}3POST /orders4POST /orders/1/cancellation5POST /orders/1/cancellation6POST /orders/2/shipment7POST /orders/2/cancellation
Server-এর অবস্থা
order 1
order 2
চালের stock
কার্টের মোট
  1. কার্টে চাল। বাঁয়ের সব path /api-এর নিচে। ২ × 42000 পয়সা = 84000. লাইনটি আগে ছিল না, তাই PUT-এর উত্তর 201. একই PUT আবার পাঠিয়ে আমরা 200 পেয়েছি, qty সেই ২-ই। Stock এখনো 12, কারণ কার্টে রাখা মানে কেনা নয়।
  2. কার্টে তেল। এই run-এ তেলের দাম 18000, তাই মোট 84000 + 18000 = 102000.
  3. অর্ডার তৈরি। POST-এ body নেই, কারণ server নিজেই কার্ট থেকে নেয় এবং নিজের দাম দিয়ে হিসাব করে। Status placed, চালের stock 10, কার্ট খালি। উত্তরে Location.
  4. বাতিল। Server যাচাই করে অবস্থা placed, তাই বাতিল সম্ভব। Stock ফিরে 12, status cancelled. কারণটি পরে GET /api/orders/1/cancellation-এ পড়া যায়।
  5. আবার বাতিল। অবস্থা আর placed নয়, তাই 409. Stock আবার বাড়েনি, 12-ই আছে। State machine-এর পাহারা না থাকলে এই retry-তে stock 14 হতো।
  6. অন্য অর্ডার পাঠানো হলো। লবণ দিয়ে order 2 আগেই বানানো হয়েছে। এখানে shipment POST করায় status shipped. কার্টে কিছু নেই।
  7. পাঠানোর পর বাতিল। নিয়মে নেই, তাই 409, উত্তরে বর্তমান অবস্থা। Client-এর কোড দেখতে পায় কেন হলো না। Status code-এর পুরো নকশা পর্ব ৩৩-এর।

একটি ছোট প্রমাণ দেখুন যে client status লিখতে পারে না। /api/orders/1-এ PATCH route-ই নেই। Express নিজে থেকে OPTIONS-এর উত্তর দেয়, যা চুক্তি পরখ করার সহজ উপায়:

$ curl -i -X OPTIONS localhost:3000/api/orders/1
HTTP/1.1 200 OK
Allow: GET, HEAD
…
$ curl -i -X OPTIONS localhost:3000/api/products/1
HTTP/1.1 200 OK
Allow: DELETE, GET, HEAD, PATCH, PUT
…

Bulk কাজ — একসাথে অনেক

ঈদের আগে ৫০০ পণ্যের দাম বদলাতে হবে। ৫০০টি PATCH ধীর। Bulk operation মানে এক request-এ অনেক কাজ। ঠিকানাটিও বিশেষ্য রাখা যায়: POST /api/price-updates. শুরুতেই দুটি প্রশ্নের উত্তর ঠিক করতে হয়।

  • একটি ব্যর্থ হলে? হয় সব বাতিল (atomic, transaction-এ), নয়তো প্রতিটির আলাদা ফল।
  • সর্বোচ্চ কতটি? সীমা না থাকলে একটি request-ই server আটকে দিতে পারে। আমাদের সীমা ১০০।
$ curl -X POST localhost:3000/api/price-updates -H "Content-Type: application/json" \
       -d '{"items":[{"id":1,"pricePaisa":41000},{"id":77,"pricePaisa":100},{"id":3,"pricePaisa":42.5}]}'
{"results":[{"id":1,"result":"updated"},{"id":77,"result":"not_found"},{"id":3,"result":"invalid_price"}]}

তিনটি জিনিস, তিন রকম ফল। এখানে প্রতিটির আলাদা ফল নেওয়া হয়েছে, আর তাই client-কে results পড়তেই হবে, status দেখে “সব ঠিক” ভাবা চলবে না। এমন আংশিক সাফল্যের status কী হবে, তা নিয়ে মতভেদ আছে, পর্ব ৩৩-এ দেখবেন। খুব বড় কাজে background job-ই ভালো, পর্ব ৬২-এ।

৮ · Representation — field, তারিখ, টাকা, ID আর envelope

REST-এর “R” এখানে। Server-এ ডেটা থাকে database-এ। Client পায় তার একটি representation, বাংলায় রূপ, সাধারণত JSON. Database column আর JSON field এক হতে হবে এমন নয়, আর password hash-এর মতো জিনিস কখনোই বাইরে যাবে না। এই রূপটিই চুক্তির সবচেয়ে বড় অংশ।

বিষয়“বাজারঘর”-এর সিদ্ধান্তকেন
Field-এর নামcamelCase: pricePaisa, createdAtJavaScript client-এ স্বাভাবিক; snake_case-ও চলে, তবে একটিই রাখুন
তারিখISO 8601, UTC: 2026-11-01T17:16:13.692Zদ্ব্যর্থহীন, সাজালে সময়ের ক্রমে আসে
টাকাপূর্ণসংখ্যা পয়সা: "pricePaisa":42000দশমিকের গোলমাল নেই, নামেই একক
IDJSON-এ সংখ্যা বা string, ধরন স্থিরনিচে দেখুন
Boolean আর enum"status":"placed"নতুন অবস্থা যোগ করা সহজ
না-থাকা fieldKey বাদ, বা null, একটিই নিয়মClient-এর ভুল কমে

তারিখে 01/11/2026 লিখলে ১ নভেম্বর না ১১ জানুয়ারি, বলা যায় না। আগের সময়টিই বাংলাদেশে রাত ১১টা ১৬ মিনিট, কারণ এখানে UTC + ৬ ঘণ্টা: 2026-11-01T23:16:13.692+06:00 একই মুহূর্ত। Server UTC-তে পাঠাবে, দেখানোর সময় client স্থানীয় সময়ে বদলাবে। বাংলা তারিখ পর্ব ৫৯-এ।

টাকা কেন float নয়

৳১৯.৯০ দেখতে সরল। কিন্তু JavaScript-এর সংখ্যা দ্বিমিক ভগ্নাংশে থাকে (IEEE 754), আর দশমিকের ০.১-এর মতো অনেক মান দ্বিমিকে ঠিক লেখাই যায় না। ঠিক যেমন ১/৩ দশমিকে ০.৩৩৩… হয়ে চলতেই থাকে। হিসাব একসঙ্গে দেখুন।

// শেখার মডেল: একই বিক্রি, দুই রকম টাকার হিসাব, JSON হয়ে client-এ
const http = require("node:http");

const server = http.createServer((req, res) => {
  let takaFloat = 0;
  let paisaInt = 0;
  for (let sale = 0; sale < 1000; sale++) {
    takaFloat += 19.9; // ৳১৯.৯০ দশমিকে
    paisaInt += 1990; // একই দাম পয়সায়
  }
  res.writeHead(200, { "Content-Type": "application/json" });
  res.end(JSON.stringify({ takaFloat, paisaInt }));
});

server.listen(0, "127.0.0.1", async () => {
  const r = await fetch(`http://127.0.0.1:${server.address().port}/api/sales`);
  const { takaFloat, paisaInt } = await r.json();
  console.log("1000 sales, float taka :", takaFloat);
  console.log("1000 sales, int paisa  :", paisaInt);
  console.log("equal to 19900 taka?   :", takaFloat === 19900, paisaInt === 1990000);
  console.log("3 x 19.9               :", 3 * 19.9);
  console.log("1.005 to 2 places      :", (1.005).toFixed(2));
  console.log("42 taka 50 paisa shown :", (4250 / 100).toFixed(2));
  server.close();
});

Float-এ হাজারটি বিক্রির যোগফল 19900.000000000047, ৳১৯,৯০০ নয়। তুলনায় false. পয়সায় 1990000, হুবহু ঠিক, আর তুলনায় true. এমনকি তিনটি জিনিসের দাম 3 x 19.9 হলো 59.699999999999996. আর (1.005).toFixed(2)-এর ফল 1.00, কারণ ১.০০৫ ভেতরে একটু কম। পার্থক্য ছোট, কিন্তু দুই খাতা না মিললে হিসাবরক্ষকের সমস্যা বড়। দেখানোর সময় শেষ লাইনের মতো ভাগ করে 42.50 বানানো যায়। নিয়মটি মিলছে পর্ব ২৯-এর সঙ্গে, যেখানে টাকা সবসময় পূর্ণ পয়সা।

একটি বিনীত সংশোধন: পর্ব ২৫-এর price ছিল টাকায়, কারণ তখন বিষয়টি ছিল routing. চুক্তি এখন থেকে pricePaisa. ছাড় হিসাবে ভাগ এলে কীভাবে গোল করবেন, সেটি ব্যবসায়িক নিয়ম: ১৯,৯০০ পয়সার ৭.৫% = ১,৪৯২.৫ পয়সা। একটিই গোল করার নিয়ম একটিই জায়গায় রাখুন। ভিন্ন কৌশল হিসেবে কিছু API দামকে string ("199.00") পাঠায়, তাতেও float এড়ানো যায়। Database-এ integer বা DECIMAL, যেকোনোটি চলে, FLOAT নয়।

ID — সংখ্যা, string না UUID

ধরনসুবিধাসাবধান
ক্রমিক: 17ছোট, সহজ, index-এ ভালোমোট অর্ডারের সংখ্যা ফাঁস, অন্য id অনুমান করা যায়
UUIDকেন্দ্র ছাড়াই অনন্য, অনুমান কঠিনলম্বা। সময়-ভিত্তিক UUIDv7 (RFC 9562, ২০২৪) index-এ তুলনায় ভালো
৬৪-বিট সংখ্যাবড় সিস্টেমের idJSON-এ string হিসেবে পাঠান

শেষ সারির কারণটি পরীক্ষা করা। JavaScript-এ নিরাপদ পূর্ণসংখ্যার সীমা Number.MAX_SAFE_INTEGER = 9007199254740991. Node-এ আমাদের চালানো ফল: JSON.parse('9007199254740993') দিল 9007199254740992. Id-র শেষ অঙ্ক বদলে গেল, কোনো error ছাড়াই। অনুমান-কঠিন id-ও অনুমতির বিকল্প নয়।

Envelope বনাম সরাসরি JSON

একটি জিনিস ফেরত দিলে সরাসরি JSON যথেষ্ট: {"id":1,"status":"placed"}. তালিকায় ছোট envelope কাজে লাগে: {"items":[…],"count":2}. কারণ খোলা array-তে পরে nextCursor যোগ করার জায়গা নেই, অথচ object-এ নতুন key যোগ করলে পুরোনো client ভাঙে না। {"success":true,"data":{…}} ধাঁচের সবকিছু মোড়ানো envelope-ও বহু API-তে দেখবেন। এতে একই তথ্য দুবার থাকে, কারণ সাফল্যের খবর HTTP status আগেই দিয়েছে। “বাজারঘর”-এর নিয়ম: একক জিনিস সরাসরি, তালিকা items-এ।

Location header — নতুন জিনিসের ঠিকানা

POST-এ নতুন জিনিস হলে client জানতে চায় সেটি কোথায়। RFC 9110 বলে, 201-এর সঙ্গে server-এর Location পাঠানো উচিত। Express-এ res.status(201).location(url).json(data). সত্যিকারের run:

$ curl -i -X POST localhost:3000/api/products -H "Content-Type: application/json" \
       -d '{"sku":"SALT-1KG","name":"Salt 1kg","pricePaisa":4200,"stock":10}'
HTTP/1.1 201 Created
X-Powered-By: Express
Location: /api/products/3
Content-Type: application/json; charset=utf-8
…

{"id":3,"sku":"SALT-1KG","name":"Salt 1kg","pricePaisa":4200,"stock":10,"createdAt":"2026-11-01T17:16:13.692Z"}

Location-এর মান আপেক্ষিক হতে পারে, client সেটি request-এর ঠিকানার সঙ্গে মিলিয়ে নেয়। Client-কে id জুড়ে URL বানাতে হলো না, server-ই বলে দিল। আবার id, createdAt client পাঠায়নি, ওগুলো server-এর। তৈরির সময় client যে ঘরগুলো পাঠাতে পারে, শুধু সেগুলোই তোলা হচ্ছে, যাতে mass assignment না হয়।

৯ · বাজারঘরের পুরো API — কাগজ থেকে Express 5, আর একটি খারাপ API সারানো

এবার সব জোড়া দিই। প্রথমে কোড নয়, একটি পাতা। ক্রেতার গল্প, বিশেষ্য, সম্পর্ক, তারপর ঠিকানা আর method.

বাজারঘর API-এর resource মানচিত্র

Every path below starts with /api

Products থেকে reviews, আর cart থেকে orders-এ যাওয়ার মানচিত্র পণ্য · products /products GET POST /products/:id GET PUT PATCH DELETE /price-updates POST (bulk) Review · reviews /products/:id/reviews GET POST /reviews/:id GET কার্ট · cart (একবচন) /cart GET /cart/items/:productId PUT DELETE অর্ডার · orders /orders POST /orders/:id GET /orders/:id/shipment POST /orders/:id/cancellation POST GET এক পণ্যের অনেক review কার্ট থেকে অর্ডার
ছবি ৩২.১ — চারটি পরিবার, প্রতিটিতে একই নিয়ম। একটি শিখলে বাকিগুলো অনুমান করা যায়।

কাগজের সিদ্ধান্তগুলোর কারণ লিখে রাখুন, কারণ ছয় মাস পরে সেগুলোই কাজে লাগবে:

  • কার্টের লাইন PUT: ঠিকানা client জানে, আর “qty ২ বসানো” idempotent.
  • POST /api/orders-এ body নেই: দাম আর জিনিস server-ই কার্ট থেকে নেয়। Client-এর পাঠানো totalPaisa কখনো বিশ্বাস করা হয় না।
  • অর্ডারের ভেতরে দামের কপি: unitPricePaisa কেনার মুহূর্তের দাম। পরে পণ্যের দাম বদলালেও পুরোনো অর্ডার বদলায় না।
  • /api/cart-এ ক্রেতার id নেই: পরে login থেকে আসবে (পর্ব ৪৫)। এই demo-তে একজনই ক্রেতা।
  • অর্ডারের তালিকা এখনো নেই: pagination ছাড়া তালিকা খোলা ঝুঁকি, পর্ব ৩৪।

এবার Express 5-এ। এই পর্বের জন্য চালানো সত্যিকারের run: Node 24.15.0, Express 5.3.0 (লেখার সময়ের সর্বশেষ stable), ডেটা memory-তে। পর্ব ২৫-এর মতো এখানেও পড়ার সুবিধায় port 3000 লেখা হয়েছে, বাকি সব হুবহু। পুরো ফাইলের মূল অংশ:

app.post("/api/products", (req, res) => {
  const { sku, name, pricePaisa, stock = 0, description } = req.body ?? {};
  if (!sku || !name || !isMoney(pricePaisa)) {
    return badRequest(res, "sku, name and integer pricePaisa are required");
  }
  const product = addProduct({ sku, name, pricePaisa, stock, description });
  res.status(201).location(`/api/products/${product.id}`).json(product);
});

// ঠিকানা client জানে, তাই PUT; একই qty বারবার পাঠালে ফল একই
app.put("/api/cart/items/:productId", (req, res) => {
  const productId = Number(req.params.productId);
  if (!db.products.has(productId)) return notFound(res);
  const { qty } = req.body ?? {};
  if (!Number.isInteger(qty) || qty < 1) return badRequest(res, "qty must be a positive integer");
  const created = !db.cart.has(productId);
  db.cart.set(productId, qty);
  res.status(created ? 201 : 200).json(cartView());
});

// অবস্থা বদল: client status লেখে না, ঘটনার resource তৈরি করে
app.post("/api/orders/:id/cancellation", (req, res) => {
  const order = findOrder(req, res);
  if (!order) return;
  if (order.status !== "placed") {
    return res.status(409).json({ error: "cannot_cancel", status: order.status });
  }
  for (const line of order.items) {
    const product = db.products.get(line.productId);
    if (product) product.stock += line.qty;
  }
  order.status = "cancelled";
  order.cancellation = { reason: req.body?.reason, cancelledAt: now() };
  res.status(201).location(`/api/orders/${order.id}/cancellation`).json(order);
});

এখানে যাচাই ইচ্ছা করেই হাতে লেখা ও ছোট, পূর্ণ schema পর্ব ২৭-এ, আর স্তরে ভাগ করা পর্ব ২৯-এ। আসল project-এ stock-এর বদল আর অর্ডার তৈরি এক transaction-এ হবে, পর্ব ৪৩-এ। PUT আর PATCH-এর real ফল দেখুন। শুরুতে পণ্য ২-এ description ছিল:

$ curl localhost:3000/api/products/2
{"id":2,"sku":"OIL-1L","name":"Soybean oil 1L","pricePaisa":19000,"stock":3,"createdAt":"2026-11-01T17:16:13.314Z","description":"1 litre bottle"}

$ curl -X PUT localhost:3000/api/products/2 -H "Content-Type: application/json" \
       -d '{"sku":"OIL-1L","name":"Soybean oil 1L","pricePaisa":18500,"stock":3}'
{"id":2,"sku":"OIL-1L","name":"Soybean oil 1L","pricePaisa":18500,"stock":3,"createdAt":"2026-11-01T17:16:13.314Z"}

$ curl -X PUT localhost:3000/api/products/2 -H "Content-Type: application/json" -d '{"pricePaisa":18500}'
{"error":"PUT needs the full product: sku, name, pricePaisa, stock"}

$ curl -X PATCH localhost:3000/api/products/2 -H "Content-Type: application/merge-patch+json" \
       -d '{"pricePaisa":18000,"description":null}'
{"id":2,"sku":"OIL-1L","name":"Soybean oil 1L","pricePaisa":18000,"stock":3,"createdAt":"2026-11-01T17:16:13.314Z"}

$ curl -X PATCH localhost:3000/api/products/2 -H "Content-Type: application/merge-patch+json" \
       -d '{"id":99,"pricePaisa":1}'
{"error":"not patchable: id"}

লক্ষ করুন, PUT-এ description চলে গেছে, কিন্তু createdAt আর id থেকে গেছে, কারণ ওগুলো server-এর ঘর। আর id PATCH করার চেষ্টা আটকে গেছে একটি allow-list-এ। DELETE দুবার ডেকেও আমাদের run-এ দুবারই 204 এসেছে। কেউ দ্বিতীয়বারে 404 দিলেও idempotent থাকে, কারণ দুই ক্ষেত্রেই পণ্যটি নেই।

একটি খারাপ API-র review

ধরুন একটি পুরোনো দোকানের (কাল্পনিক) API হাতে এল। এটি “চলে”, কিন্তু প্রতিটি সারিই একটি ঝুঁকি। ডান দিকে চুক্তির নতুন রূপ।

পুরোনোসমস্যানতুন
POST /getProductsপড়ার কাজে POST, cache হারায়GET /api/products
POST /addToCartRetry-তে qty বাড়তে পারেPUT /api/cart/items/2
GET /removeFromCart?id=2GET-এ বদল, crawler-এর ঝুঁকিDELETE /api/cart/items/2
GET /order/cancel/17GET-এ বাতিল, আর একবচনPOST /api/orders/17/cancellation
POST /placeOrder
{"total":840.5}
Client-এর দাম বিশ্বাস, floatPOST /api/orders, server হিসাব করে
{"date":"01/11/26"}দ্ব্যর্থক তারিখ"createdAt":"2026-11-01T…Z"
{"Order_ID":17,"cust-name":"…"}এলোমেলো নামের রীতি{"id":17,"customerName":"…"}

তবে মানুষ যে API ব্যবহার করছেন, তা একদিনে বদলানো যায় না, পুরোনো client ভাঙবে। নতুন ঠিকানা পাশে বসিয়ে ধীরে সরানোর পথ পর্ব ৩৫-এ।

আরও গভীরে: Merge Patch-এর সীমা, অর্থাৎ null বসানো আর array-এর একটি উপাদান বদলাতে না পারা, মেটায় JSON Patch (RFC 6902, ২০১৩)। সেখানে body হলো কাজের তালিকা: [{"op":"replace","path":"/stock","value":5}]. বেশি ক্ষমতা, বেশি জটিলতা, আর সব কাজ idempotent হবে এমন নিশ্চয়তাও নেই। দুজন একসঙ্গে একই জিনিস বদলালে যে lost update হয়, তা ঠেকাতে RFC 5789 ETag আর If-Match-এর পরামর্শ দেয়। বিস্তারিত সিস্টেম ডিজাইনের এই পর্বে।

পরীক্ষা ও ইন্টারভিউতে: PUT আর POST-এর পার্থক্য? PUT idempotent, ঠিকানা client জানে; POST-এ server ঠিকানা ঠিক করে, আর পুনরাবৃত্তি নতুন প্রভাব ফেলতে পারে। Safe আর idempotent এক? না, DELETE idempotent কিন্তু safe নয়। Idempotent হলে কি উত্তরও একই? না, প্রভাব একই, উত্তর আলাদা হতে পারে। টাকা JSON-এ কীভাবে? ক্ষুদ্রতম এককে পূর্ণসংখ্যা, বা string. REST কি একটি protocol? না, স্থাপত্যের একটি ধরন।

১০ · AI-এর যুগে API নকশা — খসড়া AI-র, চুক্তি আপনার

AI কয়েক সেকেন্ডে পুরো CRUD API লিখে দেয়। কোড প্রায়ই চলেও। কিন্তু চলা আর ভালো চুক্তি এক নয়, আর চুক্তির ভুল পরে সারাতে সবচেয়ে দাম লাগে।

AI প্রায়ই যা করেকেন সমস্যাযাচাই
price: 19.99, FLOATদশমিকের ভুল জমেSchema পড়া, যোগফল test
Object.assign(order, req.body)Client status বা total বদলাতে পারেDiff পড়া, বাড়তি field পাঠিয়ে দেখা
Client-এর পাঠানো total ব্যবহারযে কেউ দাম কমিয়ে দেবে৳১ পাঠিয়ে দেখা
PUT-কে আংশিক বদলে ব্যবহারMethod-এর অর্থ ভাঙেএকটি field-সহ PUT পাঠানো
পুরোনো Express 4 syntax বা নেই এমন optionServer শুরুই হয় না, বা ভুল চলেআপনার version-এর docs, আসল run
মালিকানা যাচাই ছাড়া /api/orders/:idঅন্যের অর্ডার দেখা যায়অন্য ক্রেতার id দিয়ে ডাকা

AI-কে কোড লিখতে দেওয়ার আগে নকশার review করান। নিচের prompt-এ শেষ শর্তটি খেয়াল করুন: এতে AI আপনার চুক্তি বদলে নতুন কিছু বানিয়ে ফেলতে পারে না।

আমি Express 5-এ একটি অনলাইন দোকানের REST API বানাচ্ছি। নিচে আমার route তালিকা আর একটি নমুনা JSON আছে। [এখানে route তালিকা আর JSON পেস্ট করুন] নিয়ম: resource নাম বহুবচন বিশেষ্য, টাকা পূর্ণসংখ্যা পয়সায় (pricePaisa), তারিখ ISO 8601 UTC, PATCH মানে JSON Merge Patch, তৈরি হলে 201 আর Location. ১. কোন route বা field এই নিয়ম ভাঙছে, সারি ধরে দেখাও। ২. কোন GET ডেটা বদলাচ্ছে, আর কোন POST retry-তে দুবার কাজ করবে, বলো। ৩. কোথায় client-এর পাঠানো মান বিশ্বাস করা হচ্ছে, বলো। ৪. কোনো package বা option প্রস্তাব করলে তার অফিসিয়াল docs-এর লিংক দাও। নিশ্চিত না হলে বলো। ৫. কোড লিখো না। নতুন route বানিও না। শুধু review করো।

AI-এর API খসড়া নেওয়ার আগে — চেকলিস্ট

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

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১আপনার API Fielding-এর সংজ্ঞায় REST না হলে কি কোনো ক্ষতি আছে? তাহলে শব্দটি ব্যবহার করা উচিত কি না?

দুটি প্রশ্ন আলাদা। প্রথমটি প্রকৌশলের, দ্বিতীয়টি ভাষার। Fielding ২০০০ সালের গবেষণাপত্রে দেখাতে চেয়েছিলেন, web কেন দশকের পর দশক ধরে বড় হতে পেরেছে। তাঁর লক্ষ্য ছিল এমন সিস্টেম, যেখানে server আর client আলাদা সংগঠনের হাতে থেকেও বহু বছর ধরে স্বাধীনভাবে বদলাতে পারে। সেই লক্ষ্যের জন্য hypermedia জরুরি।

বেশিরভাগ দোকানের API-র লক্ষ্য ততটা বড় নয়। Client আপনারই টিম লেখে, documentation আছে, version আছে। এখানে স্তর ২-এর API দিব্যি কাজ করে: resource, method-এর অর্থ, cache, চেনা tool. Hypermedia যোগ করলে খরচ বাড়ে, কিন্তু client-এর কোড আগের মতোই নির্দিষ্ট field-এর ওপর নির্ভর করে। তাই ক্ষতি নেই, যদি আপনি জানেন কী ছাড়ছেন।

ক্ষতিটি তখনই হয়, যখন “REST” বলে শৃঙ্খলাটিও ছেড়ে দেওয়া হয়। সব কাজ POST, GET-এ বদল, সব উত্তর 200: এগুলোতে method-এর অর্থ হারায়, আর CDN, retry ও cache-এর বিনামূল্যের সুবিধাও যায়। ঠিক সেই জন্যই ২০০৮ সালে Fielding বিরক্ত হয়েছিলেন, এবং অন্যদের কেউ কেউ বলেন শব্দটির অর্থ ভাষার স্বাভাবিক নিয়মেই বদলে গেছে। দুই পক্ষেরই যুক্তি আছে।

বাস্তব পরামর্শ: দৈনন্দিন কথায় “REST API” বলুন, কিন্তু ইন্টারভিউতে পার্থক্যটি জানান। “এটি HTTP-ভিত্তিক resource API, Richardson-এর স্তর ২-এ” কথাটি বেশি সৎ।

মূল উপলব্ধি: শুদ্ধতার লেবেল লক্ষ্য নয়। কোন শর্ত ছাড়লে কোন সুবিধা হারায়, সেটি জানাই আসল নকশা-জ্ঞান।

প্র ০২GET যদি কেউ ডেটা বদলাতে ব্যবহার করে, তাতে সমস্যা শুধু “নিয়ম ভাঙা” নাকি আরও গভীর?

গভীর, কারণ method-এর অর্থ শুধু আপনার আর client-এর মধ্যে নয়। মাঝের প্রতিটি যন্ত্রের সঙ্গেও এটি চুক্তি। “GET safe” ধরে নিয়ে অনেক প্রোগ্রাম নিজে থেকে GET ডাকে: search engine-এর crawler, chat app-এর link preview, browser-এর prefetch, নিরাপত্তা স্ক্যানার, এমনকি ইমেইলের link যাচাইকারী।

২০০৫ সালের Google Web Accelerator ঘটনাটি এর ক্লাসিক উদাহরণ। টুলটি পাতার link আগেভাগে ডেকে রাখত, যাতে ব্রাউজিং দ্রুত মনে হয়। তখনকার রিপোর্ট ও ডেভেলপারদের লেখা অনুযায়ী, কিছু web app-এর admin পাতায় “delete” ছিল সাধারণ link, ফলে ব্যবহারকারীর ক্লিক ছাড়াই ডেটা মুছে যায়। JavaScript-এর confirm বার্তা মানুষের জন্য ছিল, যন্ত্র সেটি দেখেইনি।

তখন দোষ নিয়ে তর্কও হয়েছিল। একপক্ষ বলেছিল, specification বলছে GET safe, তাই দোষ app-এর। অন্যপক্ষ বলেছিল, বাস্তব web এমন app-এ ভরা, তাই টুলের সাবধান হওয়া উচিত ছিল। দুটি কথাই শেখার মতো।

একই দুর্বলতা নিরাপত্তাতেও। GET /api/orders/17/cancel থাকলে যে কেউ একটি ছবির ট্যাগে সেই ঠিকানা বসিয়ে দিতে পারে: <img src="…/cancel">. ব্যবহারকারীর browser cookie-সহ সেটি ডাকবে। এটিই CSRF-এর একটি সরল রূপ। SameSite cookie কিছুটা সাহায্য করে, তবে শুরুর সমাধান হলো বদলের কাজ GET-এ না রাখা।

মূল উপলব্ধি: আপনার URL শুধু আপনার client ডাকে না। Method-এর অর্থ মানা মানে অচেনা যন্ত্রদের সঙ্গেও নিরাপদে বাঁচা।

প্র ০৩এক পয়সার ভগ্নাংশ দিয়ে কি সত্যিই বড় ক্ষতি হতে পারে? টাকার জন্য integer, decimal string না float?

পারে, কারণ ছোট ভুল একই দিকে জমলে আর ছোট থাকে না। একটি বিখ্যাত ঘটনা Vancouver Stock Exchange-এর সূচক। ১৯৮২ সালের জানুয়ারিতে সূচক শুরু হয় ১,০০০ থেকে, আর প্রতি লেনদেনের পর হিসাব করা হতো। প্রকাশিত বিবরণ অনুযায়ী, মান গোল করার বদলে শেষ অঙ্ক কেটে ফেলা হতো। প্রতি লেনদেনে ক্ষতি নগণ্য, কিন্তু সবসময় নিচের দিকে। প্রায় ২২ মাস পরে, ১৯৮৩ সালের নভেম্বরে, সূচক প্রায় ৫২৫-এ নেমে আসে, আর সংশোধনের পর সেটি হয় প্রায় ১,০৯৯। বাজার পড়েনি, পড়েছিল হিসাব।

আমাদের real run-এ হাজারটি বিক্রিতে ভুল মাত্র 0.000000000047, যা তুচ্ছ। কিন্তু টাকার সিস্টেমে আসল ব্যথা হলো সমতা: total === paid মিথ্যা হলে অর্ডার আটকে যায়, হিসাব মেলে না। গোল করে ঢাকলে কোন জায়গায় গোল হলো, সেই প্রশ্ন ওঠে।

তিনটি পথের তুলনা করুন। Integer পয়সা: সরল, দ্রুত, যোগ-তুলনা হুবহু। সীমা হলো মুদ্রাভেদে ক্ষুদ্রতম একক আলাদা, আর JavaScript-এর নিরাপদ সীমা প্রায় ৯ × ১০১৫, যা পয়সার হিসাবেও সাধারণ দোকানের জন্য যথেষ্ট। Decimal string: "199.00" হুবহু থাকে, পড়া সহজ, তবে গণিতের জন্য decimal library লাগে। Float: মূল্য তালিকায় দেখানো ছাড়া টাকার হিসাবে এড়িয়ে চলুন।

অনেক payment API-তে পরিমাণ ক্ষুদ্রতম এককে নেওয়ার রীতি দেখা যায়, তবে এটি সর্বজনীন নয়। প্রতিটি gateway-র docs নিজে পড়ুন। পর্ব ৬৫ (Payment integration)-এ বিষয়টি আবার আসবে।

মূল উপলব্ধি: ভুলের আকার নয়, ভুল জমে কি না আর সমতা ভাঙে কি না, সেটিই প্রশ্ন। একবার JSON-এ দশমিক টাকা পাঠালে পরে তা বদলানো মানে চুক্তি বদলানো।

প্র ০৪PUT শুদ্ধ শোনায়, তবু অনেক API শুধু PATCH রাখে কেন? দুজন একসঙ্গে সম্পাদনা করলে কী হয়?

PUT-এর দার্শনিক সৌন্দর্য আছে: পুরো অবস্থা পাঠান, ফল নিশ্চিত। কিন্তু একটি বাস্তব ফাঁদ আছে, যা API বড় হলেই দেখা দেয়। ধরুন mobile app-এর পুরোনো version পণ্যে পাঁচটি field জানে। Server-এ নতুন field যোগ হলো: description. পুরোনো app পণ্য পড়ল, দাম বদলাল, তারপর পাঁচ field-সহ PUT করল। Server-এর চোখে description পাঠানো হয়নি, তাই সেটি মুছে গেল। কেউ কিছু ভুল করেননি, তবু ডেটা হারাল। আমাদের real run-এ PUT-এর পর ঠিক এই ঘটনাটিই দেখা গেছে।

PATCH এই ঝুঁকি কমায়, কারণ অচেনা field ছোঁয়াই হয় না। তাই ফর্মভিত্তিক সম্পাদনায় PATCH জনপ্রিয়। আবার PATCH-এর নিজের দুর্বলতা আছে। Merge Patch-এ null সংরক্ষিত, তাই null-ই যেখানে অর্থবহ, সেখানে মুশকিল। Array-এর একটি উপাদান বদলাতে পুরো array পাঠাতে হয়।

দুজনের একসঙ্গে সম্পাদনা দুই method-এই সমস্যা। করিম আর রহিমা একই পণ্য খুললেন, stock ১০। করিম ২ বিক্রি করে ৮ লিখলেন, রহিমা ৩ বিক্রি করে ৭ লিখলেন। শেষেরটি জিতল, stock ৭, অথচ হওয়ার কথা ৫। একে বলে lost update. দুই প্রতিকার: বদলের সময় যাচাই (ETag আর If-Match, যাতে পুরোনো version-এর ওপর লেখা আটকে যায়), আর মানটি লেখার বদলে ঘটনা পাঠানো, যেমন অর্ডার, যাতে server নিজে stock কমায়। শেষ পথটিই “বাজারঘর” নিয়েছে। Race condition-এর ডেটাবেস দিক পর্ব ৪৩-এ।

মূল উপলব্ধি: প্রশ্নটি “PUT না PATCH” নয়। প্রশ্ন হলো, এই ডেটা কি client-এর মালিকানায়, নাকি server নিয়ম মেনে বদলায়?

প্র ০৫URL-এ ক্রিয়া কি সবসময়ই ভুল? POST /orders/17/cancel লিখলে কি API “নষ্ট” হয়ে যায়?

না। “URL-এ ক্রিয়া নয়” একটি ভালো অভ্যাস, কিন্তু প্রাকৃতিক নিয়ম নয়। নিয়মটির আসল উদ্দেশ্য ছিল এমন API ঠেকানো, যেখানে /getUser, /fetchUser, /readUser পাশাপাশি থাকে, আর method-এর অর্থ হারিয়ে যায়।

কিন্তু সব কাজ CRUD-এ পড়ে না। অনুসন্ধান, হিসাব, পাসওয়ার্ড রিসেট, অর্ডার বাতিল: এগুলো কাজ। জোর করে বিশেষ্য বানালে কখনো ভালো ফল আসে। “cancellation”-এর ডেটা আছে, ইতিহাস আছে, তাই নামটি অর্থবহ। আবার কখনো বিশেষ্যটি শুধু ভান, যেমন POST /api/password-reset-requests, যদিও সেটিও খারাপ নয়।

প্রকাশ্য গাইডগুলোও এক সুরে কথা বলে না। উদাহরণ হিসেবে Google-এর API Improvement Proposals (AIP-136) standard method পছন্দ করতে বলে, তবে যা সেগুলোতে ধরে না তার জন্য POST …:cancel ধাঁচের custom method রাখে। অন্যদিকে কিছু দল sub-resource পছন্দ করে। আর RPC-ধাঁচের gRPC ও tRPC তো পুরোটাই ক্রিয়াভিত্তিক, এবং ভেতরের service-এ দারুণ চলে।

তাই বিচারের মাপকাঠি ভিন্ন। এই ডাক কি safe কাজ GET-এ, আর বদলের কাজ POST-এ রাখছে? একই API-তে কি একই রীতি চলছে? Client কি অনুমান করতে পারে? Log-এ আর cache-এ কি সমস্যা হচ্ছে না? যদি উত্তর হ্যাঁ, /cancel-এ কোনো লজ্জা নেই। পক্ষান্তরে “শুদ্ধ” PATCH {"status":"cancelled"} ব্যবসার নিয়ম লুকিয়ে ফেলতে পারে।

মূল উপলব্ধি: নিয়ম মুখস্থ না রেখে তার কারণ মনে রাখুন। কারণটি হলো অনুমানযোগ্যতা আর method-এর সঠিক অর্থ, বিশুদ্ধ ব্যাকরণ নয়।

অনুশীলন

হাতে হিসাব, নকশা আর ফল অনুমান। শেষেরটিতে একটি ছোট কমান্ড চালাতে হবে।

  1. পয়সায় হিসাব: কার্টে আছে চাল 42000 পয়সা × ২, ডাল 14000 পয়সা × ৩, তেল 19000 পয়সা × ১। ৫% ছাড় নিচের দিকে গোল করে (floor) বাদ যাবে। মোট, ছাড়, দেয় টাকা বের করুন, আর JSON-এ field কেমন হবে লিখুন।

    মোট = 2 × 42000 + 3 × 14000 + 1 × 19000 = 84000 + 42000 + 19000 = 145000 পয়সা। ছাড় = floor(145000 × 5 / 100) = 7250 পয়সা। দেয় = 145000 − 7250 = 137750 পয়সা, অর্থাৎ ৳১,৩৭৭.৫০। আগে গুণ, পরে ভাগ করলে পূর্ণসংখ্যার হিসাব থাকে পরিষ্কার।

    JSON: {"subtotalPaisa":145000,"discountPaisa":7250,"totalPaisa":137750}. দেখানোর দায়িত্ব client-এর: (137750 / 100).toFixed(2) দেয় 1377.50.

  2. নকশা ও সমালোচনা: “বাজারঘর”-এ ঠিকানা-বই যোগ হবে: একজন ক্রেতার কয়েকটি ঠিকানা, একটি default. প্রস্তাবিত route: POST /addAddress, GET /getAddresses?user=5, POST /setDefaultAddress?id=3, GET /deleteAddress/3. প্রতিটির সমস্যা বলুন আর নতুন নকশা দিন।
    • POST /addAddress: URL-এ ক্রিয়া। নতুন: POST /api/addresses, সঙ্গে 201 আর Location.
    • GET /getAddresses?user=5: ক্রিয়া, আর অন্যের id বসিয়ে অন্যের ঠিকানা দেখার ঝুঁকি। নতুন: GET /api/addresses, ক্রেতা আসবে login থেকে।
    • POST /setDefaultAddress?id=3: নতুন PATCH /api/addresses/3 {"isDefault":true}, বা singleton: PUT /api/default-address {"addressId":3}. দ্বিতীয়টিতে “একটিই default” নিয়মটি নকশায় ধরা থাকে, আর এটি idempotent. দুটিই যুক্তিসংগত, কারণ লিখে রাখুন।
    • GET /deleteAddress/3: GET-এ মোছা, crawler ও CSRF-এর ঝুঁকি। নতুন: DELETE /api/addresses/3, মালিকানা যাচাইসহ।

    পুরোনো অর্ডারে ঠিকানার কপি রাখুন, যেমন অর্ডারে দামের কপি রাখা হয়। ঠিকানা বদলালে পুরোনো রসিদ বদলানো উচিত নয়।

  3. ফল অনুমান করুন: খালি কার্ট, পণ্য ১-এর stock 12, দাম 42000. পরপর চলল: (ক) PUT /api/cart/items/1 {"qty":2} তিনবার (খ) POST /api/orders (গ) আবার POST /api/orders (ঘ) POST /api/orders/1/cancellation (ঙ) একই বাতিল আবার। প্রতিটির status, শেষে stock আর অর্ডারের সংখ্যা কত? বাতিল শুধু placed অবস্থায় হয়, খালি কার্টে অর্ডার হলে 409.

    (ক) 201, 200, 200: qty ২-ই থাকে। (খ) 201, Location: /api/orders/1, stock 10, কার্ট খালি। (গ) 409, কারণ কার্ট খালি, আমাদের run-এর উত্তর {"error":"cart_is_empty"}. (ঘ) 201, stock 12, status cancelled. (ঙ) 409 {"error":"cannot_cancel","status":"cancelled"}.

    শেষে stock 12, অর্ডার ১টি (বাতিল অবস্থায়)। লক্ষ করুন, এখানে POST দ্বিতীয়বারে নতুন অর্ডার বানায়নি, কারণ কার্ট খালি হয়ে গেছে। এটি একটি সৌভাগ্য, idempotency-র নিশ্চয়তা নয়। মাঝে কার্টে অন্য কিছু যোগ হলে আবার নতুন অর্ডার হতো।

  4. কমান্ড দিয়ে যাচাই: আপনার Express 5 project-এ app.use(express.json()) রেখে একটি PATCH route লিখুন, যা req.body ?? "undefined" ফেরত দেয়। curl দিয়ে application/json আর application/merge-patch+json, দুই Content-Type-এ {"stock":1} পাঠান। তারপর সংশোধন করুন।

    আমাদের real run-এ প্রথমটিতে {"body":{"stock":1}}, দ্বিতীয়টিতে {"body":"undefined"}, দুটিতেই status 200. কারণ parser শুধু নিজের চেনা Content-Type পড়ে, বাকিগুলো চুপচাপ ছেড়ে দেয়।

    app.use(express.json({
      type: ["application/json", "application/merge-patch+json"],
    }));

    এরপর দ্বিতীয় কমান্ডেও {"stock":1} আসবে। আমাদের server-এ বাড়তি নিরাপত্তা হিসেবে req.is("application/merge-patch+json") মিথ্যা হলে 415 ফেরত দেওয়া হয়েছে, আর application/json-এ PATCH পাঠিয়ে real run-এ 415 Unsupported Media Type এসেছে। এটি একটি নকশার সিদ্ধান্ত। আপনি চাইলে দুটি type-ই গ্রহণ করতে পারেন, তবে docs-এ লিখে রাখুন।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি নতুন API নকশার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Client কারা আর তাদের কাজ কী, তা লেখা
  2. গল্প থেকে বিশেষ্য বেছে resource ঠিক করা
  3. সম্পর্ক দেখে URL সাজানো
  4. প্রতিটি URL-এ method বসানো
  5. Field, তারিখ, টাকা আর ID-র representation ঠিক করা
  6. কোড লিখে আসল request পাঠিয়ে চুক্তি যাচাই করা

একটি পণ্য মোছার DELETE /api/products/3 দুবার পাঠানো হলো। প্রথমবার 204, দ্বিতীয়বার 404 এলো। DELETE কি তাহলে idempotent রইল?

  • না, কারণ দুই উত্তর আলাদা
  • হ্যাঁ, কারণ দুবারের পরেও server-এর অবস্থা একই
  • শুধু দুবারই 204 দিলে idempotent হবে
  • না, কারণ DELETE ডেটা বদলায়

একটি পণ্যের সব review-এর তালিকা আনার জন্য কোনটি সবচেয়ে প্রচলিত resource-ধাঁচের নকশা?

  • GET /getReviewsOfProduct?id=2
  • POST /api/products/2/reviews/list
  • GET /api/products/2/reviews
  • GET /API/Product_Reviews/2

একটি পণ্য আছে {"name":"Oil","stock":3,"tags":["a","b"]}. JSON Merge Patch হিসেবে পাঠানো হলো {"stock":null,"tags":["c"]}. ফল কী?

  • {"name":"Oil","stock":null,"tags":["c"]}
  • {"name":"Oil","tags":["a","b","c"]}
  • {"tags":["c"]}
  • {"name":"Oil","tags":["c"]}

একটি API-র JSON-এ পণ্যের দাম রাখার সবচেয়ে নিরাপদ রূপ কোনটি?

  • "pricePaisa": 19990
  • "price": 199.9
  • "price": "৳১৯৯.৯০"
  • "price": 19990

POST-এ একটি নতুন অর্ডার তৈরি হলো। Server 201-এর সঙ্গে সাধারণত Location header-এ কী পাঠায়?

  • অর্ডারের বর্তমান status
  • নতুন অর্ডারের URL, যেমন /api/orders/17
  • ক্রেতার বাসার ঠিকানা
  • Server কোন শহরে আছে

একটি সাইটের ইমেইলে GET /orders/17/cancel লিংক আছে। এই নকশার প্রধান ঝুঁকি কী?

  • URL খুব লম্বা হয়ে যায়
  • GET JSON ফেরত দিতে পারে না
  • যন্ত্র নিজে থেকে লিংকটি ডাকলে অর্ডার বাতিল হয়ে যেতে পারে
  • Server অনেক ধীর হয়ে যায়

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. API একটি চুক্তি; URL-এ বিশেষ্য, method-এ ক্রিয়া, নাম বহুবচন, ছোট হাতের, hyphen.
  2. GET safe, PUT আর DELETE idempotent, POST কোনোটিই নয়। বদলের কাজ কখনো GET-এ নয়।
  3. PUT পুরো বসায়, PATCH অংশ বদলায়; Merge Patch-এ null মোছে, array পুরো বদলায়।
  4. তালিকা nested, একক জিনিস flat; অবস্থা বদলের কাজ POST, আর একটি API-তে একই রীতি।
  5. টাকা পূর্ণসংখ্যা পয়সায়, তারিখ ISO 8601, তৈরি হলে 201 আর Location.

পরের পর্বে (পর্ব ৩৩ · Status code আর error format): আজ আমরা 409 ফেরত দিয়েছি একটি ছোট JSON-সহ। এবার ঠিক করব কোন ভুলে কোন status, error-এর এক ছাঁচ, আর POST-এর retry কীভাবে নিরাপদ করা যায়।

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

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান