RESTful API Development - সম্পূর্ণ গাইড
API (Application Programming Interface) হলো এমন একটি মাধ্যম যার মাধ্যমে ফ্রন্টএন্ড এবং ব্যাকএন্ড একে অপরের সাথে কথা বলে। বর্তমানে ওয়েব ডেভেলপমেন্টে REST (Representational State Transfer) আর্কিটেকচার সবচেয়ে জনপ্রিয়।
REST API কি এবং কেন?
API কি?
ধরুন একটি রেস্টুরেন্টে আপনি খেতে গেছেন:
- আপনি (Client/Frontend): খাবার অর্ডার করছেন
- ওয়েটার (API): আপনার অর্ডার নিয়ে রান্নাঘরে দিচ্ছে এবং খাবার এনে দিচ্ছে
- রান্নাঘর (Server/Backend): খাবার তৈরি করছে
API হলো ওই ওয়েটার - যে দুই পক্ষের মধ্যে যোগাযোগ করায়।
REST কি?
REST (Representational State Transfer) হলো একটা আর্কিটেকচারাল স্টাইল যা HTTP প্রোটোকল ব্যবহার করে।
REST এর মূল ধারণা:
- সবকিছুকে Resource হিসেবে দেখা (Users, Posts, Products)
- প্রতিটি resource এর একটা unique URL (endpoint) আছে
- HTTP methods (GET, POST, PUT, DELETE) দিয়ে resource এর উপর কাজ করা
- JSON/XML ফরম্যাটে ডেটা আদান-প্রদান
REST vs Traditional API
Traditional API (RPC Style):
POST /getUserById
POST /createNewUser
POST /updateUserDetails
POST /deleteUserREST API:
GET /users/:id → Get user
POST /users → Create user
PUT /users/:id → Update user
DELETE /users/:id → Delete userREST অনেক বেশি organized এবং predictable!
১. REST API Principles (6টি মূলনীতি)
একটি API কে সত্যিকারের RESTful হতে হলে ৬টি নিয়ম মেনে চলতে হয়:
ক. Client-Server Architecture
ফ্রন্টএন্ড এবং ব্যাকএন্ড সম্পূর্ণ আলাদা থাকবে।
সুবিধা:
- Frontend এবং Backend আলাদাভাবে ডেভেলপ করা যায়
- Frontend (React, Vue) পরিবর্তন করলে Backend (Express, Django) ঠিক থাকবে
- Multiple frontends (Web, Mobile, Desktop) একই API ব্যবহার করতে পারে
┌─────────────┐ HTTP Request/Response ┌─────────────┐
│ Client │ ◄───────────────────────────────────► │ Server │
│ (React) │ JSON/XML Data │ (Express) │
└─────────────┘ └─────────────┘খ. Stateless
প্রতিটি রিকোয়েস্ট স্বাধীন হবে। সার্ভার ক্লায়েন্টের আগের কোনো রিকোয়েস্টের তথ্য মনে রাখবে না।
Example:
❌ Stateful (Bad):
// Request 1: Login
POST /login → Server stores: user is logged in
// Request 2: Get profile
GET /profile → Server remembers previous login✅ Stateless (Good):
// Every request includes authentication
GET /profile
Headers: {
Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}প্রতিটি রিকোয়েস্টে token/credentials পাঠাতে হবে।
সুবিধা:
- সার্ভার সহজে scale করা যায়
- Load balancing সহজ
- কোন সার্ভার crash করলে অন্যটা কাজ করবে
গ. Cacheable
রেসপন্স ক্যাশ করার ব্যবস্থা থাকবে যাতে পারফরম্যান্স বাড়ে।
app.get('/api/products', (req, res) => {
res.set('Cache-Control', 'public, max-age=300'); // 5 minutes cache
res.json(products);
});সুবিধা:
- নেটওয়ার্ক calls কমে
- Server load কমে
- Response time কমে
ঘ. Uniform Interface
সব resource এক্সেস করার পদ্ধতি একই রকম হবে।
// Users resource
GET /users
POST /users
GET /users/:id
PUT /users/:id
DELETE /users/:id
// Posts resource (same pattern)
GET /posts
POST /posts
GET /posts/:id
PUT /posts/:id
DELETE /posts/:idঙ. Layered System
Client সরাসরি server এর সাথে connect আছে কিনা জানবে না। মাঝখানে proxy, load balancer থাকতে পারে।
Client → API Gateway → Load Balancer → Server 1
→ Server 2
→ Server 3চ. Code on Demand (Optional)
Server চাইলে executable code (JavaScript) পাঠাতে পারে।
// Server sends JavaScript code
{
"data": {...},
"executable": "function() { alert('Hello'); }"
}(তবে এটা rarely ব্যবহার হয়)
২. Resource Naming Conventions (URL Design)
REST API তে URL গুলোকে "Resource" হিসেবে চিন্তা করতে হবে, "Action" হিসেবে নয়।
মূল নিয়মাবলী:
১. Nouns ব্যবহার করুন, Verbs নয়
✅ Good Practice:
GET /users → সব ইউজার আনা
GET /users/123 → নির্দিষ্ট ইউজার আনা
POST /users → নতুন ইউজার তৈরি করা
PUT /users/123 → ইউজার আপডেট করা
DELETE /users/123 → ইউজার ডিলিট করা❌ Bad Practice:
GET /getAllUsers
GET /getUserById/123
POST /createNewUser
POST /updateUser/123
DELETE /deleteUser/123কেন? HTTP method-ই action বলে দেয়, URL এ আবার verb লাগে না।
২. Plural Nouns ব্যবহার করুন
✅ Good:
/users
/products
/orders
/posts❌ Bad:
/user
/product
/order
/postকারণ: Consistency. /users এবং /users/123 দুটোই logical।
৩. Hierarchical Structure (Nested Resources)
// User এর posts
GET /users/123/posts → User 123 এর সব posts
GET /users/123/posts/456 → User 123 এর post 456
// Post এর comments
GET /posts/456/comments → Post 456 এর সব comments
POST /posts/456/comments → Post 456 এ নতুন comment
// User এর orders
GET /users/123/orders → User 123 এর orders
GET /users/123/orders/789 → নির্দিষ্ট orderসতর্কতা: 3 level এর বেশি nested করবেন না:
❌ /users/123/posts/456/comments/789/likes (Too deep!)
✅ /comments/789/likes (Better)৪. Lowercase এবং Hyphens ব্যবহার করুন
✅ Good:
/api/user-profiles
/api/blog-posts
/api/shopping-cart-items❌ Bad:
/api/UserProfiles (camelCase)
/api/user_profiles (snake_case)
/api/BLOG-POSTS (uppercase)৫. File Extensions যোগ করবেন না
✅ Good:
GET /users/123
Accept: application/json❌ Bad:
GET /users/123.json
GET /users/123.xmlকারণ: Content type headers দিয়ে specify করা উচিত।
৬. CRUD Operation এর জন্য Standard Pattern
// Collection endpoints (multiple resources)
GET /resources → List all
POST /resources → Create new
// Member endpoints (single resource)
GET /resources/:id → Get one
PUT /resources/:id → Update (full replace)
PATCH /resources/:id → Update (partial)
DELETE /resources/:id → DeleteReal-world Examples:
E-commerce API:
// Products
GET /api/products → All products
GET /api/products/123 → Single product
GET /api/products/123/reviews → Product reviews
POST /api/products → Create product (admin)
PUT /api/products/123 → Update product
// Cart
GET /api/cart → Get user's cart
POST /api/cart/items → Add item to cart
DELETE /api/cart/items/456 → Remove item
PUT /api/cart/items/456 → Update quantity
// Orders
GET /api/orders → User's orders
GET /api/orders/789 → Single order
POST /api/orders → Create order (checkout)
GET /api/orders/789/invoice → Download invoiceBlog API:
// Posts
GET /api/posts → All posts
GET /api/posts?author=ripon → Filter by author
GET /api/posts/123 → Single post
POST /api/posts → Create post
PUT /api/posts/123 → Update post
DELETE /api/posts/123 → Delete post
// Comments
GET /api/posts/123/comments → Post comments
POST /api/posts/123/comments → Add comment
DELETE /api/comments/456 → Delete comment
// Categories
GET /api/categories → All categories
GET /api/categories/tech/posts → Posts in categorySocial Media API:
// Users & Profiles
GET /api/users/ripon → User profile
GET /api/users/ripon/posts → User's posts
GET /api/users/ripon/followers → User's followers
GET /api/users/ripon/following → Who user follows
POST /api/users/ripon/follow → Follow user
DELETE /api/users/ripon/follow → Unfollow user
// Posts & Interactions
POST /api/posts → Create post
GET /api/posts/123/likes → Post likes
POST /api/posts/123/like → Like post
DELETE /api/posts/123/like → Unlike postActions যা Noun নয়
কিছু কিছু ক্ষেত্রে action-based endpoints প্রয়োজন:
// Authentication
POST /api/auth/login
POST /api/auth/logout
POST /api/auth/refresh-token
POST /api/auth/forgot-password
POST /api/auth/reset-password
// Bulk operations
POST /api/products/bulk-delete
POST /api/users/bulk-import
// Complex operations
POST /api/orders/123/cancel
POST /api/orders/123/refund
POST /api/products/123/publish
POST /api/products/123/archive
// Search
GET /api/search?q=express&type=posts
// Analytics
GET /api/dashboard/analytics
GET /api/reports/salesএসব ক্ষেত্রে verb ব্যবহার করা acceptable কারণ এগুলো সত্যিকারের resource নয়।
৩. HTTP Methods & CRUD (বিস্তারিত)
CRUD অপারেশন ইমপ্লিমেন্ট করার জন্য স্ট্যান্ডার্ড HTTP মেথড ব্যবহার করা উচিত।
| Operation | HTTP Method | Idempotent? | Safe? | Description |
|---|---|---|---|---|
| Create | POST | ❌ | ❌ | নতুন রিসোর্স তৈরি করতে |
| Read | GET | ✅ | ✅ | ডেটা পড়তে বা আনতে |
| Update (Full) | PUT | ✅ | ❌ | পুরো রিসোর্স রিপ্লেস করতে |
| Update (Partial) | PATCH | ❌ | ❌ | রিসোর্সের কিছু অংশ মডিফাই করতে |
| Delete | DELETE | ✅ | ❌ | রিসোর্স মুছে ফেলতে |
পরিভাষা:
- Idempotent: একই request বারবার পাঠালে same result
- Safe: Server state পরিবর্তন করে না
GET - Read করা (Safe & Idempotent)
বৈশিষ্ট্য:
- শুধু ডেটা পড়ে, কোনো পরিবর্তন করে না
- ব্রাউজারে cache হতে পারে
- URL এ query parameters পাঠানো যায়
Examples:
// 1. Get all resources
app.get('/api/users', (req, res) => {
const users = await User.find();
res.json({
success: true,
count: users.length,
data: users
});
});
// 2. Get single resource
app.get('/api/users/:id', (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
res.json({
success: true,
data: user
});
});
// 3. Get with filters
app.get('/api/products', (req, res) => {
const { category, minPrice, maxPrice, sort } = req.query;
let query = {};
if (category) query.category = category;
if (minPrice || maxPrice) {
query.price = {};
if (minPrice) query.price.$gte = minPrice;
if (maxPrice) query.price.$lte = maxPrice;
}
const products = await Product.find(query).sort(sort);
res.json({
success: true,
count: products.length,
data: products
});
});
// URL: /api/products?category=electronics&minPrice=100&maxPrice=500&sort=priceResponse Codes:
200 OK- সফল404 Not Found- রিসোর্স পাওয়া যায়নি
POST - Create করা (Not Idempotent, Not Safe)
বৈশিষ্ট্য:
- নতুন রিসোর্স তৈরি করে
- একই request দুইবার পাঠালে দুইটা resource তৈরি হবে
- Body তে data পাঠাতে হয়
Examples:
// 1. Basic create
app.post('/api/users', async (req, res) => {
const { name, email, password } = req.body;
// Validation
if (!name || !email || !password) {
return res.status(400).json({
success: false,
error: 'All fields are required'
});
}
// Check if email exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(409).json({
success: false,
error: 'Email already exists'
});
}
// Create user
const user = await User.create({ name, email, password });
res.status(201).json({
success: true,
data: user
});
});
// 2. Nested resource create
app.post('/api/posts/:postId/comments', async (req, res) => {
const { postId } = req.params;
const { text, author } = req.body;
// Check if post exists
const post = await Post.findById(postId);
if (!post) {
return res.status(404).json({
success: false,
error: 'Post not found'
});
}
const comment = await Comment.create({
text,
author,
post: postId
});
res.status(201).json({
success: true,
data: comment
});
});
// 3. Bulk create
app.post('/api/products/bulk', async (req, res) => {
const { products } = req.body;
if (!Array.isArray(products)) {
return res.status(400).json({
success: false,
error: 'Products must be an array'
});
}
const createdProducts = await Product.insertMany(products);
res.status(201).json({
success: true,
count: createdProducts.length,
data: createdProducts
});
});Response Codes:
201 Created- নতুন রিসোর্স তৈরি হয়েছে400 Bad Request- ভুল ডেটা409 Conflict- ইতিমধ্যে আছে (duplicate)
PUT - পুরো Resource Replace করা (Idempotent, Not Safe)
বৈশিষ্ট্য:
- পুরো resource replace করে
- যে fields দেওয়া হয়নি সেগুলো null/empty হবে
- একই request বারবার পাঠালে same result
Examples:
app.put('/api/users/:id', async (req, res) => {
const { id } = req.params;
const { name, email, password, bio, avatar } = req.body;
// সব field দিতে হবে, নাহলে null হয়ে যাবে
const updatedUser = await User.findByIdAndUpdate(
id,
{ name, email, password, bio, avatar },
{ new: true, overwrite: true } // overwrite: পুরো replace
);
if (!updatedUser) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
res.json({
success: true,
data: updatedUser
});
});
// Request Body (সব field দিতে হবে):
{
"name": "Ripon Ahmed",
"email": "ripon@example.com",
"password": "newpassword123",
"bio": "Full Stack Developer",
"avatar": "https://example.com/avatar.jpg"
}Response Codes:
200 OK- আপডেট সফল404 Not Found- রিসোর্স নেই
PATCH - কিছু Field আপডেট করা (Not Idempotent, Not Safe)
বৈশিষ্ট্য:
- শুধু দেওয়া fields আপডেট হবে
- বাকি fields unchanged থাকবে
- Partial update এর জন্য ideal
Examples:
app.patch('/api/users/:id', async (req, res) => {
const { id } = req.params;
const updates = req.body; // শুধু যা পাঠানো হয়েছে
// শুধু allowed fields আপডেট করুন
const allowedUpdates = ['name', 'bio', 'avatar'];
const updateKeys = Object.keys(updates);
const isValidOperation = updateKeys.every(key =>
allowedUpdates.includes(key)
);
if (!isValidOperation) {
return res.status(400).json({
success: false,
error: 'Invalid updates'
});
}
const user = await User.findByIdAndUpdate(
id,
updates,
{ new: true, runValidators: true }
);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
res.json({
success: true,
data: user
});
});
// Request Body (শুধু যা পরিবর্তন করতে চান):
{
"bio": "Updated bio"
}
// name, email, password unchanged থাকবেPUT vs PATCH তুলনা:
// Original data:
{
"id": 1,
"name": "Ripon",
"email": "ripon@example.com",
"bio": "Developer",
"avatar": "avatar.jpg"
}
// PUT request (শুধু name পাঠালে):
PUT /users/1
{ "name": "Ripon Ahmed" }
Result: {
"name": "Ripon Ahmed",
"email": null, // ❌ Lost!
"bio": null, // ❌ Lost!
"avatar": null // ❌ Lost!
}
// PATCH request (শুধু name পাঠালে):
PATCH /users/1
{ "name": "Ripon Ahmed" }
Result: {
"name": "Ripon Ahmed",
"email": "ripon@example.com", // ✅ Preserved
"bio": "Developer", // ✅ Preserved
"avatar": "avatar.jpg" // ✅ Preserved
}Response Codes:
200 OK- আপডেট সফল404 Not Found- রিসোর্স নেই
DELETE - Resource মুছে ফেলা (Idempotent, Not Safe)
বৈশিষ্ট্য:
- Resource permanently মুছে দেয়
- একই request বারবার পাঠালে same result (already deleted)
- সাধারণত empty response body
Examples:
// 1. Simple delete
app.delete('/api/users/:id', async (req, res) => {
const { id } = req.params;
const user = await User.findByIdAndDelete(id);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
// Option 1: No content
res.status(204).send();
// Option 2: Confirmation message
// res.json({
// success: true,
// message: 'User deleted successfully'
// });
});
// 2. Soft delete (recommended)
app.delete('/api/posts/:id', async (req, res) => {
const { id } = req.params;
// Actually না মুছে 'deleted' flag set করুন
const post = await Post.findByIdAndUpdate(
id,
{ deleted: true, deletedAt: new Date() },
{ new: true }
);
if (!post) {
return res.status(404).json({
success: false,
error: 'Post not found'
});
}
res.json({
success: true,
message: 'Post deleted'
});
});
// 3. Cascade delete (related data ও মুছা)
app.delete('/api/users/:id', async (req, res) => {
const { id } = req.params;
// User মুছলে তার সব posts ও মুছবে
await Post.deleteMany({ author: id });
await Comment.deleteMany({ author: id });
await User.findByIdAndDelete(id);
res.status(204).send();
});Response Codes:
204 No Content- সফলভাবে deleted (no response body)200 OK- deleted with confirmation message404 Not Found- রিসোর্স নেই
অন্যান্য HTTP Methods
HEAD - Metadata পাওয়া
GET এর মতো কিন্তু শুধু headers, body নেই।
app.head('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
if (user) {
res.status(200).end();
} else {
res.status(404).end();
}
});OPTIONS - Supported methods জানা
CORS এর জন্য ব্যবহার হয়।
app.options('/api/users', (req, res) => {
res.set('Allow', 'GET, POST, PUT, DELETE');
res.send();
});৪. HTTP Status Codes - সম্পূর্ণ গাইড
ক্লায়েন্টকে সঠিক স্ট্যাটাস কোড পাঠানো খুব জরুরি। এতে ফ্রন্টএন্ড ডেভেলপার বুঝতে পারে কী হয়েছে।
Status Code Categories
1xx - Informational (রিকোয়েস্ট রিসিভ হয়েছে)
2xx - Success (সফল হয়েছে)
3xx - Redirection (আরও কাজ করতে হবে)
4xx - Client Error (ক্লায়েন্টের ভুল)
5xx - Server Error (সার্ভারের ভুল)2xx - Success Codes
200 OK - সাধারণ সফলতা
সবচেয়ে common status code।
// GET request সফল
app.get('/api/users', (req, res) => {
res.status(200).json({ data: users });
});
// PUT/PATCH request সফল
app.put('/api/users/:id', (req, res) => {
res.status(200).json({ data: updatedUser });
});201 Created - নতুন Resource তৈরি
POST request সফল হলে।
app.post('/api/users', (req, res) => {
const newUser = await User.create(req.body);
res.status(201)
.location(`/api/users/${newUser.id}`) // নতুন resource এর URL
.json({
success: true,
data: newUser
});
});202 Accepted - প্রসেসিং চলছে
Async operation এর জন্য।
app.post('/api/reports/generate', (req, res) => {
// Background job start করুন
const jobId = startReportGeneration(req.body);
res.status(202).json({
message: 'Report generation started',
jobId: jobId,
statusUrl: `/api/jobs/${jobId}`
});
});204 No Content - সফল কিন্তু কোন ডেটা নেই
DELETE request এর জন্য ideal।
app.delete('/api/users/:id', (req, res) => {
await User.findByIdAndDelete(req.params.id);
res.status(204).send(); // Empty response
});3xx - Redirection Codes
301 Moved Permanently - স্থায়ী পরিবর্তন
app.get('/api/v1/users', (req, res) => {
res.redirect(301, '/api/v2/users');
});302 Found - অস্থায়ী redirect
app.get('/api/users/:id', (req, res) => {
res.redirect(302, '/api/users');
});304 Not Modified - Cache থেকে ব্যবহার করুন
app.get('/api/users', (req, res) => {
const lastModified = users.lastUpdated;
const clientLastModified = req.headers['if-modified-since'];
if (clientLastModified === lastModified) {
return res.status(304).send(); // Cache থেকে নিন
}
res.set('Last-Modified', lastModified);
res.json(users);
});4xx - Client Error Codes
400 Bad Request - ভুল ইনপুট
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({
success: false,
error: 'Name and email are required',
fields: {
name: !name ? 'Name is required' : null,
email: !email ? 'Email is required' : null
}
});
}
// Create user...
});401 Unauthorized - Authentication প্রয়োজন
app.get('/api/profile', (req, res) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({
success: false,
error: 'Authentication required',
message: 'Please provide a valid token'
});
}
// Verify token...
});Note: "Unauthorized" মানে actually "Unauthenticated" (logged in নেই)
403 Forbidden - Permission নেই
app.delete('/api/posts/:id', authenticate, (req, res) => {
const post = await Post.findById(req.params.id);
// Check if user owns this post
if (post.author.toString() !== req.user.id) {
return res.status(403).json({
success: false,
error: 'Forbidden',
message: 'You do not have permission to delete this post'
});
}
await post.remove();
res.status(204).send();
});404 Not Found - Resource পাওয়া যায়নি
app.get('/api/users/:id', (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found',
message: `No user found with ID: ${req.params.id}`
});
}
res.json({ success: true, data: user });
});
// 404 for invalid routes
app.use((req, res) => {
res.status(404).json({
success: false,
error: 'Route not found',
message: `Cannot ${req.method} ${req.url}`
});
});405 Method Not Allowed - ভুল HTTP method
// শুধু GET এবং POST allowed
app.all('/api/users', (req, res, next) => {
if (!['GET', 'POST'].includes(req.method)) {
return res.status(405)
.set('Allow', 'GET, POST')
.json({
success: false,
error: 'Method not allowed',
allowedMethods: ['GET', 'POST']
});
}
next();
});409 Conflict - ডুপ্লিকেট বা conflict
app.post('/api/users', (req, res) => {
const { email } = req.body;
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(409).json({
success: false,
error: 'Conflict',
message: 'User with this email already exists'
});
}
// Create user...
});422 Unprocessable Entity - Validation error
app.post('/api/users', (req, res) => {
const { email } = req.body;
// Email format ভুল
if (!isValidEmail(email)) {
return res.status(422).json({
success: false,
error: 'Validation failed',
details: {
email: 'Invalid email format'
}
});
}
// Create user...
});429 Too Many Requests - Rate limit exceeded
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
statusCode: 429,
message: {
success: false,
error: 'Too many requests',
message: 'Please try again after 15 minutes'
}
});
app.use('/api/', limiter);5xx - Server Error Codes
500 Internal Server Error - সাধারণ সার্ভার error
app.get('/api/users', async (req, res) => {
try {
const users = await User.find();
res.json(users);
} catch (error) {
console.error(error);
res.status(500).json({
success: false,
error: 'Internal server error',
message: 'Something went wrong on our end'
});
}
});
// Global error handler
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.statusCode || 500).json({
success: false,
error: err.message,
...(process.env.NODE_ENV === 'development' && {
stack: err.stack
})
});
});502 Bad Gateway - Upstream server error
app.get('/api/external-data', async (req, res) => {
try {
const response = await fetch('https://external-api.com/data');
const data = await response.json();
res.json(data);
} catch (error) {
res.status(502).json({
success: false,
error: 'Bad Gateway',
message: 'External service is unavailable'
});
}
});503 Service Unavailable - Temporarily down
let isMaintenanceMode = false;
app.use((req, res, next) => {
if (isMaintenanceMode) {
return res.status(503)
.set('Retry-After', '3600') // 1 hour
.json({
success: false,
error: 'Service unavailable',
message: 'System is under maintenance'
});
}
next();
});Status Code Selection Guide
// যখন কী status code ব্যবহার করবেন
// GET Requests
✅ 200 - Resource পাওয়া গেছে
✅ 404 - Resource পাওয়া যায়নি
✅ 304 - Not modified (cached)
// POST Requests
✅ 201 - Successfully created
✅ 400 - Invalid data
✅ 409 - Already exists
✅ 422 - Validation failed
// PUT/PATCH Requests
✅ 200 - Successfully updated
✅ 404 - Resource not found
✅ 400 - Invalid data
// DELETE Requests
✅ 204 - Successfully deleted (no content)
✅ 200 - Deleted with confirmation
✅ 404 - Resource not found
✅ 403 - No permission to delete
// Authentication/Authorization
✅ 401 - Not logged in
✅ 403 - Logged in but no permission
// Errors
✅ 400 - Client's fault (bad input)
✅ 500 - Server's fault (bug/crash)Best Practices
// ✅ Good: Specific status code
res.status(404).json({ error: 'User not found' });
// ❌ Bad: Generic 200 for everything
res.status(200).json({ error: 'User not found' }); // Wrong!
// ✅ Good: Consistent error format
{
"success": false,
"error": "Validation failed",
"details": { ... }
}
// ❌ Bad: Inconsistent format
{
"err": "error message",
"msg": "validation failed"
}৫. Query Parameters - Filtering, Sorting, Pagination
RESTful API তে query parameters ব্যবহার করে data filtering, sorting এবং pagination করা হয়।
Filtering - ডেটা ফিল্টার করা
// URL: /api/products?category=electronics&minPrice=100&maxPrice=500
app.get('/api/products', async (req, res) => {
try {
// Build query object
const query = {};
// 1. Simple filtering
if (req.query.category) {
query.category = req.query.category;
}
// 2. Range filtering
if (req.query.minPrice || req.query.maxPrice) {
query.price = {};
if (req.query.minPrice) {
query.price.$gte = parseInt(req.query.minPrice);
}
if (req.query.maxPrice) {
query.price.$lte = parseInt(req.query.maxPrice);
}
}
// 3. Boolean filtering
if (req.query.inStock !== undefined) {
query.inStock = req.query.inStock === 'true';
}
// 4. Array filtering (comma-separated)
if (req.query.tags) {
query.tags = { $in: req.query.tags.split(',') };
}
const products = await Product.find(query);
res.json({
success: true,
count: products.length,
data: products
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
// Example URLs:
// /api/products?category=electronics
// /api/products?minPrice=100&maxPrice=500
// /api/products?inStock=true
// /api/products?tags=new,featured,saleSorting - সর্ট করা
// URL: /api/products?sort=price (ascending)
// URL: /api/products?sort=-price (descending)
app.get('/api/products', async (req, res) => {
const query = {};
// Sorting
let sortOption = {};
if (req.query.sort) {
const sortFields = req.query.sort.split(',');
sortFields.forEach(field => {
if (field.startsWith('-')) {
// Descending
sortOption[field.substring(1)] = -1;
} else {
// Ascending
sortOption[field] = 1;
}
});
} else {
// Default sort
sortOption = { createdAt: -1 }; // Newest first
}
const products = await Product.find(query).sort(sortOption);
res.json({
success: true,
data: products
});
});
// Example URLs:
// /api/products?sort=price → Price low to high
// /api/products?sort=-price → Price high to low
// /api/products?sort=name → Name A to Z
// /api/products?sort=-createdAt → Newest first
// /api/products?sort=price,name → Price then namePagination - পেজিনেশন
// URL: /api/products?page=2&limit=10
app.get('/api/products', async (req, res) => {
try {
// Pagination
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
// Get total count for pagination info
const total = await Product.countDocuments();
// Fetch paginated data
const products = await Product.find()
.skip(skip)
.limit(limit);
res.json({
success: true,
pagination: {
page: page,
limit: limit,
total: total,
pages: Math.ceil(total / limit),
hasNext: page * limit < total,
hasPrev: page > 1
},
data: products
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
// Response:
{
"success": true,
"pagination": {
"page": 2,
"limit": 10,
"total": 45,
"pages": 5,
"hasNext": true,
"hasPrev": true
},
"data": [...]
}Field Selection - নির্দিষ্ট ফিল্ড আনা
// URL: /api/users?fields=name,email
app.get('/api/users', async (req, res) => {
let selectFields = {};
if (req.query.fields) {
const fields = req.query.fields.split(',');
fields.forEach(field => {
selectFields[field] = 1;
});
}
const users = await User.find().select(selectFields);
res.json({
success: true,
data: users
});
});
// Response (শুধু name এবং email):
{
"data": [
{ "name": "Ripon", "email": "ripon@example.com" },
{ "name": "Karim", "email": "karim@example.com" }
]
}Search - সার্চ করা
// URL: /api/products?search=laptop
app.get('/api/products', async (req, res) => {
let query = {};
if (req.query.search) {
query.$or = [
{ name: { $regex: req.query.search, $options: 'i' } },
{ description: { $regex: req.query.search, $options: 'i' } }
];
}
const products = await Product.find(query);
res.json({
success: true,
data: products
});
});Complete Advanced Query Handler
সব features একসাথে:
const APIFeatures = class {
constructor(query, queryString) {
this.query = query;
this.queryString = queryString;
}
filter() {
const queryObj = { ...this.queryString };
const excludedFields = ['page', 'sort', 'limit', 'fields', 'search'];
excludedFields.forEach(field => delete queryObj[field]);
// Advanced filtering (gte, lte, gt, lt)
let queryStr = JSON.stringify(queryObj);
queryStr = queryStr.replace(/\b(gte|gt|lte|lt)\b/g, match => `$${match}`);
this.query = this.query.find(JSON.parse(queryStr));
return this;
}
search() {
if (this.queryString.search) {
this.query = this.query.find({
$or: [
{ name: { $regex: this.queryString.search, $options: 'i' } },
{ description: { $regex: this.queryString.search, $options: 'i' } }
]
});
}
return this;
}
sort() {
if (this.queryString.sort) {
const sortBy = this.queryString.sort.split(',').join(' ');
this.query = this.query.sort(sortBy);
} else {
this.query = this.query.sort('-createdAt');
}
return this;
}
limitFields() {
if (this.queryString.fields) {
const fields = this.queryString.fields.split(',').join(' ');
this.query = this.query.select(fields);
}
return this;
}
paginate() {
const page = parseInt(this.queryString.page) || 1;
const limit = parseInt(this.queryString.limit) || 10;
const skip = (page - 1) * limit;
this.query = this.query.skip(skip).limit(limit);
return this;
}
};
// Usage
app.get('/api/products', async (req, res) => {
try {
const features = new APIFeatures(Product.find(), req.query)
.filter()
.search()
.sort()
.limitFields()
.paginate();
const products = await features.query;
res.json({
success: true,
count: products.length,
data: products
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
// Example URLs:
// /api/products?category=electronics&price[gte]=100&price[lte]=500&sort=-price&page=2&limit=10&fields=name,price&search=laptop৬. CRUD Implementation Example (Complete)
একটি সম্পূর্ণ User Management API:
const express = require('express');
const app = express();
app.use(express.json());
// Fake database
let users = [
{ id: 1, name: "Ripon", email: "ripon@example.com", role: "admin" },
{ id: 2, name: "Karim", email: "karim@example.com", role: "user" }
];
// GET: All Users (with filtering, sorting, pagination)
app.get('/api/v1/users', (req, res) => {
try {
let filteredUsers = [...users];
// Filtering
if (req.query.role) {
filteredUsers = filteredUsers.filter(u => u.role === req.query.role);
}
if (req.query.search) {
const search = req.query.search.toLowerCase();
filteredUsers = filteredUsers.filter(u =>
u.name.toLowerCase().includes(search) ||
u.email.toLowerCase().includes(search)
);
}
// Sorting
if (req.query.sort) {
const sortField = req.query.sort.replace('-', '');
const sortOrder = req.query.sort.startsWith('-') ? -1 : 1;
filteredUsers.sort((a, b) => {
if (a[sortField] < b[sortField]) return -1 * sortOrder;
if (a[sortField] > b[sortField]) return 1 * sortOrder;
return 0;
});
}
// Pagination
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const startIndex = (page - 1) * limit;
const endIndex = page * limit;
const paginatedUsers = filteredUsers.slice(startIndex, endIndex);
res.status(200).json({
status: 'success',
results: paginatedUsers.length,
pagination: {
page,
limit,
total: filteredUsers.length,
pages: Math.ceil(filteredUsers.length / limit)
},
data: { users: paginatedUsers }
});
} catch (error) {
res.status(500).json({
status: 'error',
message: error.message
});
}
});
// GET: Single User
app.get('/api/v1/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({
status: 'fail',
message: 'User not found'
});
}
res.status(200).json({
status: 'success',
data: { user }
});
});
// POST: Create User
app.post('/api/v1/users', (req, res) => {
const { name, email, role } = req.body;
// Validation
if (!name || !email) {
return res.status(400).json({
status: 'fail',
message: 'Name and email are required'
});
}
// Check duplicate email
const existingUser = users.find(u => u.email === email);
if (existingUser) {
return res.status(409).json({
status: 'fail',
message: 'Email already exists'
});
}
const newUser = {
id: users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1,
name,
email,
role: role || 'user'
};
users.push(newUser);
res.status(201)
.location(`/api/v1/users/${newUser.id}`)
.json({
status: 'success',
data: { user: newUser }
});
});
// PUT: Update User (full replace)
app.put('/api/v1/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const index = users.findIndex(u => u.id === id);
if (index === -1) {
return res.status(404).json({
status: 'fail',
message: 'User not found'
});
}
const { name, email, role } = req.body;
if (!name || !email) {
return res.status(400).json({
status: 'fail',
message: 'Name and email are required'
});
}
users[index] = { id, name, email, role: role || 'user' };
res.status(200).json({
status: 'success',
data: { user: users[index] }
});
});
// PATCH: Update User (partial)
app.patch('/api/v1/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const user = users.find(u => u.id === id);
if (!user) {
return res.status(404).json({
status: 'fail',
message: 'User not found'
});
}
// Update only provided fields
if (req.body.name) user.name = req.body.name;
if (req.body.email) user.email = req.body.email;
if (req.body.role) user.role = req.body.role;
res.status(200).json({
status: 'success',
data: { user }
});
});
// DELETE: Delete User
app.delete('/api/v1/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const index = users.findIndex(u => u.id === id);
if (index === -1) {
return res.status(404).json({
status: 'fail',
message: 'User not found'
});
}
users.splice(index, 1);
res.status(204).send();
});
// 404 Handler
app.use((req, res) => {
res.status(404).json({
status: 'fail',
message: 'Route not found'
});
});
// Error Handler
app.use((err, req, res, next) => {
res.status(500).json({
status: 'error',
message: err.message
});
});
const PORT = 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));৭. API Versioning - বিস্তারিত
ভবিষ্যতে API পরিবর্তন হলে যাতে পুরনো ক্লায়েন্টদের সমস্যা না হয়, সেজন্য ভার্সনিং ব্যবহার করা আবশ্যক।
কেন Versioning প্রয়োজন?
ধরুন আপনার API এখন এভাবে কাজ করে:
GET /api/users/123
{
"name": "Ripon Ahmed",
"email": "ripon@example.com"
}কিন্তু পরে আপনি response format পরিবর্তন করলেন:
{
"user": {
"fullName": "Ripon Ahmed",
"contactEmail": "ripon@example.com"
}
}পুরনো mobile apps crash করবে! এজন্য versioning দরকার।
Versioning Strategies
১. URL Versioning (সবচেয়ে জনপ্রিয়) ✅
// v1
app.get('/api/v1/users', (req, res) => {
res.json({
name: "Ripon",
email: "ripon@example.com"
});
});
// v2 (নতুন structure)
app.get('/api/v2/users', (req, res) => {
res.json({
user: {
fullName: "Ripon",
contactInfo: {
email: "ripon@example.com"
}
}
});
});
// URLs:
// /api/v1/users
// /api/v2/usersসুবিধা:
- সহজ এবং clear
- Browser এ test করা easy
- Documentation সহজ
অসুবিধা:
- URL পরিবর্তন হয়
২. Header Versioning
app.get('/api/users', (req, res) => {
const version = req.headers['api-version'] || '1';
if (version === '1') {
res.json({
name: "Ripon",
email: "ripon@example.com"
});
} else if (version === '2') {
res.json({
user: {
fullName: "Ripon",
contactInfo: { email: "ripon@example.com" }
}
});
}
});
// Request:
// GET /api/users
// Headers: { "API-Version": "2" }সুবিধা:
- URL একই থাকে
- RESTful principle মানে
অসুবিধা:
- ব্রাউজারে test করা কঠিন
- Header সেট করতে হয়
৩. Query Parameter Versioning
app.get('/api/users', (req, res) => {
const version = req.query.version || '1';
if (version === '1') {
// v1 response
} else if (version === '2') {
// v2 response
}
});
// URL: /api/users?version=2৪. Accept Header Versioning (RESTful)
app.get('/api/users', (req, res) => {
const acceptHeader = req.headers.accept;
if (acceptHeader.includes('application/vnd.myapi.v2+json')) {
// v2 response
} else {
// v1 response
}
});
// Request:
// Accept: application/vnd.myapi.v2+jsonBest Practices
// 1. Version from the start
✅ /api/v1/users (শুরু থেকেই)
❌ /api/users (পরে পরিবর্তন করা কঠিন)
// 2. Support multiple versions
app.use('/api/v1', require('./routes/v1'));
app.use('/api/v2', require('./routes/v2'));
// 3. Deprecation notice
app.get('/api/v1/users', (req, res) => {
res.set('Warning', '299 - "API v1 is deprecated. Please migrate to v2"');
res.json(users);
});
// 4. Sunset header (কবে বন্ধ হবে)
res.set('Sunset', 'Sat, 31 Dec 2026 23:59:59 GMT');Version Migration Example
// routes/v1/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.json({
name: "Ripon",
email: "ripon@example.com"
});
});
module.exports = router;
// routes/v2/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.json({
data: {
firstName: "Ripon",
lastName: "Ahmed",
contact: {
email: "ripon@example.com",
phone: "+880123456789"
}
}
});
});
module.exports = router;
// app.js
const v1Routes = require('./routes/v1/users');
const v2Routes = require('./routes/v2/users');
app.use('/api/v1/users', v1Routes);
app.use('/api/v2/users', v2Routes);৮. Standard Response Format (JSend Pattern)
সবসময় একটি consistent ফরম্যাটে রেসপন্স পাঠানো উচিত। এতে ফ্রন্টএন্ড ডেভেলপারদের কাজ অনেক সহজ হয়।
JSend Specification
তিন ধরনের response status আছে:
১. Success Response
সবকিছু ঠিক আছে।
{
"status": "success",
"data": {
"id": 1,
"name": "Ripon",
"email": "ripon@example.com"
}
}// Implementation
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === req.params.id);
res.json({
status: 'success',
data: user
});
});২. Fail Response
Client এর ভুল (validation error, not found, etc.)
{
"status": "fail",
"message": "Validation failed",
"errors": {
"email": "Email is required",
"password": "Password must be at least 6 characters"
}
}app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({
status: 'fail',
message: 'Validation failed',
errors: {
name: !name ? 'Name is required' : null,
email: !email ? 'Email is required' : null
}
});
}
// Create user...
});৩. Error Response
Server এ problem (bug, crash, database down)
{
"status": "error",
"message": "Internal server error",
"code": "DATABASE_CONNECTION_FAILED"
}app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({
status: 'error',
message: err.message,
...(process.env.NODE_ENV === 'development' && {
stack: err.stack
})
});
});Extended Response Format
আরও তথ্য যোগ করা:
{
"status": "success",
"message": "User created successfully",
"data": {
"user": {
"id": 123,
"name": "Ripon"
}
},
"meta": {
"timestamp": "2026-01-17T10:30:00Z",
"version": "v1",
"requestId": "abc-123-def-456"
},
"pagination": {
"page": 1,
"limit": 10,
"total": 100,
"pages": 10
}
}Collection Response (Multiple Items)
{
"status": "success",
"results": 25,
"data": [
{ "id": 1, "name": "User 1" },
{ "id": 2, "name": "User 2" }
],
"pagination": {
"page": 1,
"limit": 10,
"total": 25,
"pages": 3
}
}Nested Resource Response
{
"status": "success",
"data": {
"user": {
"id": 1,
"name": "Ripon",
"posts": [
{ "id": 101, "title": "First Post" },
{ "id": 102, "title": "Second Post" }
]
}
}
}Response Helper Functions
// helpers/response.js
class ResponseHelper {
static success(res, data, statusCode = 200, message = null) {
const response = {
status: 'success',
...(message && { message }),
data
};
return res.status(statusCode).json(response);
}
static fail(res, message, errors = null, statusCode = 400) {
const response = {
status: 'fail',
message,
...(errors && { errors })
};
return res.status(statusCode).json(response);
}
static error(res, message, statusCode = 500) {
const response = {
status: 'error',
message
};
return res.status(statusCode).json(response);
}
static paginated(res, data, pagination, statusCode = 200) {
return res.status(statusCode).json({
status: 'success',
results: data.length,
pagination,
data
});
}
}
module.exports = ResponseHelper;
// Usage
const ResponseHelper = require('./helpers/response');
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === req.params.id);
if (!user) {
return ResponseHelper.fail(res, 'User not found', null, 404);
}
return ResponseHelper.success(res, { user });
});
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return ResponseHelper.fail(res, 'Validation failed', {
name: !name ? 'Name is required' : null,
email: !email ? 'Email is required' : null
});
}
const newUser = createUser({ name, email });
return ResponseHelper.success(res, { user: newUser }, 201, 'User created');
});Error Response with Codes
// Custom error codes
const ERROR_CODES = {
VALIDATION_FAILED: 'VALIDATION_FAILED',
USER_NOT_FOUND: 'USER_NOT_FOUND',
DUPLICATE_EMAIL: 'DUPLICATE_EMAIL',
UNAUTHORIZED: 'UNAUTHORIZED',
FORBIDDEN: 'FORBIDDEN'
};
app.post('/api/users', (req, res) => {
const { email } = req.body;
if (users.find(u => u.email === email)) {
return res.status(409).json({
status: 'fail',
message: 'Email already exists',
code: ERROR_CODES.DUPLICATE_EMAIL
});
}
// Create user...
});
// Frontend can check:
// if (response.code === 'DUPLICATE_EMAIL') { ... }৯. Authentication & Authorization
Authentication - ইউজার কে?
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
// Register
app.post('/api/auth/register', async (req, res) => {
const { name, email, password } = req.body;
// Hash password
const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({
name,
email,
password: hashedPassword
});
// Generate JWT token
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.status(201).json({
status: 'success',
data: {
user: {
id: user.id,
name: user.name,
email: user.email
},
token
}
});
});
// Login
app.post('/api/auth/login', async (req, res) => {
const { email, password } = req.body;
// Find user
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({
status: 'fail',
message: 'Invalid credentials'
});
}
// Verify password
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
return res.status(401).json({
status: 'fail',
message: 'Invalid credentials'
});
}
// Generate token
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({
status: 'success',
data: {
user: {
id: user.id,
name: user.name,
email: user.email
},
token
}
});
});
// Authentication Middleware
const authenticate = (req, res, next) => {
try {
// Get token from header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
status: 'fail',
message: 'No token provided'
});
}
const token = authHeader.split(' ')[1];
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Add user to request
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({
status: 'fail',
message: 'Invalid or expired token'
});
}
};
// Protected route
app.get('/api/profile', authenticate, (req, res) => {
res.json({
status: 'success',
data: {
user: req.user
}
});
});Authorization - ইউজার কী করতে পারবে?
// Role-based authorization
const authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({
status: 'fail',
message: 'You do not have permission to perform this action'
});
}
next();
};
};
// Usage
app.delete('/api/users/:id',
authenticate, // Must be logged in
authorize('admin'), // Must be admin
(req, res) => {
// Delete user
}
);
app.post('/api/posts',
authenticate, // Must be logged in
authorize('admin', 'editor'), // Must be admin OR editor
(req, res) => {
// Create post
}
);Resource Ownership Check
// Check if user owns the resource
const checkOwnership = (resourceType) => {
return async (req, res, next) => {
try {
let resource;
if (resourceType === 'post') {
resource = await Post.findById(req.params.id);
} else if (resourceType === 'comment') {
resource = await Comment.findById(req.params.id);
}
if (!resource) {
return res.status(404).json({
status: 'fail',
message: `${resourceType} not found`
});
}
// Check if user owns this resource
if (resource.author.toString() !== req.user.userId) {
return res.status(403).json({
status: 'fail',
message: 'You can only modify your own resources'
});
}
req.resource = resource;
next();
} catch (error) {
res.status(500).json({
status: 'error',
message: error.message
});
}
};
};
// Usage
app.delete('/api/posts/:id',
authenticate,
checkOwnership('post'),
(req, res) => {
// User owns this post, can delete
req.resource.remove();
res.status(204).send();
}
);API Key Authentication
const API_KEYS = {
'abc123': { name: 'Mobile App', permissions: ['read', 'write'] },
'xyz789': { name: 'Analytics', permissions: ['read'] }
};
const apiKeyAuth = (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || !API_KEYS[apiKey]) {
return res.status(401).json({
status: 'fail',
message: 'Invalid API key'
});
}
req.apiClient = API_KEYS[apiKey];
next();
};
app.get('/api/data', apiKeyAuth, (req, res) => {
res.json({
status: 'success',
data: { ... },
client: req.apiClient.name
});
});১০. API Security Best Practices
১. HTTPS ব্যবহার করুন
সবসময় HTTPS ব্যবহার করুন, HTTP নয়।
// Redirect HTTP to HTTPS
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(`https://${req.header('host')}${req.url}`);
} else {
next();
}
});২. Rate Limiting
একই IP থেকে অতিরিক্ত request block করুন।
npm install express-rate-limitconst rateLimit = require('express-rate-limit');
// General API limiter
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per 15 minutes
message: {
status: 'fail',
message: 'Too many requests, please try again later'
},
standardHeaders: true,
legacyHeaders: false
});
app.use('/api/', apiLimiter);
// Stricter limiter for authentication endpoints
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // 5 failed attempts
skipSuccessfulRequests: true
});
app.post('/api/auth/login', authLimiter, loginController);৩. Input Validation & Sanitization
npm install express-validatorconst { body, validationResult } = require('express-validator');
const validator = require('validator');
app.post('/api/users',
// Validation rules
[
body('email')
.isEmail().withMessage('Invalid email')
.normalizeEmail(),
body('password')
.isLength({ min: 8 }).withMessage('Password must be at least 8 characters')
.matches(/\d/).withMessage('Password must contain a number'),
body('name')
.trim()
.escape()
.isLength({ min: 2 }).withMessage('Name too short')
],
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({
status: 'fail',
errors: errors.array()
});
}
// Create user
}
);৪. SQL Injection Prevention
// ❌ Bad - SQL Injection vulnerable
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`;
// ✅ Good - Use parameterized queries
const query = 'SELECT * FROM users WHERE email = ?';
db.query(query, [req.body.email]);
// ✅ Good - Use ORM (Sequelize, TypeORM)
const user = await User.findOne({ where: { email: req.body.email } });৫. NoSQL Injection Prevention
// ❌ Bad
const user = await User.findOne({ email: req.body.email });
// If attacker sends: { "email": { "$gt": "" } }
// It will return any user!
// ✅ Good - Validate type
if (typeof req.body.email !== 'string') {
return res.status(400).json({ error: 'Invalid email' });
}
const user = await User.findOne({ email: req.body.email });৬. CORS Configuration
npm install corsconst cors = require('cors');
// Production CORS config
app.use(cors({
origin: process.env.FRONTEND_URL, // Only allow your frontend
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization']
}));৭. Helmet - Security Headers
npm install helmetconst helmet = require('helmet');
app.use(helmet()); // Adds various security headers
// Custom configuration
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"]
}
}
}));৮. Hide Sensitive Information
// Don't expose stack traces in production
app.use((err, req, res, next) => {
res.status(500).json({
status: 'error',
message: process.env.NODE_ENV === 'production'
? 'Internal server error'
: err.message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
});
// Hide Express from X-Powered-By header
app.disable('x-powered-by');৯. Secure Password Storage
const bcrypt = require('bcrypt');
// Hash password before saving
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);
// Never store plain text passwords
❌ { password: 'mypassword123' }
✅ { password: '$2b$10$N9qo8uLOickgx2ZMRZoMye' }১০. Environment Variables
// .env file
PORT=3000
JWT_SECRET=your-super-secret-key-here-very-long-and-random
DATABASE_URL=mongodb://localhost:27017/myapp
NODE_ENV=production
// Use dotenv
require('dotenv').config();
const jwtSecret = process.env.JWT_SECRET;১১. Error Handling Pattern
Centralized Error Handling
// Custom error class
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
// Async error handler wrapper
const catchAsync = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
};
// Usage
app.get('/api/users/:id', catchAsync(async (req, res, next) => {
const user = await User.findById(req.params.id);
if (!user) {
return next(new AppError('User not found', 404));
}
res.json({
status: 'success',
data: { user }
});
}));
// Global error handler
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
if (process.env.NODE_ENV === 'development') {
res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
} else {
// Production
if (err.isOperational) {
res.status(err.statusCode).json({
status: err.status,
message: err.message
});
} else {
console.error('ERROR 💥', err);
res.status(500).json({
status: 'error',
message: 'Something went wrong'
});
}
}
});
module.exports = { AppError, catchAsync };১২. API Documentation
Swagger/OpenAPI Documentation
npm install swagger-ui-express swagger-jsdocconst swaggerJsDoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const swaggerOptions = {
definition: {
openapi: '3.0.0',
info: {
title: 'My API',
version: '1.0.0',
description: 'API Documentation',
contact: {
name: 'Ripon',
email: 'ripon@example.com'
}
},
servers: [
{
url: 'http://localhost:3000',
description: 'Development server'
}
]
},
apis: ['./routes/*.js']
};
const swaggerDocs = swaggerJsDoc(swaggerOptions);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs));
// Annotate your routes
/**
* @swagger
* /api/users:
* get:
* summary: Get all users
* tags: [Users]
* responses:
* 200:
* description: List of users
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* data:
* type: array
* items:
* type: object
* properties:
* id:
* type: integer
* name:
* type: string
* email:
* type: string
*/
app.get('/api/users', (req, res) => {
// ...
});১৩. API Testing
Manual Testing Tools
1. Postman (সবচেয়ে জনপ্রিয়)
- Collections তৈরি করুন
- Environment variables ব্যবহার করুন
- Automated tests লিখুন
// Postman Test Script
pm.test("Status code is 200", function () {
pm.response.to.have.status(200);
});
pm.test("Response has user data", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.status).to.eql("success");
pm.expect(jsonData.data).to.have.property("user");
});2. Thunder Client (VS Code Extension)
- VS Code এর মধ্যেই কাজ করে
- হালকা এবং দ্রুত
3. Insomnia
- সুন্দর UI
- GraphQL support
Automated Testing (Jest + Supertest)
npm install --save-dev jest supertest// users.test.js
const request = require('supertest');
const app = require('../app');
describe('User API', () => {
describe('GET /api/users', () => {
it('should return all users', async () => {
const res = await request(app)
.get('/api/users')
.expect('Content-Type', /json/)
.expect(200);
expect(res.body.status).toBe('success');
expect(Array.isArray(res.body.data)).toBe(true);
});
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const newUser = {
name: 'Test User',
email: 'test@example.com',
password: 'password123'
};
const res = await request(app)
.post('/api/users')
.send(newUser)
.expect(201);
expect(res.body.status).toBe('success');
expect(res.body.data.user).toHaveProperty('id');
expect(res.body.data.user.name).toBe('Test User');
});
it('should fail without email', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Test' })
.expect(400);
expect(res.body.status).toBe('fail');
});
});
describe('DELETE /api/users/:id', () => {
it('should delete a user', async () => {
await request(app)
.delete('/api/users/1')
.expect(204);
});
it('should return 404 for non-existent user', async () => {
await request(app)
.delete('/api/users/9999')
.expect(404);
});
});
});১৪. Complete Production-Ready API Example
// app.js
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const morgan = require('morgan');
require('dotenv').config();
const app = express();
// 1. Security middleware
app.use(helmet());
app.use(cors({
origin: process.env.FRONTEND_URL,
credentials: true
}));
// 2. Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
});
app.use('/api', limiter);
// 3. Logging
app.use(morgan('combined'));
// 4. Body parsers
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true, limit: '10kb' }));
// 5. Routes
app.use('/api/v1/users', require('./routes/userRoutes'));
app.use('/api/v1/posts', require('./routes/postRoutes'));
// 6. Health check
app.get('/health', (req, res) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// 7. 404 handler
app.all('*', (req, res) => {
res.status(404).json({
status: 'fail',
message: `Route ${req.originalUrl} not found`
});
});
// 8. Global error handler
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
res.status(err.statusCode).json({
status: err.status || 'error',
message: err.message,
...(process.env.NODE_ENV === 'development' && {
stack: err.stack
})
});
});
module.exports = app;
// server.js
const app = require('./app');
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`🚀 Server running on port ${PORT}`);
console.log(`📚 API Docs: http://localhost:${PORT}/api-docs`);
});১৫. Best Practices Summary
✅ Do's (করণীয়)
- Use proper HTTP methods and status codes
- Version your API from day one (
/api/v1/...) - Use nouns for resources, not verbs (
/usersnot/getUsers) - Implement pagination for collections
- Use filtering, sorting, and field selection
- Consistent response format (JSend)
- Authentication and authorization
- Input validation and sanitization
- Rate limiting
- Error handling (centralized)
- API documentation (Swagger)
- HTTPS everywhere
- Environment variables for config
- Logging (Morgan, Winston)
- Testing (unit + integration)
❌ Don'ts (বর্জনীয়)
- Don't use verbs in URLs (
/createUser) - Don't return different formats (inconsistent responses)
- Don't expose sensitive data (passwords, keys)
- Don't skip authentication
- Don't ignore error handling
- Don't use GET for state-changing operations
- Don't return 200 for everything
- Don't trust user input (always validate)
- Don't nest routes too deep (max 2-3 levels)
- Don't forget documentation
সারাংশ
একটি Production-Ready RESTful API তে থাকা উচিত:
✅ Proper resource naming
✅ Correct HTTP methods & status codes
✅ Query parameters (filtering, sorting, pagination)
✅ Consistent response format
✅ Authentication & authorization
✅ Input validation
✅ Security headers (Helmet)
✅ Rate limiting
✅ CORS configuration
✅ Error handling
✅ API documentation
✅ Logging
✅ Testing
✅ Versioning
Next Steps:
- Database integration (MongoDB/PostgreSQL)
- File uploads (Multer)
- Real-time features (Socket.io)
- Caching (Redis)
- Microservices architecture
Happy Coding! 🚀