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

Express — route, params, query আর router

Express 5 — Routes, Params, Query Strings and Routers

Taught in Bangla, this lesson moves from raw node:http to Express 5: installing it and what changed in version 5 such as the new wildcard and optional-parameter syntax, routes as method and path pairs, data from req.params, req.query and req.body, sending responses with the right REST status codes, why route order matters, splitting files with express.Router, the Bazarghar products and orders API, and testing it with supertest without opening a port.

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

  • Raw node:http-এর বদলে framework কেন নেওয়া হয়, আর তার দাম কী
  • Express 5 ইনস্টল করে প্রথম server চালানো, আর Express 4-এর অভ্যাসের কোনগুলো এখন ভাঙে
  • Route params, query string আর req.body পড়া, ও string থেকে সংখ্যায় নিরাপদে যাওয়া
  • “বাজারঘর”-এর products আর orders API বানিয়ে curl ও supertest দিয়ে যাচাই করা
  • Route order-এর ফাঁদ চেনা, express.Router দিয়ে কোড ভাগ করা

১ · Raw node:http থেকে framework-এ কেন

পর্ব ২৪-এ আপনি server নিজের হাতে বানিয়েছেন। node:http আপনাকে দেয় একটি ফাংশন, যেটি প্রতিটি request-এ চলে। বাকি সব আপনার দায়িত্ব। “বাজারঘর”-এর মাত্র দুটি ঠিকানা হাতে লিখলে কেমন দেখায়, দেখুন।

const server = http.createServer((req, res) => {
  const url = new URL(req.url, "http://localhost");

  if (req.method === "GET" && url.pathname === "/api/products") {
    res.writeHead(200, { "Content-Type": "application/json" });
    return res.end(JSON.stringify(products));
  }

  const match = url.pathname.match(/^\/api\/products\/(\d+)$/);
  if (req.method === "GET" && match) {
    // id বের করা, খোঁজা, না পেলে 404, পেলে আবার writeHead ...
  }

  // POST হলে body টুকরো টুকরো জমানো, JSON.parse, তার ভুল ধরা ...
  res.writeHead(404);
  res.end();
});

কোড ভুল নয়। সমস্যা হলো বৃদ্ধি। পঞ্চাশটি ঠিকানা হলে এটি একটি লম্বা if-এর সিঁড়ি, যেখানে প্রতিটি ধাপে একই কাজ বারবার লেখা। Express এই বারবার লেখা কাজগুলো নিজের কাঁধে নেয়। সে কোনো জাদু নয়, node:http-এর ওপর একটি পাতলা স্তর। ভেতরে সেই একই createServer চলে।

ডাকঘরের বাছাই ঘর ভাবুন। একজন পিয়ন একা প্রতিটি চিঠি হাতে নিয়ে মনে মনে ভাবতে পারেন, “এটি ঢাকার, এটি রাজশাহীর।” চিঠি কম হলে চলে। চিঠি বাড়লে দরকার হয় খোপওয়ালা তাক, যেখানে প্রতিটি খোপের গায়ে ঠিকানা লেখা। Express সেই তাক। আপনি শুধু বলে দেন কোন ঠিকানার চিঠি কোন খোপে যাবে। প্রতিটি চিঠি খুলে পড়ার কাজ আগের মতোই আপনার।

কাজnode:httpExpress
ঠিকানা মেলানোif আর regex হাতেapp.get("/api/products/:id", …)
Path-এর মান পড়াmatch group থেকে নিজে তোলাreq.params.id
Query পড়াnew URL(…)req.query
JSON body পড়াটুকরো জমিয়ে JSON.parseexpress.json(), তারপর req.body
JSON উত্তরwriteHead + stringify + endres.json(data)
কোনো মিল না পেলেনিজে লিখতে হয়স্বয়ংক্রিয় 404

Framework-এর দামও আছে: একটি বাড়তি dependency, তার version বদল, আর কিছু নিয়ম যেগুলো আপনি লেখেননি অথচ মানতে হয়। এই পর্বের অর্ধেকটাই সেই নিয়ম জানার জন্য। তবে একটি বা দুটি ঠিকানার ছোট কাজে framework না নিলেও চলে। কেন framework লাগে তার বড় আলোচনা আছে পর্ব ০১-এ।

Express শুরু করেন TJ Holowaychuk, ২০১০ সালের দিকে। Node.js তখন একেবারে নতুন (২০০৯), আর Ruby-র Sinatra-র মতো ছোট, হালকা framework-এর ধারণা থেকে এটি অনুপ্রাণিত বলে সাধারণত বলা হয়। Express 4 আসে ২০১৪ সালে। Express 5-এর release pull request খোলা হয় ২০১৪ সালের জুলাইয়ে, আর ৫.০ প্রকাশ পায় প্রায় দশ বছর পরে, ২০২৪ সালে। ২০২৫ সালের ৩১ মার্চ ৫.১ npm-এর latest tag পায়।

২ · Express 5 — ইনস্টল, প্রথম server আর যা বদলেছে

লেখার সময়ে npm view express version দেখায় 5.3.0. Express 5 চায় Node.js 18 বা তার বেশি। পর্ব ০৫-এর মতো একটি নতুন ফোল্ডারে শুরু করি। এটি এই পর্বের জন্য চালানো সত্যিকারের run, Node 24 আর Express 5.3.0-এ।

$ mkdir bazarghar-api && cd bazarghar-api
$ npm init -y
$ npm pkg set type=module
$ npm install express

added 65 packages, and audited 66 packages in 3s
found 0 vulnerabilities
import express from "express";

const app = express();
const PORT = Number(process.env.PORT ?? 3000);

app.get("/", (req, res) => {
  res.send("Bazarghar API is running");
});

app.listen(PORT, "127.0.0.1", (error) => {
  if (error) throw error;
  console.log(`listening on http://127.0.0.1:${PORT}`);
});

একটি terminal-এ node server.js চালিয়ে অন্যটিতে curl দিয়ে ডাকুন। সত্যিকারের run-এ একটি আলাদা port ব্যবহার করা হয়েছিল। পড়ার সুবিধায় এই পর্বের সব transcript-এ 3000 লেখা হলো, বাকি সবকিছু হুবহু। -i দিলে header-ও দেখা যায়।

$ curl -i localhost:3000/
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 24
ETag: W/"18-Gt1SeND5MlxOThw+hbRxxDLS1EE"
Date: Sun, 01 Nov 2026 16:36:31 GMT
Connection: keep-alive

Bazarghar API is running

তিন লাইনের কোডে Express নিজে থেকেই status, Content-Type, Content-Length আর একটি ETag বসিয়েছে। app.listen-এর তৃতীয় argument লক্ষ করুন। Express 5-এ EADDRINUSE-এর মতো ভুল এই callback-এ আসে। তাই error না দেখলে port আটকে থাকলেও server “চলছে” বলে মনে হতে পারে।

Windows-এ curl: Windows PowerShell 5.1-এ curl নামটি আসলে Invoke-WebRequest-এর অন্য নাম। curl.exe লিখুন, বা Git Bash ব্যবহার করুন। JSON-এর উদ্ধৃতি চিহ্নও শেলভেদে আলাদাভাবে লিখতে হয়।

Express 4-এর অভ্যাস যেগুলো এখন ভাঙে

পুরোনো টিউটোরিয়াল আর AI-এর লেখা অনেক কোড Express 4-এর। অফিসিয়াল migration গাইড মিলিয়ে নিচের প্রতিটি সারি আমরা নিজে চালিয়ে দেখেছি। প্রথম চারটি সারি সবচেয়ে বিপজ্জনক দুই ধরনের: কিছু ভুল server শুরুর সময়েই থামিয়ে দেয়, আর কিছু ভুল কোনো error ছাড়াই চুপচাপ ভুল ফল দেয়।

