REST API

ধরো, তুমি একটি চমৎকার মোবাইল অ্যাপ্লিকেশন বা ওয়েব প্ল্যাটফর্ম তৈরি করেছ। এখন তোমার এই অ্যাপটিকে সার্ভার থেকে প্রতিনিয়ত ডেটা নিয়ে আসতে হবে বা সার্ভারে নতুন ডেটা পাঠাতে হবে—যেমন ইউজারের প্রোফাইল ইনফরমেশন, নতুন পোস্ট, বা প্রোডাক্টের লিস্ট। ক্লায়েন্ট (তোমার অ্যাপ বা ব্রাউজার) এবং সার্ভারের মধ্যে এই ডেটা আদান-প্রদানের যে সুশৃঙ্খল মাধ্যম, সেটিই হলো REST API।

REST (Representational State Transfer) কোনো টুল বা লাইব্রেরি নয়, এটি মূলত একটি আর্কিটেকচারাল স্টাইল (Architectural Style) বা সফটওয়্যার তৈরির সুনির্দিষ্ট ডিজাইন প্যাটার্ন। ২০০০ সালে রয় ফিল্ডিং (Roy Fielding) তার গবেষণাপত্রে এই ধারণাটি প্রথম তুলে ধরেন। REST-এর নিয়মকানুন মেনে যে API তৈরি করা হয়, তাকেই আমরা RESTful API বলি। এটি ক্লায়েন্ট এবং সার্ভারের মধ্যে যোগাযোগের জন্য সাধারণত HTTP প্রোটোকল ব্যবহার করে। এটি ঠিক যেমন তুমি ফোনে কারও সাথে একটি নির্দিষ্ট ভাষায় কথা বলে তথ্য আদান-প্রদান করো!

REST আর্কিটেকচারের মূলনীতি (REST Principles)

একটি API-কে সম্পূর্ণভাবে "RESTful" হতে হলে তাকে নিচের ৬টি মূলনীতি (Constraints) মেনে চলতে হয়:

১. স্টেটলেসনেস (Statelessness): এটি REST-এর সবচেয়ে গুরুত্বপূর্ণ নিয়ম। এর মানে হলো, ক্লায়েন্ট থেকে সার্ভারে পাঠানো প্রতিটি রিকুয়েস্টে (Request) সেই কাজ সম্পন্ন করার জন্য প্রয়োজনীয় সকল ডেটা বা কনটেক্সট থাকতে হবে। সার্ভার ক্লায়েন্টের আগের কোনো সেশন বা রিকুয়েস্টের হিস্ট্রি মেমরিতে সেভ করে রাখবে না। এর ফলে সার্ভারের ওপর চাপ কমে এবং সিস্টেম খুব সহজেই স্কেল (Scale) করা যায়।

২. ক্লায়েন্ট-সার্ভার আর্কিটেকচার (Client-Server Architecture): ইউজার ইন্টারফেস (UI) বা ফ্রন্টএন্ড এবং ডেটা স্টোরেজ বা ব্যাকএন্ডের লজিক সম্পূর্ণ আলাদা থাকতে হবে। ক্লায়েন্ট শুধু জানবে কীভাবে ডেটা রিকুয়েস্ট করতে হয়, আর সার্ভার জানবে কীভাবে ডেটা প্রসেস করে পাঠাতে হয়। এতে ফ্রন্টএন্ড এবং ব্যাকএন্ড স্বাধীনভাবে ডেভেলপ ও আপডেট করা সম্ভব হয়।

৩. ক্যাশেবিলিটি (Cacheability): সার্ভারের রেসপন্সগুলোতে স্পষ্টভাবে উল্লেখ থাকতে হবে যে সেই ডেটা ক্লায়েন্ট ক্যাশ (Cache) করে রাখতে পারবে কি না। ক্যাশিং ব্যবহার করলে বারবার একই ডেটার জন্য সার্ভারকে রিকুয়েস্ট করতে হয় না, ফলে পারফরম্যান্স এবং স্পিড বহুগুণ বেড়ে যায়।