Express 4-এর লেখাExpress 5.3.0-তে যা ঘটলএখন লিখুন
app.get("/*", h)শুরুতেই TypeError: Missing parameter name"/*splat", রুটসহ চাইলে "/{*splat}"
"/products/:id?"শুরুতেই TypeError: Unexpected ?"/products{/:id}"
"/(a|b)" বা "/a+"শুরুতেই TypeError: Unexpected (পথের array: ["/a", "/b"]
res.send(201)চুপচাপ status 200, body লেখা 201res.sendStatus(201)
res.json(obj, 201)চুপচাপ status 200res.status(201).json(obj)
res.redirect("back")Location: back নামের ভুল ঠিকানায় পাঠায়res.redirect(req.get("Referrer") || "/")
app.del(), req.param()এখন আর নেই, ডাকলে TypeErrorapp.delete(), আর req.params, req.query বা req.body
JSON parser ছাড়া req.bodyআগে ছিল {}, এখন undefinedexpress.json() বসান, আর req.body ?? {}
req.query.page = 1কোনো error নেই, মানটিও থাকে নানিজের আলাদা variable রাখুন

Regex বাদ পড়ল কেন? কারণটি নিরাপত্তা। খারাপভাবে লেখা regex কিছু ইনপুটে বিপুল সময় নিতে পারে, যাকে বলে ReDoS. Express 5 নিচের path-to-regexp library বদলে সেই পথ সংকুচিত করেছে। আমাদের project-এ এর version ছিল 8.4.2. তাই /:id(\d+) লেখার বদলে id-র ধরন handler-এ বা validation স্তরে যাচাই করা হয়।

Wildcard আর ঐচ্ছিক অংশের সত্যিকারের ফল দেখুন। Wildcard-এর মান একটি array, string নয়। আর catch-all ছাড়া মেলেনি এমন ঐচ্ছিক param-এর key-ই থাকে না।

app.get("/files/*path", …)        GET /files/a/b.png   {"path":["a","b.png"]}
app.get("/docs{/:slug}", …)       GET /docs            {}
                                  GET /docs/intro      {"slug":"intro"}
app.get("/img/:file{.:ext}", …)   GET /img/photo       {"file":"photo"}
                                  GET /img/photo.png   {"file":"photo","ext":"png"}

Promise rejection: Express 5-এ async handler-এর ভেতরের throw বা rejected promise নিজে থেকে error পথে যায়, যেন next(err) ডাকা হয়েছে। Express 4-এ এর জন্য বাড়তি try/catch লাগত। আমাদের run-এ throw new Error("db is down") দিয়ে client পেল 500, server থামেনি। Error handler নিজে লেখার পুরো গল্প পর্ব ২৮ (Error handling)-এর।

Express 4 কি এখনো চলে? npm-এ latest-4 tag-এ 4.22.3 আছে, তাই পুরোনো project ভাঙবে না। তবে ২০২৫ সালের অফিসিয়াল পোস্ট বলেছিল, 4.x-এর জীবনের শেষ ২০২৬ সালের অক্টোবরের আগে হবে না। এটি সর্বনিম্ন তারিখ, নির্দিষ্ট তারিখ নয়। আজকের অবস্থা expressjs.com-এ দেখে নিন। নতুন project-এ Express 5 নিন।

৩ · Route — method আর path-এর জোড়া

একটি route তিনটি জিনিস একসাথে বাঁধে: HTTP method, path, আর একটি ফাংশন। সেই ফাংশনকে বলে route handler. Handler পায় দুটি বস্তু: req (যা এসেছে) আর res (যা পাঠাবেন)।

app.get("/api/products", listProducts);        // পড়া
app.get("/api/products/:id", getProduct);      // একটি পড়া
app.post("/api/products", createProduct);      // নতুন তৈরি
app.put("/api/products/:id", replaceProduct);  // পুরোটা বদল
app.patch("/api/products/:id", updateProduct); // কিছু অংশ বদল
app.delete("/api/products/:id", removeProduct);// মুছে ফেলা

একই path, ভিন্ন method মানে ভিন্ন route. এই সারিগুলোই মিলে একটি CRUD endpoint-এর সেট: Create, Read, Update, Delete. একই path বারবার না লিখতে চাইলে app.route("/api/products/:id").get(…).put(…) লেখা যায়।

Express-এর ভেতরে এই route-গুলো কীভাবে মেলে? সেটি বুঝতে নিজেই একটি ছোট router বানিয়ে দেখি। নিচের কোড আসল Express নয়, শেখার জন্য বানানো একটি মডেল, কারণ এই পাতায় Express import করা যায় না। ধারণাটি কিন্তু একই: path থেকে regex, তালিকায় একে একে মেলানো, আর param-গুলো req.params-এ বসানো।

// শেখার মডেল: আসল Express নয়, ভেতরের ধারণার ছোট রূপ
const http = require("node:http");
const routes = [];
function add(method, path, handler) {
  const names = [];
  const src = path.replace(/:(\w+)/g, (_, name) => { names.push(name); return "([^/]+)"; });
  routes.push({ method, re: new RegExp(`^${src}/?$`), names, handler });
}
add("GET", "/api/products", (req, res) => res.json({ list: "all" }));
add("GET", "/api/products/:id", (req, res) => res.json(req.params));
add("GET", "/api/orders/:oid/items/:iid", (req, res) => res.json(req.params));

const server = http.createServer((req, res) => {
  res.json = (data, code = 200) => {
    res.writeHead(code, { "Content-Type": "application/json" });
    res.end(JSON.stringify(data));
  };
  const { pathname } = new URL(req.url, "http://localhost");
  for (const r of routes) {
    const m = pathname.match(r.re);
    if (m && r.method === req.method) {
      req.params = Object.fromEntries(r.names.map((n, i) => [n, m[i + 1]]));
      return r.handler(req, res);
    }
  }
  res.json({ error: "no route" }, 404);
});
server.listen(0, "127.0.0.1", async () => {
  const base = `http://127.0.0.1:${server.address().port}`;
  for (const p of ["/api/products", "/api/products/7", "/api/orders/3/items/9", "/api/carts"]) {
    const r = await fetch(base + p);
    console.log(r.status, p, await r.text());
  }
  server.close();
});

আউটপুটে 200 /api/products/7 {"id":"7"} আসে, আর /api/orders/3/items/9-এ {"oid":"3","iid":"9"}. লক্ষ করুন, "7" উদ্ধৃতির ভেতরে, কারণ URL থেকে আসা মান সবসময় string. শেষের /api/carts কোনো route-এ মেলেনি, তাই 404 /api/carts {"error":"no route"}. res.json-ও এখানে আমরা নিজেই res-এর গায়ে বসিয়েছি। Express-ও সারমর্মে একই কাজ করে, সে req আর res-এ অনেক সুবিধাজনক ফাংশন যোগ করে দেয়।

৪ · ডেটা আসে তিন জায়গা থেকে — params, query আর body

একটি পার্সেল ভাবুন। ঠিকানার অংশ হলো params: “ধানমন্ডি, বাড়ি ৭”। ঠিকানা ছাড়া এটি পৌঁছাবেই না। খামের ওপরে লেখা বাড়তি নির্দেশ হলো query: “সন্ধ্যার পরে দেবেন”। না থাকলেও পার্সেল পৌঁছায়। আর খামের ভেতরের জিনিস হলো body: পথে কেউ সেটি পড়ার কথা নয়, শুধু প্রাপক খুলবেন।

উৎসকোথায় থাকেউদাহরণকখন ব্যবহার
req.paramsPath-এর ভেতরে/products/2: { id: "2" }কোন জিনিসটি, তা চেনাতে
req.query?-এর পরে?maxPrice=300: { maxPrice: "300" }Filter, sort, page
req.bodyHTTP body-তে{"qty":2}: { qty: 2 }নতুন বা বদলানো ডেটা

Params — সব string, আর Number()-এর ছোট ফাঁদ

পর্ব ২০-এ browser-এ route params দেখেছেন। Server-এও একই ধারণা। সবচেয়ে সাধারণ ভুলটি হলো p.id === req.params.id লেখা। ডানে string "2", বাঁয়ে সংখ্যা 2, তাই কখনোই মিলবে না, আর প্রতিটি পণ্য 404 দেবে। সমাধান Number(req.params.id). তবে এই রূপান্তরও পুরো পাহারা নয়। আমাদের run-এ "2.0", "02" আর "2e0" তিনটিই 2 হয়ে পণ্য ২-ই খুঁজে পেয়েছে। আর Number("abc") হলো NaN, যা কোনো কিছুর সমান নয়, তাই ফল 404. কড়া যাচাই পর্ব ২৭-এর কাজ, সেখানে Zod আসবে।

Query string — URL যা দিচ্ছে আর দিচ্ছে না

Query string-এর সব মানও string. না থাকলে পাবেন null বা undefined. Express ছাড়াই browser আর Node-এর নিজস্ব URL দিয়ে এটি পরখ করে দেখুন।

const link = "/api/products?q=%E0%A6%9A%E0%A6%BE%E0%A6%B2&maxPrice=300&tag=new&tag=sale&page=abc";
const url = new URL(link, "http://localhost");
const p = url.searchParams;

console.log("path    :", url.pathname);
console.log("q       :", p.get("q"));
console.log("maxPrice:", p.get("maxPrice"), typeof p.get("maxPrice"));
console.log("tag     :", p.get("tag"), p.getAll("tag"));
console.log("color   :", p.get("color"));

// সংখ্যা চাইলে নিজেকেই বদলাতে হয়, আর ভুলটাও ধরতে হয়
console.log("Number(page):", Number(p.get("page")));
console.log("Number(null):", Number(p.get("color")));

function pageNumber(text, fallback = 1) {
  const n = Number(text);
  return Number.isInteger(n) && n >= 1 ? n : fallback;
}
console.log(["2", "abc", "-1", "2.5", null].map((t) => pageNumber(t)));

// উল্টো দিক: বস্তু থেকে নিরাপদ query string
const qs = new URLSearchParams({ q: "চাল ডাল", maxPrice: 300 });
console.log(qs.toString());

%E0%A6%9A… decode হয়ে q : চাল হলো। maxPrice: 300 string লাইনটি মূল শিক্ষা: দেখতে সংখ্যা, আসলে লেখা। tag-এর ক্ষেত্রে get শুধু প্রথমটি new দিল, getAll দিল [ 'new', 'sale' ]. সবচেয়ে সূক্ষ্ম লাইন Number(null): 0: না থাকা মান শূন্য হয়ে গেল, যা maxPrice-এর ক্ষেত্রে “সব পণ্য বাদ” অর্থ দিত। তাই আমাদের pageNumber ফল দিল [ 2, 1, 1, 1, 1 ]: শুধু ভালো মানটিই থাকল, বাকিগুলো ঠিক করা মানে ফিরল। শেষ লাইনে স্পেস হলো +, বাংলা percent-encoded হলো। ?-এর পরের অংশ হাতে জোড়া না দিয়ে URLSearchParams ব্যবহার করলে এই encoding ভুল হয় না।

Express 5-এ req.query-র চেহারা প্রায় একই, তবে ডিফল্ট parser “simple”. আমাদের run-এর ফল দেখুন। পুনরাবৃত্ত key array হয়, কিন্তু বন্ধনীর লেখা আর nested object হয় না। Express 4-এ extended ছিল ডিফল্ট। দরকার হলে app.set("query parser", "extended") লেখা যায়, তবে ব্যবহারকারীর URL থেকে nested object বানানো মানে আক্রমণের জায়গা বাড়ানো, তাই ডিফল্টেই থাকা ভালো।

GET /q?page=2&q=rice        {"page":"2","q":"rice"}
GET /q?tag=a&tag=b          {"tag":["a","b"]}
GET /q?filter[price]=100    {"filter[price]":"100"}
GET /q?q=miniket+rice       {"q":"miniket rice"}
GET /q                      {}

set("query parser","extended")
GET /q?filter[price]=100    {"filter":{"price":"100"}}

req.body — parser না বসালে খালি

Body একটি stream হিসেবে আসে। কেউ পড়ে JSON না বানানো পর্যন্ত req.body-তে কিছু থাকে না। সেই কাজ করে app.use(express.json()). Middleware-এর পুরো ব্যাখ্যা আছে পর্ব ২৬-এ। আপাতত এটুকুই: লাইনটি route-গুলোর আগে বসে। এটি ছাড়া আমাদের run-এ typeof req.body ছিল undefined, আর destructure করতেই 500:

TypeError: Cannot destructure property 'name' of 'req.body' as it is undefined.

এজন্য আমাদের কোডে req.body ?? {} লেখা। একটি কথা মনে রাখুন: express.json() শুধু Content-Type: application/json দেখলেই কাজ করে। curl-এ -H ভুলে গেলে req.body আবার ফাঁকা থাকবে। আর তিনটি ইনপুটের কোনোটিই বিশ্বাসযোগ্য নয়, কারণ সবই client পাঠায়। যাচাই ছাড়া database query-তে বসানোর বিপদ পড়ুন Injection প্রতিরোধ-এ।

৫ · উত্তর পাঠানো আর REST-এর status code

Handler-এর কাজ শেষ হয় একটি উত্তর পাঠিয়ে। উত্তর না পাঠালে client অপেক্ষা করতেই থাকে। কাজের ফাংশনগুলো কয়েকটিই। ডানের কলামের আচরণ আমাদের run থেকে নেওয়া।

লেখাকাজRun-এ পাওয়া ফল
res.json(obj)JSON পাঠায়application/json, status 200
res.status(201).json(obj)আগে status, তারপর উত্তরStatus 201
res.send("hello")Text পাঠায়text/html; charset=utf-8
res.sendStatus(201)শুধু statusBody Created
res.status(204).end()Body ছাড়া শেষStatus 204, কোনো body নেই
res.redirect("/t")অন্য ঠিকানায় পাঠায়302, Location: /t
res.redirect(301, "/api/products")স্থায়ী redirect301 Moved Permanently

res.status() নিজে কিছু পাঠায় না, শুধু সংখ্যাটি বসিয়ে res-কেই ফেরত দেয়। সেজন্যই শিকলের মতো লেখা যায়। Express 5 এখন এটি কড়াভাবে দেখে। res.status("201") দিলে TypeError: Invalid status code, আর res.status(99) দিলে RangeError.

return ভুলে যাওয়ার ফাঁদ: if (!product) res.status(404).json(…); লিখে পরের লাইনে res.json(product) রাখলে একই request-এ দুবার উত্তর পাঠানোর চেষ্টা হয়। আমাদের run-এ client ঠিকই 404 পেল, কিন্তু server-এর log-এ এলো Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client. তাই সবসময় return res.status(404).json(…) লিখুন।

REST: ঠিকানা হলো বিশেষ্য, কাজ বলে method

REST ধারণাটি আসে Roy Fielding-এর ২০০০ সালের গবেষণাপত্র থেকে। তার পূর্ণ তত্ত্ব আছে REST API নকশা-য়, আর এই কোর্সে পর্ব ৩২ ও ৩৩-এ আবার আসবে। এখন দরকার শুধু ব্যবহারিক অংশ: /api/products একটি resource (বিশেষ্য, বহুবচন), আর কী করবেন তা বলে method. /getProducts বা /deleteProduct?id=3 REST নয়।

Statusমানেবাজারঘরে কখন
200 OKহয়ে গেছে, ফলাফল সঙ্গেপণ্যের তালিকা, PATCH-এর পরের পণ্য
201 Createdনতুন resource তৈরিPOST সফল, সঙ্গে Location header
204 No Contentহয়েছে, পাঠানোর কিছু নেইDELETE সফল
400 Bad Requestঅনুরোধের ডেটাই ভুলmaxPrice=cheap, price ছাড়া POST
404 Not Foundএই resource নেইপণ্য ৯৯ খোঁজা
409 Conflictঅনুরোধ ঠিক, কিন্তু বর্তমান অবস্থার সাথে সংঘাতএকই SKU আবার, মজুদের চেয়ে বেশি অর্ডার

4xx বলে দোষ client-এর দিকে, 5xx বলে server-এর দিকে। এই সীমারেখা নিয়ে মাঝেমধ্যে মতভেদ আছে, যেমন বিষয়বস্তু-ভিত্তিক ভুলে কেউ 400, কেউ 422 দেন। দলের ভেতরে একটি নিয়ম ঠিক করে সেটি মানাই আসল কথা। HTTP অংশের ভিত্তি পড়ুন HTTP পর্বে। পরের কোডে একটি পণ্যের পুরো জীবনচক্র চালিয়ে দেখুন, এখানেও Express নেই, কিন্তু status-এর নিয়ম একই।

const http = require("node:http");
const products = [{ id: 1, sku: "RICE-5KG", stock: 12 }];
let nextId = 2;
const server = http.createServer(async (req, res) => {
  let raw = "";
  for await (const chunk of req) raw += chunk;
  const body = raw ? JSON.parse(raw) : {};
  const send = (code, data) => { res.statusCode = code; res.end(data ? JSON.stringify(data) : undefined); };
  const id = Number(req.url.split("/")[2]);
  const i = products.findIndex((p) => p.id === id);
  if (req.method === "POST") {
    if (products.some((p) => p.sku === body.sku)) return send(409, { error: "sku exists" });
    const item = { id: nextId++, sku: body.sku, stock: body.stock };
    products.push(item);
    return send(201, item);
  }
  if (i === -1) return send(404, { error: "not found" });
  if (req.method === "GET") return send(200, products[i]);
  if (req.method === "PATCH") { products[i].stock = body.stock; return send(200, products[i]); }
  if (req.method === "DELETE") { products.splice(i, 1); return send(204); }
});
server.listen(0, "127.0.0.1", async () => {
  const call = async (method, path, data) => {
    const r = await fetch(`http://127.0.0.1:${server.address().port}${path}`, { method, body: data && JSON.stringify(data) });
    console.log(r.status, method, path, await r.text());
  };
  await call("POST", "/products", { sku: "OIL-1L", stock: 3 });
  await call("POST", "/products", { sku: "OIL-1L", stock: 9 });
  await call("GET", "/products/2");
  await call("PATCH", "/products/2", { stock: 1 });
  await call("DELETE", "/products/2");
  await call("DELETE", "/products/2");
  await call("GET", "/products/2");
  server.close();
});

আউটপুটের প্রথম সংখ্যাগুলো পরপর 201, 409, 200, 200, 204, 404, 404. 204 DELETE /products/2 লাইনের শেষে কিছু নেই, কারণ 204-এ body থাকে না। নিচের ধাপগুলোতে এই সাতটি অনুরোধ একটি একটি করে দেখুন।

1POST   /products    sku=OIL-1L stock=32POST   /products    sku=OIL-1L stock=93GET    /products/24PATCH  /products/2  stock=15DELETE /products/26DELETE /products/27GET    /products/2
অনুরোধের পরের অবস্থা
status
পণ্য ২
মোট পণ্য
  1. POST: তৈরি। নতুন SKU, তাই পণ্য তৈরি হলো, id পেল ২, মজুদ ৩। উত্তর 201, আর body-তে তৈরি হওয়া পুরো পণ্য, যাতে client আলাদা করে আবার না চায়।
  2. একই POST আবার: 409. অনুরোধের গঠন ঠিকই আছে, তাই এটি 400 নয়। সমস্যা হলো বর্তমান অবস্থা: এই SKU আগেই আছে। মজুদ ৯ হয়নি, ৩-ই আছে, মোট পণ্যও ২। ব্যর্থ অনুরোধ কিছু বদলায়নি।
  3. GET: পড়া। 200, আর ডেটায় কোনো বদল নেই। GET-কে নিরাপদ (safe) method বলার কারণ এটাই।
  4. PATCH: আংশিক বদল। শুধু stock পাঠানো হলো, মান ৩ থেকে ১। SKU অক্ষত। উত্তর 200, সঙ্গে নতুন অবস্থা।
  5. DELETE: 204. পণ্য মোছা হলো, মোট পণ্য ১। পাঠানোর কিছু নেই, তাই body খালি।
  6. আবার DELETE: 404. পণ্য আর নেই। Server-এর অবস্থা আগের ধাপের মতোই রইল, শুধু status আলাদা। Idempotent মানে ঠিক এটি: বারবার করলেও অবস্থা একই থাকে। Status আলাদা হতে পারে।
  7. GET: 404. মুছে ফেলা পণ্য পড়তে চাইলেও একই ফল। পুরো যাত্রায় মাত্র দুটি অনুরোধ (POST আর DELETE) তালিকার আকার বদলেছে, যা উপরের “মোট পণ্য” সারিতে দেখা যায়।

৬ · Route order — যে আগে মেলে সে-ই জেতে

Express কোনো “সেরা মিল” খোঁজে না। পর্ব ২০-এর React Router route-কে নম্বর দিয়ে সবচেয়ে নির্দিষ্টটি বেছে নেয়, Express তা করে না। সে নিবন্ধনের ক্রমে একটি তালিকা ধরে ওপর থেকে নিচে হাঁটে, আর প্রথম যে route উত্তর দেয় সেখানেই থামে। ফলে একটি বিখ্যাত ফাঁদ তৈরি হয়। সত্যিকারের Express 5.3.0-তে চালানো ফল:

app.get("/api/products/:id", …);       // আগে
app.get("/api/products/featured", …);  // পরে

$ GET /api/products/featured
{ handler: ':id', id: 'featured' }

:id যেকোনো একটি অংশে মেলে, featured শব্দেও। তাই দ্বিতীয় route কখনোই চলে না। কোনো error নেই, কোনো warning নেই, শুধু Number("featured") থেকে NaN হয়ে একটি রহস্যময় 404. নিচের মডেলে দুই ক্রম পাশাপাশি দেখুন, আর কয়টি route পরীক্ষা হলো সেটিও গুনুন।

// শেখার মডেল: Express-এর মতোই যে route আগে মেলে, সে-ই জেতে
function toRegex(path) {
  return new RegExp("^" + path.replace(/:\w+/g, "[^/]+") + "/?$");
}
function firstMatch(routes, url) {
  let tried = 0;
  for (const path of routes) {
    tried++;
    if (toRegex(path).test(url)) return `${url} -> ${path} (${tried}টি পরীক্ষা)`;
  }
  return `${url} -> কোনো মিল নেই (${tried}টি পরীক্ষা)`;
}

const wrong = ["/products", "/products/:id", "/products/out-of-stock"];
const right = ["/products", "/products/out-of-stock", "/products/:id"];

for (const [name, routes] of [["ভুল ক্রম", wrong], ["ঠিক ক্রম", right]]) {
  console.log(name);
  for (const url of ["/products/out-of-stock", "/products/7", "/carts"]) {
    console.log("  " + firstMatch(routes, url));
  }
}

ভুল ক্রমে /products/out-of-stock -> /products/:id (2টি পরীক্ষা): ভুল handler-এ চলে গেল। ঠিক ক্রমে /products/out-of-stock -> /products/out-of-stock (2টি পরীক্ষা). দাম সামান্য: /products/7-এর এখন ৩টি পরীক্ষা লাগে, আগে লাগত ২টি। নিয়মটি সহজ: নির্দিষ্ট path আগে, param-ওয়ালা path পরে, catch-all সবার শেষে।

তালিকায় শুধু route থাকে না, middleware-ও থাকে। “বাজারঘর”-এর সত্যিকারের app.js-এ Express-এর ভেতরের তালিকা (app.router.stack) আমরা ছাপিয়ে দেখেছি, ৬টি স্তর আছে। এবার GET /api/products/2 সেই তালিকা ধরে কীভাবে হাঁটে দেখুন।

1app.use(express.json())2app.get("/health", ...)3app.use("/api/products", router)4    router.get("/", ...)5    router.get("/out-of-stock", ...)6    router.get("/:id", ...)7app.use("/api/orders", router)8app.get("/products", ...)9app.use(json 404)
Express যা দেখছে
req.url
মিলল?
পরীক্ষা
req.params
  1. Middleware আগে। express.json()-এর কোনো path বাঁধা নেই, তাই সব request-এ চলে। GET-এ body নেই, সে কিছু না করে পরের স্তরে পাঠাল।
  2. /health মেলেনি। Path আলাদা, তাই handler চলল না। পরীক্ষা ২।
  3. Prefix মিলল। /api/products অংশটি মিলে গেল, Express router-এর ভেতরে ঢুকল। এখন থেকে req.url শুধু /2, কারণ prefix কেটে রাখা হয়েছে।
  4. / মেলেনি। Router-এর নিজের তালিকা শুরু। /2 আর / এক নয়।
  5. /out-of-stock মেলেনি। নির্দিষ্ট path বলে এটি /:id-এর আগে বসানো, আর 2 এর সাথে মেলে না।
  6. /:id মিলল, যাত্রা শেষ। req.params হলো { id: "2" }, handler res.json পাঠাল। মোট ৬টি পরীক্ষা। তালিকার ৭, ৮ আর ৯ নম্বর সারি এই request-এর জন্য চলেইনি। সেজন্য সবচেয়ে ব্যবহৃত route ওপরে রাখা যায়, তবে বাস্তবে তার লাভ খুবই কম।

আরও কয়েকটি মেলানোর নিয়ম (আমাদের run থেকে)

  • বড়-ছোট হাতের অক্ষর আর শেষের /: ডিফল্টে /API/Products/1 আর /api/products/1/ দুটোই মেলে, 200 আসে। app.set("case sensitive routing", true) আর app.set("strict routing", true) দিলে এগুলো 404 দেয়।
  • ভুল method-এ 405 নয়, 404: POST /api/products/1-এ শুধু GET আর DELETE route থাকলে Express দেয় 404, HTML-এ Cannot POST /api/products/1. HTTP-র হিসাবে 405 বেশি ঠিক, সেটি নিজে লিখতে হয় (অনুশীলন ৪ দেখুন)।
  • HEAD আর OPTIONS বিনামূল্যে: GET route থাকলে HEAD নিজে থেকে কাজ করে। OPTIONS-এ Express Allow: DELETE, GET, HEAD ফেরত দিল।
  • ভাঙা encoding: /p/%E0%A6 decode করা যায় না, তাই handler পর্যন্ত না গিয়েই 400 ফেরে।

৭ · express.Router — ফাইল ভাগ আর mount করা

সব route এক ফাইলে রাখলে কয়েকশো লাইনে পৌঁছাতে সময় লাগে না। express.Router হলো একটি “ছোট অ্যাপ”: তার নিজের route তালিকা আছে, কিন্তু সে নিজে কোনো port শোনে না। আপনি তাকে একটি prefix-এর নিচে mount করে দেন, আর তার ভেতরের path-গুলো prefix ছাড়া লেখা হয়।

বড় ডাকঘরে প্রধান বাছাইকারী শুধু প্রথম অংশ দেখেন: “ঢাকা বিভাগ”। চিঠিটি চলে যায় ঢাকা ডেস্কে। ঢাকা ডেস্ক আর “ঢাকা” শব্দ নিয়ে ভাবে না, বাকি ঠিকানা (“ধানমন্ডি, বাড়ি ৭”) দেখে। ঢাকা ডেস্ক উঠে গিয়ে অন্য শহরে বসলেও তার ভেতরের কাজ বদলায় না। Router-ও তেমনি: /api/products বদলে /api/v2/products করলে শুধু একটি লাইন বদলাবে।

বাজারঘর API-র গঠন

app prefix দেখে ভাগ করে, router বাকি path দেখে

বাজারঘর API-র গঠন: app, দুটি router, in-memory ডেটা আর 404 Request app.js productsRouter ordersRouter ডেটা (memory-তে) কোনো মিল নেই: JSON 404 GET /api/products/7 json() → prefix দেখে ভাগ /api/products /api/orders GET / GET /:id POST / PUT PATCH DELETE GET / GET /:id POST / products[] orders[]
ছবি ২৫.১ — প্রতিটি router আলাদা ফাইলে থাকে। App-এর ফাইলটি ছোট থাকে, শুধু কে কোথায় বসবে সেটুকু।

এবার /api/products/7?color=red অনুরোধটি ধাপে ধাপে দেখুন। প্রতিটি মান সত্যিকারের run-এ handler থেকে ছাপানো।

  1. ClientGET …/7?color=redঅনুরোধ এলো
  2. app/api/productsprefix মিলল
  3. Router/7?color=redprefix কাটা
  4. Handler/:idউত্তর
reqএখনো কোনো router ছোঁয়নি
url:         /api/products/7?color=red
originalUrl: /api/products/7?color=red
app.jsএকটি লাইনে mount
app.use("/api/products", productsRouter);
reqRouter-এর ভেতরে
baseUrl:     /api/products
url:         /7?color=red
path:        /7
originalUrl: /api/products/7?color=red
routes/products.jsHandler-এ যা পৌঁছাল
route.path:  /:id
params:      { id: "7" }
query:       { color: "red" }
POST /api/products/2Router-এ মিলল না
404 {"error":"no route for POST /api/products/2"}
  1. অনুরোধ এলো। req.url আর req.originalUrl এখন একই।
  2. Prefix মিলল। app.use পুরো path নয়, শুরুর অংশ মেলায়। তাই /api/products, /api/products/7 সবই এই router-এ আসে।
  3. Prefix কেটে রাখা হলো। req.baseUrl-এ /api/products, আর req.url-এ বাকিটুকু /7?color=red. req.originalUrl অক্ষত থাকে, log-এর জন্য সেটিই কাজের।
  4. Router নিজের তালিকা মেলাল। সে শুধু /7 দেখে, তাই route লেখা হয়েছে /:id. এই ফাইল জানেই না তাকে কোথায় mount করা হয়েছে।
  5. মিল না পেলে? POST /api/products/2 নামের কোনো route router-এ নেই। Router ত্রুটি দেয় না, request-কে বাইরের তালিকায় ফেরত পাঠায়। সে অর্ডার router, /products পেরিয়ে শেষের JSON 404-এ পৌঁছায়, আর req.path-এ আবার পুরো ঠিকানা দেখা যায়।

mergeParams-এর ফাঁদ: app.use("/products/:id/reviews", reviewsRouter) করলে ভেতরের handler-এ id পাওয়া যায় না। আমাদের run-এ req.params ছিল {}. Router তৈরির সময় Router({ mergeParams: true }) দিলে পাওয়া গেল { id: '7' }.

৮ · “বাজারঘর”-এর products আর orders API

এবার সব জোড়া লাগাই। ডেটা থাকবে in-memory, মানে একটি সাধারণ JavaScript array-তে। Server বন্ধ হলেই সব মুছে যাবে। শুরুতে data.js-এ তিনটি পণ্য আছে: চাল, তেল আর চা-পাতা, যার মধ্যে চায়ের মজুদ শূন্য। মডিউল ৬-এ এর জায়গায় আসল database বসবে। ফোল্ডারের গঠন:

bazarghar-api/
  src/
    app.js              app তৈরি ও mount (listen নেই)
    server.js           শুধু app.listen
    data.js             products, orders array
    routes/
      products.js
      orders.js
  test/
    api.test.js
import express from "express";
import { productsRouter } from "./routes/products.js";
import { ordersRouter } from "./routes/orders.js";

export const app = express();

app.use(express.json());                     // req.body ভরে (পর্ব ২৬)

app.get("/health", (req, res) => res.json({ ok: true }));

app.use("/api/products", productsRouter);
app.use("/api/orders", ordersRouter);

// পুরোনো ঠিকানা, স্থায়ীভাবে সরে গেছে
app.get("/products", (req, res) => res.redirect(301, "/api/products"));

// কিছুই মেলেনি: HTML পাতা নয়, JSON উত্তর
app.use((req, res) => {
  res.status(404).json({ error: `no route for ${req.method} ${req.path}` });
});

app.listen ইচ্ছা করেই আলাদা ফাইলে। তাই test-এ port না খুলেই app ব্যবহার করা যায়। ডিফল্ট 404 হলো HTML পাতা (Cannot GET /nothing), আর API-র client সেটি JSON ভেবে পড়তে গেলে ভাঙে। শেষের লাইনটি এজন্যই। এটি অবশ্যই সবার শেষে বসে, কারণ এর পরে কোনো route লিখলে সেটি চলবে না।

import { Router } from "express";
import { db } from "../data.js";

export const productsRouter = Router();

function findProduct(idText) {
  const id = Number(idText);          // params সবসময় string
  return db.products.find((p) => p.id === id);
}

// GET /api/products?q=rice&maxPrice=300
productsRouter.get("/", (req, res) => {
  const { q, maxPrice } = req.query;
  let items = db.products;
  if (q) {
    const needle = String(q).toLowerCase();
    items = items.filter((p) => p.name.toLowerCase().includes(needle));
  }
  if (maxPrice !== undefined) {
    const max = Number(maxPrice);
    if (Number.isNaN(max)) {
      return res.status(400).json({ error: "maxPrice must be a number" });
    }
    items = items.filter((p) => p.price <= max);
  }
  res.json({ count: items.length, items });
});

// নির্দিষ্ট path, তাই /:id-এর আগে
productsRouter.get("/out-of-stock", (req, res) => {
  res.json(db.products.filter((p) => p.stock === 0));
});

productsRouter.get("/:id", (req, res) => {
  const product = findProduct(req.params.id);
  if (!product) return res.status(404).json({ error: "product not found" });
  res.json(product);
});

productsRouter.post("/", (req, res) => {
  const { sku, name, price, stock = 0 } = req.body ?? {};
  if (!sku || !name || typeof price !== "number") {
    return res.status(400).json({ error: "sku, name and numeric price are required" });
  }
  if (db.products.some((p) => p.sku === sku)) {
    return res.status(409).json({ error: `sku ${sku} already exists` });
  }
  const product = { id: db.nextProductId++, sku, name, price, stock };
  db.products.push(product);
  res.status(201).location(`/api/products/${product.id}`).json(product);
});

// PATCH: শুধু পাঠানো field বদলায়
productsRouter.patch("/:id", (req, res) => {
  const product = findProduct(req.params.id);
  if (!product) return res.status(404).json({ error: "product not found" });
  const { price, stock } = req.body ?? {};
  if (price !== undefined) product.price = price;
  if (stock !== undefined) product.stock = stock;
  res.json(product);
});

productsRouter.delete("/:id", (req, res) => {
  const index = db.products.findIndex((p) => p.id === Number(req.params.id));
  if (index === -1) return res.status(404).json({ error: "product not found" });
  db.products.splice(index, 1);
  res.status(204).end();
});

// PUT পুরো পণ্য বদলায়: সব field লাগে, নইলে 400

PATCH-এ price আর stock আলাদা করে তোলা হয়েছে, Object.assign(product, req.body) লেখা হয়নি। এখানে যাচাইটি ইচ্ছা করেই সংক্ষিপ্ত, পূর্ণ schema আছে পর্ব ২৭-এ। Order-এর route-টি এবার দেখুন, এখানে 409-এর আসল ব্যবসায়িক অর্থ আছে।

// POST /api/orders  { "productId": 2, "qty": 2 }
ordersRouter.post("/", (req, res) => {
  const { productId, qty } = req.body ?? {};
  if (!Number.isInteger(qty) || qty < 1) {
    return res.status(400).json({ error: "qty must be a positive integer" });
  }
  const product = db.products.find((p) => p.id === productId);
  if (!product) return res.status(404).json({ error: "product not found" });
  if (product.stock < qty) {
    return res.status(409).json({ error: "not enough stock", available: product.stock });
  }
  product.stock -= qty;
  const order = {
    id: db.nextOrderId++, productId, qty,
    total: product.price * qty, status: "placed",
  };
  db.orders.push(order);
  res.status(201).location(`/api/orders/${order.id}`).json(order);
});

লক্ষ করুন, দাম নেওয়া হচ্ছে server-এর ডেটা থেকে: product.price * qty. Client যদি total পাঠাত, যে কেউ ৳১ লিখে দিত। Handler-এ ব্যবসার নিয়ম ঢুকে পড়ছে, সেটিও দেখা যাচ্ছে। পর্ব ২৯-এ এটি service-এ সরানো হবে, পর্ব ০৪-এর fat controller সমস্যার কথা মনে করুন।

curl দিয়ে সত্যিকারের run

Express 5.3.0, Node 24. একই server-এ পরপর চালানো, শুধু লম্বা header ছেঁটে দেওয়া হয়েছে।

$ curl "localhost:3000/api/products?q=oil"
{"count":1,"items":[{"id":2,"sku":"OIL-1L","name":"Soybean oil 1L","price":190,"stock":3}]}

$ curl "localhost:3000/api/products?maxPrice=cheap"
{"error":"maxPrice must be a number"}

$ curl localhost:3000/api/products/out-of-stock
[{"id":3,"sku":"TEA-400G","name":"Tea leaves 400g","price":260,"stock":0}]

$ curl -i -X POST localhost:3000/api/products -H "Content-Type: application/json" \
       -d '{"sku":"SALT-1KG","name":"Salt 1kg","price":42,"stock":10}'
HTTP/1.1 201 Created
Location: /api/products/4
Content-Type: application/json; charset=utf-8
Content-Length: 65
ETag: W/"41-dQXfm0vfu10bLxNf/8aOv+lCTys"
…
{"id":4,"sku":"SALT-1KG","name":"Salt 1kg","price":42,"stock":10}

$ curl -i -X POST localhost:3000/api/products -H "Content-Type: application/json" \
       -d '{"sku":"SALT-1KG","name":"Salt again","price":45}'
HTTP/1.1 409 Conflict
…
{"error":"sku SALT-1KG already exists"}

$ curl -X PATCH localhost:3000/api/products/4 -H "Content-Type: application/json" \
       -d '{"price":40}'
{"id":4,"sku":"SALT-1KG","name":"Salt 1kg","price":40,"stock":10}

$ curl -X PUT localhost:3000/api/products/4 -H "Content-Type: application/json" \
       -d '{"sku":"SALT-1KG","name":"Salt 1kg","price":44,"stock":8}'
{"id":4,"sku":"SALT-1KG","name":"Salt 1kg","price":44,"stock":8}

$ curl -i -X DELETE localhost:3000/api/products/4
HTTP/1.1 204 No Content
…

$ curl -i -X POST localhost:3000/api/orders -H "Content-Type: application/json" \
       -d '{"productId":2,"qty":2}'
HTTP/1.1 201 Created
Location: /api/orders/1
…
{"id":1,"productId":2,"qty":2,"total":380,"status":"placed"}

$ curl -i -X POST localhost:3000/api/orders -H "Content-Type: application/json" \
       -d '{"productId":2,"qty":5}'
HTTP/1.1 409 Conflict
…
{"error":"not enough stock","available":1}

$ curl -i localhost:3000/products
HTTP/1.1 301 Moved Permanently
Location: /api/products
…
Moved Permanently. Redirecting to /api/products

হিসাব মিলিয়ে নিন: তেলের দাম ১৯০ টাকা, ২টি অর্ডারে total ৩৮০। শুরুতে মজুদ ছিল ৩, ২টি যাওয়ার পর ১, তাই ৫টির অর্ডারে available: 1.

ভুল JSON আর stack trace: -d '{bad json' পাঠালে express.json() নিজেই 400 দিল, কিন্তু HTML পাতায় পুরো stack trace, এমনকি server-এর ফাইলের path-সহ। আবার async handler-এর একটি throw-এর run-এ NODE_ENV=production সেট করলে client শুধু Internal Server Error দেখল, stack trace দেখল না। তাই production-এ এটি সেট থাকা জরুরি। JSON আকারে error দেওয়ার ব্যবস্থা পর্ব ২৮-এ আসবে।

৯ · supertest দিয়ে যাচাই, আর অন্য framework

প্রতিবার হাতে curl চালানো টেকে না। supertest আপনার app-কে নিজে একটি সাময়িক port-এ চালায়, অনুরোধ পাঠায়, উত্তর মেলায়। এখানে Node-এর নিজস্ব node:test ব্যবহার করা হলো, যাতে বাড়তি কিছু না লাগে। Vitest আসবে পর্ব ৭৩-এ, আর test database-সহ পূর্ণ API test পর্ব ৭৫-এ। লেখার সময়ে supertest-এর version 7.3.1.

npm install -D supertest
import { test } from "node:test";
import assert from "node:assert/strict";
import request from "supertest";
import { app } from "../src/app.js";

test("the query string filters by price", async () => {
  const res = await request(app).get("/api/products?maxPrice=200").expect(200);
  assert.deepEqual(res.body.items.map((p) => p.sku), ["OIL-1L"]);
});

test("the fixed path is not swallowed by /:id", async () => {
  const res = await request(app).get("/api/products/out-of-stock").expect(200);
  assert.equal(res.body[0].sku, "TEA-400G");
});

test("POST creates a product, a second POST conflicts", async () => {
  const body = { sku: "SALT-1KG", name: "Salt 1kg", price: 42, stock: 10 };
  const first = await request(app).post("/api/products").send(body).expect(201);
  assert.equal(first.headers.location, `/api/products/${first.body.id}`);
  await request(app).post("/api/products").send(body).expect(409);
});

// আরও আছে: GET তালিকা, DELETE, আর order-এর 409
$ npm test

> node --test

✔ GET /api/products lists the seed data (21.1732ms)
✔ the query string filters by price (6.3497ms)
✔ the fixed path is not swallowed by /:id (2.3642ms)
✔ POST creates a product, a second POST conflicts (10.5048ms)
✔ DELETE returns 204, then the product is gone (3.7246ms)
✔ an order takes stock, then a large order gets 409 (4.1256ms)
ℹ tests 6
ℹ pass 6
ℹ fail 0
ℹ duration_ms 229.2764

দ্বিতীয় test-টি মূল্যবান। কেউ পরে /out-of-stock route-কে /:id-এর নিচে সরালে এটিই ব্যর্থ হয়ে route order-এর ফাঁদ ধরবে। তবে একটি দুর্বলতাও আছে: সব test একই db array ভাগ করে, তাই test-এর ক্রম বদলালে ফল বদলাতে পারে। পর্ব ৭৫-এ প্রতিটি test-এর জন্য পরিষ্কার ডেটা নিশ্চিত করা হবে। Test-এর তত্ত্ব পড়ুন Test pyramid ও integration-এ।

Express ছাড়া আর কী আছে

Frameworkলেখার সময়েভিন্নতা
Fastify5.12.7Route-এর সাথে JSON schema, plugin ব্যবস্থা, route খোঁজে tree-তে
Hono4.13.16Web standard Request/Response-ভিত্তিক, Node ছাড়াও নানা runtime-এ চলার লক্ষ্য
Koa3.2.1Express-এর সাথে যুক্ত মানুষদের শুরু করা, খুব ছোট কেন্দ্র, router আলাদা package

এগুলো ভালো বিকল্প, তবে কোনটি “সেরা” তা প্রজেক্ট আর দলের ওপর নির্ভর করে। এই কোর্সে Express নেওয়া হয়েছে কারণ এর ধারণা অন্যগুলোতেও কাজে লাগে, আর উদাহরণ, library ও পুরোনো কোড প্রচুর পাওয়া যায়। অন্যের benchmark দেখে সিদ্ধান্ত না নিয়ে নিজের কাজে মেপে দেখুন। Stack বাছাইয়ের ছক আসবে পর্ব ৮৫-এ।

আরও গভীরে: Express ভেতরে আলাদা router package (আমাদের project-এ 2.2.0) ব্যবহার করে, আর প্রতিটি route বা middleware হলো একটি Layer. app.router.stack.length “বাজারঘর”-এ ৬ দেখিয়েছে। খোঁজা রৈখিক, তাই O(n). আমরা ১০০০টি route বানিয়ে একই connection-এ মেপেছি: প্রথম route-এ প্রতি অনুরোধে প্রায় ৪২ µs, শেষ route-এ প্রায় ১৭৪ µs (৩ রাউন্ডের গড়, এক কম্পিউটারের একটি লোকাল run, সাধারণ ফল নয়)। অর্থাৎ প্রতি route-এ (১৭৪ − ৪২) / ৯৯৯ ≈ ০.১৩ µs. Fastify-র মতো framework radix tree ব্যবহার করে, যেখানে খোঁজার সময় route-সংখ্যার ওপর নির্ভর করে না, path-এর দৈর্ঘ্যের ওপর করে। আর ETag-এর দিকে তাকান: W/"41-…"-এর 41 হলো hex, দশমিকে ৬৫, ঠিক Content-Length-এর সমান।

পরীক্ষা ও ইন্টারভিউতে: Params আর query-র পার্থক্য? Params resource চেনায় আর path-এর অংশ, query ঐচ্ছিক শর্ত। PUT আর PATCH? PUT পুরোটা বদলায়, PATCH অংশ। POST আর PUT, কোনটি idempotent? PUT; POST বারবার করলে একাধিক resource হতে পারে। app.use আর app.get? use prefix মেলায় ও সব method নেয়, get পুরো path আর শুধু GET. Express 5-এ async error? Rejected promise নিজে থেকে error middleware-এ যায়।

১০ · AI-এর যুগে — Express কোড যাচাই

Express নিয়ে AI-এর সবচেয়ে বড় ঝুঁকি হলো পুরোনো স্মৃতি। ইন্টারনেটের বেশিরভাগ Express কোড version 4-এর, আর AI সেখান থেকেই শিখেছে। কিছু ভুল server শুরুতেই ধরা পড়ে। অন্যগুলো, যেমন res.json(obj, 201), কোনো শব্দ না করে ভুল status পাঠায়। আর “চলছে” দেখে নিশ্চিন্ত হলে সবচেয়ে বিপজ্জনক ভুলটি থেকে যায়।

AI যা লিখতে পারেসমস্যাকীভাবে ধরবেন
app.get("*", …), "/:id?"Express 5-এ শুরুতেই TypeErrorচালিয়ে দেখা
res.send(201)চুপচাপ 200.expect(201) লেখা test
Object.assign(product, req.body)Mass assignment: client id বা দাম বদলাতে পারে। আমাদের run-এ {"price":1,"id":999,"isAdmin":true} পাঠাতেই সব বসে গেলDiff পড়া, field আলাদা করে তোলা
Client-এর পাঠানো total বিশ্বাসনিজের দাম নিজে ঠিক করা যায়Server-এ হিসাব, test
Login যাচাই ছাড়া DELETEযে কেউ পণ্য মুছতে পারেRoute ধরে ধরে “কে পারবে?” জিজ্ঞেস করা
try/catch দিয়ে সব গিলে 200ভুল লুকানো, client ভাবে সফলব্যর্থতার test লেখা
body-parser installঅপ্রয়োজনীয় dependency, express.json() ভেতরেই আছেpackage.json-এর diff পড়া

এই লেখার Object.assign-এর ঘটনাটি API নিরাপত্তা-র একটি পরিচিত দুর্বলতা। “কে কার ডেটা ছুঁতে পারে” প্রশ্নটি পড়ুন Broken access control-এ।

AI-কে আগে থেকেই version আর সীমা বলে দিন, আর উত্তর যেন যাচাই করা যায় এমনভাবে চান:

আমি Express 5.3 আর Node 24 ব্যবহার করছি, ES module-এ। নিচের route-গুলো দেখে দাও, নতুন কিছু লিখো না: [এখানে আপনার router-এর কোড] ১. Express 4-এর এমন কিছু আছে কি যা Express 5-এ ভাঙে বা চুপচাপ ভুল করে? migration গাইডের কোন অংশ, সেটিও বলো। ২. কোনো route অন্য route-এর ছায়ায় পড়ে যাচ্ছে কি? ৩. প্রতিটি route-এ সম্ভাব্য সব status code-এর তালিকা দাও। ৪. req.body থেকে কোন field সরাসরি বসছে, যা client-এর বদলানো উচিত নয়? ৫. নতুন package যোগ করো না। যা জানো না, সেটি অনুমান না করে "নিশ্চিত নই" বলো।

AI-এর লেখা route হাতে পেলে — যাচাইয়ের চেকলিস্ট

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

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

প্র ০১Express 5 আসতে প্রায় দশ বছর লাগল কেন? এত ধীরে চলা কি দুর্বলতা, না শক্তি?

Express 5-এর release pull request খোলা হয় ২০১৪ সালের জুলাইয়ে, আর অফিসিয়াল ঘোষণা আসে ২০২৪ সালে। এই দীর্ঘ সময়ের কারণ একটি নয়। ২০১০-এর দশকের মাঝামাঝি মূল লেখক সরে যান, প্রকল্পের মালিকানা ও দেখাশোনার পথ বদলায়, আর পরে এটি OpenJS Foundation-এর ছাতার নিচে আসে। স্বেচ্ছাসেবী রক্ষণাবেক্ষণকারীদের সময়ও সীমিত ছিল। এই সময়ে JavaScript নিজেই বদলে গেছে: async/await এসেছে, ES module এসেছে, Node-এর পুরোনো version বাতিল হয়েছে।

দ্বিতীয় কারণটি গভীর। Express অগণিত প্রজেক্টের ভিত্তি, আর এমন package-এ একটি ছোট বদলও বহু মানুষের কোড ভাঙতে পারে। কত প্রজেক্ট, তা সঠিক বলা কঠিন, তবে এর ব্যবহার যে বিপুল, তাতে সন্দেহ নেই। এই কারণেই “স্থির থাকা” নিজেই একটি বৈশিষ্ট্য। ২০২২ সালের একটি পুরোনো Express 4 কোড আজও সাধারণত চলে।

তবে স্থিরতার দামও আছে। ReDoS-এর ঝুঁকি কমাতে Express 5 route-এ regex সরিয়েছে, যা একটি ভাঙনধর্মী বদল। এতদিন async error-এর জন্য বাড়তি wrapper library লিখতে হতো। নতুন framework যেমন Fastify বা Hono শুরু থেকেই আধুনিক নকশা নিতে পেরেছে, কারণ তাদের পিঠে পুরোনো কোডের ভার ছিল না। অফিসিয়াল LTS ঘোষণা (২০২৫) এখন phase ধরে নিয়ম ঠিক করেছে: CURRENT, ACTIVE, MAINTENANCE, তারপর জীবনের শেষ।

বাস্তব শিক্ষাটি এখানে। আপনি যখন কোনো dependency বেছে নেন, শুধু feature নয়, তার রক্ষণাবেক্ষণের গতিও দেখুন। কত ঘন ঘন release হয়? নিরাপত্তা ত্রুটি কত দ্রুত ঠিক হয়? ভাঙনধর্মী বদল কীভাবে জানানো হয়? ব্যাংকের একটি সফটওয়্যারে ধীর আর অনুমানযোগ্য পরিবর্তন প্রায়ই দ্রুত পরিবর্তনের চেয়ে ভালো, আবার ছোট নতুন প্রজেক্টে উল্টোটাও হতে পারে।

মূল উপলব্ধি: জনপ্রিয় ভিত্তির ধীরতা প্রায়ই অবহেলা নয়, লাখো ব্যবহারকারীর কোড না ভাঙার দাম। Library বাছাই মানে তার ভবিষ্যৎ বদলের ধরনও বেছে নেওয়া।

প্র ০২Express route খোঁজে একে একে, Fastify খোঁজে tree-তে। তাহলে কি Express-এর route-সংখ্যা কমানো দরকার?

আগে হিসাব, তারপর মত। আমাদের এক কম্পিউটারের লোকাল run-এ ১০০০ route-এর তালিকায় প্রতি route-এ খরচ দাঁড়িয়েছে প্রায় ০.১৩ µs. ধরুন আপনার API-তে ২০০টি route. সবচেয়ে খারাপ ক্ষেত্রে শেষ route-এ পৌঁছাতে ২০০ × ০.১৩ = ২৬ µs. এবার ধরে নিন একটি database query-তে ২০ ms লাগে, অর্থাৎ ২০,০০০ µs. তাহলে route খোঁজা মোট সময়ের ২৬ / ২০,০০০ = ০.১৩%. সংখ্যাগুলো আপনার যন্ত্রে আলাদা হবে, কিন্তু মাপের পার্থক্যটি বার্তা দেয়: সাধারণ ব্যবসায়িক API-তে route খোঁজা কোনো সমস্যাই নয়।

তাহলে radix tree কেন? Radix tree-তে শব্দের সাধারণ শুরু একই শাখায় থাকে। /api/products আর /api/orders /api/ পর্যন্ত একসাথে চলে, তারপর আলাদা হয়। ফলে খোঁজার সময় route-সংখ্যার বদলে path-এর দৈর্ঘ্যে নির্ভর করে। যেখানে হাজার হাজার route আর খুব কম কাজ, যেমন একটি gateway বা খুব ব্যস্ত proxy, সেখানে এটি অর্থবহ। নকশা হিসেবে এটি সুন্দর, আর Fastify-র find-my-way এই ধরনের router.

তবে রৈখিক তালিকারও একটি সুবিধা আছে: এটি বোঝা সহজ। Middleware আর route একই তালিকায়, যা লিখেছেন সেই ক্রমেই চলে। Tree-ভিত্তিক router ক্রম নিজে ঠিক করে, তাই /products/:id আর /products/featured-এর ফাঁদই থাকে না, তবে তখন middleware ক্রমের জন্য আলাদা ধারণা (hook, plugin) শিখতে হয়।

প্রকৌশলের নিয়ম এখানে স্পষ্ট: আগে profiler দিয়ে সবচেয়ে বড় খরচ খুঁজুন। সাধারণত সেটি database, বাইরের API, বা বড় JSON তৈরি। Route ০.১% কমিয়ে কোনো লাভ নেই, যদি একটি missing index ৯০% সময় খায়। সেই মাপা ও কমানোর গল্প পর্ব ৪২ আর পর্ব ৮৪-তে আছে।

মূল উপলব্ধি: Big-O তত্ত্বে O(n) খারাপ শোনায়, কিন্তু n ছোট আর প্রতি ধাপের খরচ অতি সামান্য হলে সেটি নগণ্য। অনুমান নয়, মাপ দেখে সিদ্ধান্ত নিন।

প্র ০৩একটি জিনিস দ্বিতীয়বার DELETE করলে 404 দেবেন, না 204? Idempotent মানে কি দুবারই একই উত্তর?

এই প্রশ্নে সবচেয়ে সাধারণ ভুল ধারণা হলো idempotent মানে “একই উত্তর”। HTTP-র মান RFC 9110 (২০২২) অনুযায়ী idempotent method মানে একই অনুরোধ একাধিকবার পাঠালে server-এর ওপর উদ্দিষ্ট প্রভাব একবার পাঠানোর সমান। এই সংজ্ঞা server-এর অবস্থা নিয়ে, উত্তরের status নিয়ে নয়। আমাদের CRUD run-এ প্রথম DELETE 204, দ্বিতীয়টি 404, কিন্তু দুবারের পরেই অবস্থা এক: পণ্যটি নেই। তাই এটি সম্পূর্ণ idempotent.

কেন এই ধারণাটি জরুরি? কারণ নেটওয়ার্ক অবিশ্বস্ত। ধরুন বাজারঘরের মোবাইল অ্যাপে কেউ দুর্বল সংযোগে DELETE চাপলেন। অনুরোধ পৌঁছাল, পণ্য মুছল, কিন্তু উত্তর ফেরার পথে সংযোগ কেটে গেল। অ্যাপ জানে না কী হয়েছে। Idempotent হলে সে নিশ্চিন্তে retry করতে পারে। POST তা নয়: অর্ডার দুবার পাঠালে দুটি অর্ডার হয়ে যেতে পারে, যার সমাধান idempotency key, যা পর্ব ৩৩-এ আছে।

এবার 404 না 204? দুই পক্ষেরই যুক্তি আছে। 404-এর পক্ষে: এটি সত্যি কথা বলে, resource নেই, আর ভুল id-তে DELETE-এর ভুলটিও ধরা পড়ে। 204-এর পক্ষে: client-এর লক্ষ্য ছিল “জিনিসটি না থাকুক”, আর সেটিই সত্যি। Retry করা client-কে তখন error নিয়ে ভাবতে হয় না। অনেক API দ্বিতীয় পথ নেয়।

আমরা 404 নিয়েছি, কারণ এটি সহজ আর debug করতে সুবিধা। Client-কে শুধু জানাতে হবে যে retry-এর পর 404 সফলতার মতোই ধরা যায়। আপনি যেটিই নিন, সেটি API-র ডকে লিখুন আর test-এ বেঁধে দিন, কারণ client-এর লেখক অনুমান করে কোড লিখবেন না। দুই দলের মধ্যে এই বোঝাপড়াটুকু না থাকলে একই শব্দের দুই অর্থ নিয়ে ভুল হবে।

মূল উপলব্ধি: Idempotency উত্তরের নয়, server-এর অবস্থার ধর্ম। Status নিয়ে মতভেদ থাকতে পারে, চুক্তিটি স্পষ্ট লেখা থাকাই আসল।

প্র ০৪সবসময় 200 দিয়ে body-তে success: false লিখলে সমস্যা কী? সবই তো কাজ করে।

অনেক পুরোনো API ঠিক এভাবে চলে, আর কাজও করে। তাহলে HTTP status নিয়ে এত কড়াকড়ি কেন? উত্তরটি হলো: আপনার কোড একা নেই। Client আর server-এর মাঝখানে অনেক স্তর status পড়ে নিজের সিদ্ধান্ত নেয়, body পড়ে না।

  • Browser-এর fetch: res.ok শুধু 2xx-এ সত্য। পর্ব ১৯-এর TanStack Query-তে error ধরা এই চিহ্নের ওপর নির্ভর করে, যদি আপনার fetcher সেটি ব্যবহার করে।
  • Monitoring: status code গুনে dashboard তৈরি হয়। সবই 200 হলে সার্ভার পুড়ে গেলেও ত্রুটির হার শূন্য দেখাবে।
  • Cache: 200-এর উত্তর CDN জমিয়ে রাখতে পারে, যা সাময়িক ত্রুটিকেও ছড়িয়ে দিতে পারে।
  • Retry ও load balancer: 503 দেখলে অন্য server-এ চেষ্টা করে; 200 দেখলে থেমে যায়।

বিপরীত দিকের যুক্তিও সৎভাবে দেখা দরকার। GraphQL-এ (পর্ব ৩৬) সাধারণত transport-স্তরে 200 আসে, আর ভুল errors তালিকায় থাকে, কারণ একটি অনুরোধে কয়েকটি অংশ সফল আর কয়েকটি ব্যর্থ হতে পারে। সেই নকশার নিজস্ব কারণ আছে, আর তার monitoring-ও আলাদা। REST-এর নকশায় কিন্তু একটি অনুরোধ মানে একটি ফলাফল।

Status-এর ভেতরেও বিতর্ক আছে: validation ভুলে 400 না 422? মজুদ শেষ হলে 409 না 422? “বাজারঘর”-এ JSON ঠিক থাকলে আর ব্যবসার নিয়মে আটকালে আমরা 409 নিয়েছি, কিন্তু অন্য দল অন্যটি নিতে পারে। ভুল হবে না, যদি পুরো API-তে একই নিয়ম থাকে। Body-তেও একটি যন্ত্র-পাঠযোগ্য কোড থাকা ভালো, যেমন available: 1, যাতে client ব্যবহারকারীকে কাজের বার্তা দেখাতে পারে। Error-এর একই ছাঁচ পর্ব ৩৩-এ আসবে।

মূল উপলব্ধি: HTTP status মানুষের জন্য যতটা, তার চেয়ে বেশি মাঝের যন্ত্রগুলোর জন্য। সঠিক status দিলে browser, cache, monitoring আর retry কোনো বাড়তি কোড ছাড়াই ঠিক আচরণ করে।

প্র ০৫“বাজারঘর”-এর in-memory order কোডে দুজন একসাথে শেষ পণ্যটি কিনতে পারবেন কি? Database এলে কী বদলাবে?

এখনকার কোডে পারবেন না, আর কারণটি সুন্দর। পর্ব ২৪-এর event loop মনে করুন: Node.js একটি thread-এ আপনার JavaScript চালায়। ordersRouter.post-এর ভেতরে if (product.stock < qty) থেকে product.stock -= qty পর্যন্ত কোনো await নেই। তাই এই অংশটি একটানা শেষ হয়, মাঝখানে অন্য request ঢুকতে পারে না। দুজন একই মিলিসেকেন্ডে চাপলেও একজনের handler পুরো শেষ হবে, তারপর অন্যজনের, আর দ্বিতীয়জন পাবেন 409.

এবার ভাবুন database এলে। কোডটি হবে: const p = await findProduct(id), যাচাই, তারপর await updateStock(...). এখন await-এর ফাঁকে event loop অন্য request ধরতে পারে। দুটি handler-ই পড়ল মজুদ ১, দুটিই যাচাই পাস করল, দুটিই অর্ডার করল। মজুদ −১। এটি ক্লাসিক “check-then-act” race condition, আর একই কোড in-memory-তে ঠিক থাকে বলে ভুলটি চোখে পড়ে না। Test-ও সাধারণত এটি ধরে না, কারণ সব পরপর চলে। সমাধান database-এর transaction বা শর্তযুক্ত update, যা পর্ব ৪৩-এ দেখা যাবে, তত্ত্ব আছে DBMS ও SQL কোর্সে।

In-memory-র আরও দুটি সীমা আছে। এক, server restart হলেই সব অর্ডার উধাও। হোটেলের ম্যানেজার যদি হিসাব মুখে মুখে রাখেন, তিনি বাড়ি গেলেই খাতা শেষ। দুই, দুটি process চালালে দুটি আলাদা array হয়। একজন ক্রেতা প্রথমটিতে পণ্য কিনলেন, পরের অনুরোধ load balancer দ্বিতীয়টিতে পাঠাল, সেখানে মজুদ আগের মতোই। ডেটা process-এর ভেতরে থাকলে অনুভূমিকভাবে বড় করা যায় না। তাই server-কে stateless রাখা হয়, আর ডেটা বাইরে, এক জায়গায়।

তাই in-memory ডেটা শেখা আর test-এর জন্য চমৎকার, production-এর জন্য নয়। ভালো দিক হলো, route ও status-এর নকশা একই থাকবে, ভেতরের ডেটার স্তর বদলাবে।

মূল উপলব্ধি: await মানে “এখানে অন্য কেউ ঢুকতে পারে”। Async কোডের প্রতিটি await-এর দুই পাশে আপনার যাচাই এখনো সত্য আছে কি না, এটিই ভাবার প্রশ্ন।

অনুশীলন

কাগজে আগে উত্তর ভাবুন, তারপর মিলিয়ে নিন। সব উত্তর সত্যিকারের Express 5.3.0 run থেকে নেওয়া।

  1. Route-এর খরচের হিসাব: ধরুন প্রতি route মেলানোর পরীক্ষায় ০.১৩ µs লাগে। একটি API-তে ২০০টি route. (ক) শেষ route-এ পৌঁছাতে কত সময়? (খ) গড়ে ১০০টি পরীক্ষা হলে কত? (গ) ২০ ms-এর একটি database query-র তুলনায় (ক) কত শতাংশ? (ঘ) ETag-এর শুরু W/"45-… হলে body কত byte?

    (ক) ২০০ × ০.১৩ = ২৬ µs. (খ) ১০০ × ০.১৩ = ১৩ µs. (গ) ২০ ms = ২০,০০০ µs, তাই ২৬ / ২০,০০০ = ০.০০১৩ = ০.১৩%. (ঘ) 0x45 = ৪ × ১৬ + ৫ = ৬৯ byte. আমাদের run-এ GET /api/products/2-এর ETag ঠিক এমন ছিল, আর If-None-Match পাঠাতেই 304 Not Modified এলো, তাই ৬৯ byte body আবার পাঠাতে হলো না। মনে রাখবেন, server তবু JSON বানিয়ে হিসাব করেছে, বেঁচেছে শুধু নেটওয়ার্কের খরচ।

  2. আউটপুট অনুমান করুন: নিচের কোডে পাঁচটি অনুরোধের status আর body কী হবে? /shop/sale, /shop/tea/reviews, /shop/tea/reviews/1, /SHOP/tea/, /late.
    app.get("/shop/:slug", (q, s) => s.send("A " + q.params.slug));
    app.get("/shop/sale", (q, s) => s.send("B"));
    app.get("/shop/:slug/reviews", (q, s) => s.send("C " + q.params.slug));
    app.use((q, s) => s.status(404).send("D"));
    app.get("/late", (q, s) => s.send("E"));
    /shop/sale          200 A sale
    /shop/tea/reviews   200 C tea
    /shop/tea/reviews/1 404 D
    /SHOP/tea/          200 A tea
    /late               404 D

    B কখনো আসে না, কারণ :slug আগেই sale ধরে ফেলে। :slug একটি মাত্র অংশ ধরে, তাই /shop/tea/reviews প্রথম route-এ মেলে না, আর তৃতীয় route-এ যায়। /SHOP/tea/ মেলে, কারণ ডিফল্টে বড়-ছোট হাতের অক্ষর আর শেষের / ধরা হয় না। আর app.use সব অনুরোধ ধরে নেয় বলে /late-এর route কখনো পৌঁছায় না, E-ও কখনো আসে না।

  3. নকশা সমালোচনা: একজন লিখেছেন: GET /getProducts, POST /deleteProduct?id=3, GET /orders/create?productId=2&qty=1, আর সব উত্তর 200, ভুল হলে {"success":false}. কী কী সমস্যা? নতুন নকশা দিন।
    • URL-এ ক্রিয়া: কাজ বলার দায়িত্ব method-এর। হবে GET /api/products.
    • মোছার কাজে POST আর query: হবে DELETE /api/products/3, সফল হলে 204.
    • GET দিয়ে অর্ডার তৈরি সবচেয়ে বিপজ্জনক: GET-কে safe ধরে নেওয়া হয়। Browser-এর prefetch, crawler বা link preview ওই URL ডাকলেই অর্ডার হয়ে যেতে পারে। হবে POST /api/orders, body-তে ডেটা, সফল হলে 201 আর Location.
    • সবসময় 200: fetch-এর res.ok, monitoring আর cache কিছুই ঠিক কাজ করে না। হবে 400, 404, 409.
  4. নিজে লিখে চালান: Express ভুল method-এ 404 দেয়। একটি router-এ /:id-তে শুধু GET রাখুন, আর অন্য সব method-এ 405 ও Allow header দিন। curl-এর বদলে supertest দিয়ে POST করে দেখুন।
    const router = Router();
    router.route("/:id")
      .get((req, res) => res.json({ id: req.params.id }))
      .all((req, res) => {
        res.set("Allow", "GET, HEAD").status(405).json({ error: "method not allowed" });
      });

    সত্যিকারের run-এ POST দিল 405, Allow: GET, HEAD, body {"error":"method not allowed"}. GET দিল 200 {"id":"1"}, আর HEAD-ও 200, কারণ GET route-টি HEAD-ও ধরে। .all অবশ্যই .get-এর পরে বসবে। আগে বসালে GET-ও 405 পেত, আবারও সেই route order-এর নিয়ম।

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

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

ক্রমPOST /api/orders অনুরোধে Express-এর ভেতরের ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. node:http server অনুরোধ পেল, Express-এর হাতে দিল
  2. express.json() body পড়ে req.body ভরল
  3. /api/orders prefix মিলল, req.url থেকে prefix কাটা হলো
  4. Router-এর POST / route মিলল
  5. Handler মজুদ যাচাই করে res.status(201).json(order) পাঠাল

Express 5-এ app.get("/*", handler) লিখে server চালালে কী হয়?

  • সব path-এ handler চলে
  • Server শুরুর সময়েই TypeError আসে, কারণ wildcard-এর নাম লাগে
  • Route-টি চুপচাপ উপেক্ষিত হয়
  • প্রতিটি অনুরোধে 404 আসে

Express 5-এ res.json({ ok: true }, 201) লিখলে client কোন status পাবে?

  • 201
  • 500
  • 200, দ্বিতীয় argument উপেক্ষিত হয়
  • 204

পরপর নিবন্ধিত দুটি route: app.get("/products/:id", A) আর app.get("/products/sale", B). GET /products/sale-এ কী ঘটবে?

  • A চলবে, req.params.id হবে "sale"
  • B চলবে, কারণ নির্দিষ্ট path অগ্রাধিকার পায়
  • A আর B দুটোই পরপর চলবে
  • Server শুরুতেই conflict error দেবে

URL /api/products?maxPrice=300-এ Express 5-এর req.query.maxPrice-এর মান কী, আর ?maxPrice না থাকলে কী?

  • সংখ্যা 300, না থাকলে 0
  • String "300", না থাকলে ""
  • সংখ্যা 300, না থাকলে null
  • String "300", না থাকলে undefined

একটি router app.use("/api/orders", ordersRouter) দিয়ে mount করা। Router-এর ভেতরের route /:id. GET /api/orders/5?x=1-এ handler-এর ভেতরে req.baseUrl আর req.url কী?

  • baseUrl ফাঁকা, url = /api/orders/5?x=1
  • baseUrl = /api/orders, url = /5?x=1
  • baseUrl = /api/orders, url = /5
  • baseUrl = /:id, url = /5?x=1

একটি শপের API-তে JSON ঠিক আছে, পণ্যও আছে, কিন্তু ক্রেতা ৫টি চেয়েছেন আর মজুদ ১টি। সবচেয়ে উপযুক্ত status কোনটি?

  • 200, body-তে success: false
  • 404
  • 409
  • 500

শব্দকোষ

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

মনে রাখুন

  1. Express node:http-এর ওপর পাতলা স্তর। Route হলো method + path + handler.
  2. Express 5: /*splat, {/:id}, regex নেই; res.status(201).json(); req.body parser ছাড়া undefined; async error নিজে থেকে error পথে যায়।
  3. Params ও query সবসময় string. Body আসে express.json()-এর পরে। সব ইনপুটই অবিশ্বস্ত।
  4. Status চুক্তি: 201 তৈরি, 204 মোছা, 404 নেই, 409 সংঘাত। আগাম উত্তরে return.
  5. ক্রমই নিয়ম: নির্দিষ্ট path আগে, :param পরে, catch-all শেষে। Router prefix কেটে ভেতরে পাঠায়। app আলাদা রাখলে supertest-এ সহজে test হয়।

পরের পর্বে: আজ app.use(express.json()) একটি জাদুর লাইন হয়ে রইল। পর্ব ২৬-এ তার ভেতরে ঢুকব: middleware কী, next() কীভাবে পাইপলাইন বানায়, cors, আর নিজের middleware লেখা।

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

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