৪. ইউনিফর্ম ইন্টারফেস (Uniform Interface): পুরো সিস্টেমের ডিজাইন একই রকম বা স্ট্যান্ডার্ড হতে হবে। অর্থাৎ, নির্দিষ্ট HTTP Method (GET, POST, PUT, DELETE) নির্দিষ্ট কাজের জন্যই ব্যবহার করতে হবে এবং ডেটার ফরম্যাট (যেমন JSON) সর্বত্র একই নিয়মে আদান-প্রদান করতে হবে।

৫. লেয়ার্ড সিস্টেম (Layered System): ক্লায়েন্ট এবং সার্ভারের মধ্যে সরাসরি সংযোগ থাকার বাধ্যবাধকতা নেই। মাঝখানে প্রক্সি সার্ভার, লোড ব্যালেন্সার (Load Balancer), বা সিকিউরিটি লেয়ার (যেমন Nginx বা Cloudflare) থাকতে পারে। ক্লায়েন্ট বুঝতে পারবে না সে সরাসরি মূল সার্ভারের সাথে কথা বলছে নাকি কোনো মিডেল লেয়ারের সাথে।

৬. কোড অন ডিমান্ড (Code on Demand - Optional): এটি REST-এর একমাত্র ঐচ্ছিক (Optional) নিয়ম। সার্ভার চাইলে রেসপন্সের সাথে এক্সিকিউটেবল কোড (যেমন JavaScript) পাঠাতে পারে, যা সরাসরি ক্লায়েন্টের ব্রাউজারে রান করবে।

REST API এর মূল উপাদান (Components)

রিসোর্স এবং ইউআরআই (Resources and URI): REST API-তে সবকিছুকেই একটি "রিসোর্স (Resource)" হিসেবে ধরা হয় (যেমন: user, product, order, post)। প্রতিটি রিসোর্সকে খুঁজে পাওয়ার জন্য একটি ইউনিক ঠিকানা বা URI (Uniform Resource Identifier) থাকে। উদাহরণ: [https://api.example.com/users/123](https://api.example.com/users/123) (এখানে 123 আইডি যুক্ত ইউজার হলো একটি রিসোর্স)।

এইচটিটিপি মেথডস (HTTP Methods): রিসোর্সের উপর কী অপারেশন চালানো হবে, তা বোঝাতে HTTP Method ব্যবহৃত হয়:

  • GET: সার্ভার থেকে রিসোর্স বা ডেটা রিড (Read) করে আনার জন্য। (এটি Idempotent, অর্থাৎ বারবার কল করলেও ডেটার কোনো পরিবর্তন হয় না)।

  • POST: সার্ভারে সম্পূর্ণ নতুন একটি রিসোর্স তৈরি (Create) করার জন্য।

  • PUT: সার্ভারের কোনো বিদ্যমান রিসোর্সকে সম্পূর্ণভাবে আপডেট বা রিপ্লেস (Update/Replace) করার জন্য।

  • PATCH: বিদ্যমান রিসোর্সের আংশিক (Partial Update) পরিবর্তন করার জন্য (যেমন শুধু ইউজারের পাসওয়ার্ড পরিবর্তন)।

  • DELETE: সার্ভার থেকে নির্দিষ্ট রিসোর্স মুছে (Delete) ফেলার জন্য।

REST API ইউআরএল (URL) নেমিং স্ট্যান্ডার্ড

ভালো API ডিজাইনের জন্য URL গুলো পরিষ্কার এবং অর্থবহ হওয়া জরুরি।

  • নাউন (Nouns) ব্যবহার করো, ভার্ব (Verbs) নয়: URL-এ অ্যাকশন বোঝানো যাবে না, কারণ অ্যাকশন বোঝানোর জন্য HTTP Method আছে। (ভুল: /getUser | সঠিক: GET /users)

  • বহুবচন (Plural Nouns) ব্যবহার করো: রিসোর্সের নাম সবসময় Plural হওয়া উচিত। (ভুল: /user/123 | সঠিক: /users/123)

  • সবসময় লোয়ারকেস (Lowercase): URL-এ বড় হাতের অক্ষর পরিহার করা উচিত। (ভুল: /Users/John | সঠিক: /users/john)

  • মাল্টি-ওয়ার্ডের জন্য হাইফেন (-): স্পেস বা আন্ডারস্কোরের বদলে হাইফেন ব্যবহার করবে। (সঠিক: /user-profiles)

  • নেস্টেড রিসোর্স (Nested Resources): সম্পর্কযুক্ত ডেটা বোঝাতে প্যারেন্ট-চাইল্ড ফরম্যাট ব্যবহার করবে। (যেমন ইউজারের পোস্ট: /users/123/posts)

  • কোয়েরি প্যারামিটার (Query Parameters): ফিল্টারিং, সর্টিং বা পেজিনেশনের জন্য URL Path পরিবর্তন না করে Query String ব্যবহার করবে। (যেমন: /products?category=mobile&sort=price_desc&page=2)

REST API ডিজাইনের বেস্ট প্র্যাকটিস (Best Practices)

সঠিক HTTP Status Code ব্যবহার: সার্ভারের রেসপন্স কেমন ছিল, তা বোঝাতে সঠিক স্ট্যাটাস কোড পাঠানো খুব জরুরি।

  • 2xx (Success): 200 OK (সাধারণ সফল রিকুয়েস্ট), 201 Created (নতুন ডেটা তৈরি হলে), 204 No Content (ডিলিট সফল হলে)।

  • 4xx (Client Error): 400 Bad Request (ভুল ইনপুট), 401 Unauthorized (লগইন করা নেই), 403 Forbidden (পারমিশন নেই), 404 Not Found (রিসোর্স পাওয়া যায়নি)।

  • 5xx (Server Error): 500 Internal Server Error (সার্ভারের ভেতরের কোনো বাগ বা ক্র্যাশ)।

ভার্সনিং (Versioning): API রিলিজ করার পর এর স্ট্রাকচার পরিবর্তন করলে পুরনো ইউজারদের অ্যাপ ক্র্যাশ করতে পারে। তাই সবসময় URL-এ ভার্সন রাখা উচিত (যেমন: /api/v1/users বা /api/v2/users)।

পেজিনেশন এবং ফিল্টারিং (Pagination & Filtering): লাখ লাখ রেকর্ড একসাথে না পাঠিয়ে limit এবং offset বা page এর মাধ্যমে ডেটা ভাগ করে পাঠানো উচিত।

স্ট্যান্ডার্ড এরর হ্যান্ডলিং (Error Handling): এরর মেসেজগুলো সবসময় একটি নির্দিষ্ট JSON স্ট্রাকচারে পাঠানো উচিত, যাতে ফ্রন্টএন্ড সহজে বুঝতে পারে। (যেমন: { "error_code": "USER_NOT_FOUND", "message": "The requested user does not exist" })

অথেনটিকেশন এবং সিকিউরিটি (Authentication):

  • API Key: সহজ কিন্তু সিকিউরিটি কিছুটা কম।

  • OAuth 2.0: থার্ড-পার্টি অ্যাপের জন্য ইন্ডাস্ট্রি স্ট্যান্ডার্ড।

  • JWT (JSON Web Token): মডার্ন এবং স্টেটলেস API-এর জন্য সবচেয়ে জনপ্রিয় পদ্ধতি।

HATEOAS (Hypermedia as the Engine of Application State): এটি REST এর একটি অ্যাডভান্সড কনসেপ্ট। একটি আইডিয়াল REST API রেসপন্সের সাথে পরবর্তী সম্ভাব্য অ্যাকশনগুলোর লিংক (Hyperlinks) পাঠিয়ে দেয়, যাতে ক্লায়েন্ট সহজেই বুঝতে পারে এর পর কী করা সম্ভব।

REST API-এর সুবিধা এবং সীমাবদ্ধতা

সুবিধা:

  • খুবই সহজ, ক্লিন এবং ডেভেলপারদের বুঝতে সুবিধা হয়।

  • স্টেটলেস হওয়ায় সার্ভার স্কেল (Scale) করা খুব সহজ।

  • ওয়েবের স্ট্যান্ডার্ড HTTP প্রোটোকলের সর্বোচ্চ ব্যবহার করে।

  • অসাধারণ ক্যাশিং মেকানিজম সাপোর্ট করে যা পারফরম্যান্স বাড়ায়।

  • যেকোনো প্রোগ্রামিং ল্যাঙ্গুয়েজ এবং প্ল্যাটফর্মে কাজ করে।

সীমাবদ্ধতা:

  • Over-fetching: প্রয়োজনের চেয়ে বেশি ডেটা চলে আসে। (যেমন: শুধু নাম দরকার হলেও API পুরো ইউজার অবজেক্ট পাঠিয়ে দেয়)।

  • Under-fetching (N+1 Problem): প্রয়োজনীয় সব ডেটা পেতে একাধিক এন্ডপয়েন্টে রিকুয়েস্ট করতে হয়।

  • REST-এর কোনো নির্দিষ্ট বিল্ট-ইন স্কিমা (Schema) নেই, যা SOAP-এর WSDL-এ থাকে।

  • এই ওভার-ফেচিং এবং আন্ডার-ফেচিং সমস্যা সমাধানের জন্যই বর্তমানে GraphQL অনেক জনপ্রিয়তা পাচ্ছে।

পাইথন এবং জাভাতে REST API-এর উদাহরণ

যেহেতু তুমি ব্যাকএন্ড ডেভেলপমেন্ট নিয়ে কাজ করো, তাই নিচে পাইথন (FastAPI) এবং জাভা (Spring Boot) ব্যবহার করে একটি স্ট্যান্ডার্ড REST API তৈরির উদাহরণ দেওয়া হলো।

Python Example (FastAPI - Blog API Implementation):

from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime

app = FastAPI(title="Blog REST API")

class Post(BaseModel):
    id: int
    title: str
    content: str
    author: str
    created_at: Optional[datetime] = None

# Mock database
posts_db = []

@app.get("/api/v1/posts", response_model=List[Post], status_code=status.HTTP_200_OK)
def get_all_posts(limit: int = 10, page: int = 1):
    # Logic for pagination would go here
    return posts_db

@app.get("/api/v1/posts/{post_id}", response_model=Post)
def get_single_post(post_id: int):
    for post in posts_db:
        if post.id == post_id:
            return post
    raise HTTPException(status_code=404, detail="Post resource not found")

@app.post("/api/v1/posts", response_model=Post, status_code=status.HTTP_201_CREATED)
def create_post(post: Post):
    post.created_at = datetime.utcnow()
    posts_db.append(post)
    return post

@app.delete("/api/v1/posts/{post_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_post(post_id: int):
    for i, post in enumerate(posts_db):
        if post.id == post_id:
            del posts_db[i]
            return
    raise HTTPException(status_code=404, detail="Post not found to delete")

Java Example (Spring Boot - REST Controller):

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/api/v1/posts")
public class PostController {

    // Mock database collection
    private List<Post> postsDb = new ArrayList<>();

    @GetMapping
    public ResponseEntity<List<Post>> getAllPosts(@RequestParam(defaultValue = "1") int page) {
        // Pagination logic goes here
        return ResponseEntity.ok(postsDb);
    }

    @GetMapping("/{id}")
    public ResponseEntity<?> getSinglePost(@PathVariable Long id) {
        Optional<Post> post = postsDb.stream().filter(p -> p.getId().equals(id)).findFirst();
        
        if (post.isPresent()) {
            return ResponseEntity.ok(post.get());
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(new ErrorResponse("POST_NOT_FOUND", "The requested post does not exist."));
        }
    }

    @PostMapping
    public ResponseEntity<Post> createPost(@RequestBody Post newPost) {
        postsDb.add(newPost);
        return ResponseEntity.status(HttpStatus.CREATED).body(newPost);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deletePost(@PathVariable Long id) {
        boolean removed = postsDb.removeIf(p -> p.getId().equals(id));
        if (removed) {
            return ResponseEntity.noContent().build(); // 204 No Content
        } else {
            return ResponseEntity.notFound().build(); // 404 Not Found
        }
    }
}

// Dummy classes for structure
class Post {
    private Long id;
    private String title;
    
    public Long getId() { return id; }
    // Other getters and setters omitted for brevity
}

class ErrorResponse {
    public String errorCode;
    public String message;
    
    public ErrorResponse(String errorCode, String message) {
        this.errorCode = errorCode;
        this.message = message;
    }
}