এক্সপ্রেস এপিআই অথেনটিকেশন

এর আগের লেখাতে আমরা দেখেছি কিভাবে সিম্পল একটা অ্যাপের জন্য API তৈরি করা যায়। লিংকঃ এক্সপ্রেস জেএস ব্যবহার করে সিম্পল CRUD API তৈরি। যেখানে প্রয়োজনীয় CRUD অপারেশন গুলো করা যেত। কিন্তু সমস্যা হচ্ছে যে কেউ এই API এক্সেস করে ইচ্ছে মত ডেটা তৈরি, আপডেট বা ডিলেট করতে পারবে। এর সমাধান হচ্ছে শুধু মাত্র অথেনটীকেট ইউজারকে আমরা নোট তৈরি করতে দিব। এমনকি শুধু নিজের নোট নিজে দেখতে পাবে, অন্য কারো নোট দেখতে পাবে না। এর জন্য আরেকটা নতুন টেবিল লাগবে ডেটাবেজে। যেখানে ইউজার রেজিস্ট্রেশন করার পর তাদের ডেটা গুলো রাখব।

আগের পোস্ট ফলো না করলে ঐটা আগে ফলো করতে হবে। কারণ আগের লেখার কোড গুলো এই লেখাতেও কাজে লাগবে।

db.js ফাইল আপডেট করতে হবেঃ

import sqlite3 from "sqlite3";
import { open } from "sqlite";

const dbPromise = open({
  filename: "./notes.db",
  driver: sqlite3.Database
});

(async () => {
  const db = await dbPromise;

  // Users table
  await db.run(`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      username TEXT UNIQUE NOT NULL,
      password TEXT NOT NULL,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )
  `);

  // Notes table (link to user_id)
  await db.run(`
    CREATE TABLE IF NOT EXISTS notes (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      user_id INTEGER NOT NULL,
      title TEXT NOT NULL,
      content TEXT NOT NULL,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (user_id) REFERENCES users(id)
    )
  `);
})();

export default dbPromise;

যখন কেউ রেজিস্ট্রেশন করবে, তখন তার পাসওয়ার্ড তো আমরা সরাসরি প্লেইন টেক্সট আকারে রাখতে পারি না। এতে যে কেউ কারো পাসওয়ার্ড জেনে যাবে। ভালো হয় এনক্রিপশন করে রাখলে। নডের জন্য সুন্দর একটা এনক্রিপশন প্যাকেজ হচ্ছে bcryptjs। যা যে কোন টেক্সটকে এনক্রিপ্ট করে দিবে। কেউ তার পাসওয়ার্ড দিয়ে এরপর লগিন করার চেষ্টা করলে তা এনক্রিপ্ট Bcrypt ফাংশন ব্যবহার করে ডাটাবেজে থাকা ভ্যালুর সাথে মিলিয়ে দেখবে। সঠিক হলেই একটা টোকেন জেনারেট করে দিবে। এর জন্য আমরা নিচের দুইটা প্যাকেজ ব্যবহার করবঃ

npm install bcryptjs jsonwebtoken

অথেনটিকেশনের জন্য আলাদা একটা রাউট ফাইল তৈরি করব (routes/auth.js)

import express from "express";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import dbPromise from "../db.js";

const router = express.Router();
const JWT_SECRET = "your_secret_key"; // use env variable in production

// 📝 Register
router.post("/register", async (req, res) => {
  const { username, password } = req.body;
  if (!username || !password) return res.status(400).json({ error: "Required" });

  const hashedPassword = await bcrypt.hash(password, 10);
  const db = await dbPromise;
  try {
    const result = await db.run(
      "INSERT INTO users (username, password) VALUES (?, ?)",
      [username, hashedPassword]
    );
    res.json({ message: "User created", id: result.lastID });
  } catch (err) {
    res.status(400).json({ error: "Username already exists" });
  }
});

// 🔑 Login
router.post("/login", async (req, res) => {
  const { username, password } = req.body;
  const db = await dbPromise;
  const user = await db.get("SELECT * FROM users WHERE username = ?", [username]);
  if (!user) return res.status(400).json({ error: "Invalid credentials" });

  const isMatch = await bcrypt.compare(password, user.password);
  if (!isMatch) return res.status(400).json({ error: "Invalid credentials" });

  // Sign JWT
  const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, {
    expiresIn: "1h",
  });

  res.json({ token });
});

export default router;

মিডেলওয়ার তৈরি করব (middleware/auth.js):

import jwt from "jsonwebtoken";
const JWT_SECRET = "your_secret_key"; // use env variable in production

export const authenticate = (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader) return res.status(401).json({ error: "No token provided" });

  const token = authHeader.split(" ")[1];
  if (!token) return res.status(401).json({ error: "Invalid token format" });

  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    req.user = decoded; // attach user info to request
    next();
  } catch (err) {
    res.status(401).json({ error: "Invalid token" });
  }
};

যদি কেউ ভুল টোকেন ব্যবহার করে, তাহলে আমরা এরর দেখাবো। তা এই মিডেলওয়ারে চেক করেছি। এরপর আমাদের নোট রাউটে এই মিডেলওয়ার ব্যবহার করলে রাউট গুলো সিকিউর হয়ে যাবে। শুধু মাত্র নিচের লাইন যোগ করলেই হবেঃ

import { authenticate } from "../middleware/auth.js";
router.use(authenticate);

বাকি কোড আগের মত থাকবে। সম্পূর্ন নোট রাউট (routes/notes.js):

import express from "express";
import dbPromise from "../db.js";
import { authenticate } from "../middleware/auth.js";

const router = express.Router();

// All routes now use `authenticate` middleware
router.use(authenticate);

// ✅ Create Note
router.post("/", async (req, res) => {
  const { title, content } = req.body;
  const db = await dbPromise;
  const result = await db.run(
    "INSERT INTO notes (user_id, title, content) VALUES (?, ?, ?)",
    [req.user.id, title, content]
  );
  res.json({ id: result.lastID, title, content });
});

// 📖 Get All Notes for logged-in user
router.get("/", async (req, res) => {
  const db = await dbPromise;
  const notes = await db.all("SELECT * FROM notes WHERE user_id = ? ORDER BY created_at DESC", [
    req.user.id,
  ]);
  res.json(notes);
});

// 🔍 Get Single Note (only if owner)
router.get("/:id", async (req, res) => {
  const db = await dbPromise;
  const note = await db.get("SELECT * FROM notes WHERE id = ? AND user_id = ?", [
    req.params.id,
    req.user.id,
  ]);
  if (!note) return res.status(404).json({ error: "Note not found" });
  res.json(note);
});

// ✏️ Update Note
router.put("/:id", async (req, res) => {
  const { title, content } = req.body;
  const db = await dbPromise;
  const result = await db.run(
    "UPDATE notes SET title = ?, content = ? WHERE id = ? AND user_id = ?",
    [title, content, req.params.id, req.user.id]
  );
  if (result.changes === 0) return res.status(404).json({ error: "Note not found" });
  res.json({ message: "Note updated" });
});

// ❌ Delete Note
router.delete("/:id", async (req, res) => {
  const db = await dbPromise;
  const result = await db.run("DELETE FROM notes WHERE id = ? AND user_id = ?", [
    req.params.id,
    req.user.id,
  ]);
  if (result.changes === 0) return res.status(404).json({ error: "Note not found" });
  res.json({ message: "Note deleted" });
});

export default router;

এবার index.js এ অথেনটিকেশন রাউট যোগ করব। তাহলেই অ্যাপ রেডি।

import express from "express";
import notesRouter from "./routes/notes.js";
import authRouter from "./routes/auth.js";

const app = express();
const PORT = 3000;

app.use(express.json());

app.use("/auth", authRouter); // /auth/register, /auth/login
app.use("/notes", notesRouter);

app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));

এবার আমরা এই অ্যাপে রেজিস্টার করতে পারব। তার জন্য যেতে হবে http://localhost:3000/auth/register রাউটে। এরপর Body তে raw ডেটা হিসেবে ইউজারনেম এবং পাসওয়ার্ড দিলে রেজিস্ট্রেশন করতে পারব। খেয়াল রাখতে হবে, এটা পোস্ট মেথডে কল করব।

{
  "username": "jack",
  "password": "123123"
}

লগিন রাউটঃ http://localhost:3000/auth/login

{
  "username": "jack",
  "password": "123123"
}

লগিন করার পর আমরা একটা টোকেন পাবো। এবার নোট তৈরি করা সহ যে কোন কাজ করার ক্ষেত্রে এই টোকেন ব্যবহার করতে হবে।

যেমন নতুন নোট তৈরি করার জন্য এই রাউটে পোস্ট রিকোয়েস্ট করবঃ http://localhost:3000/notes

সব কিছু আগের মতই। তবে এখানে এক্সট্রা হিসেবে টোকেন পাস করতে হবে। তার জন্য Authorization ট্যাব থেকে Type হিসেবে Bearer Token সিলেক্ট করব। এরপর ডানপাশের টোকেন ফিল্ডে টোকেন দিয়ে এরপর রিকোয়েস্ট করব। সব ঠিক থাকলে নোট তৈরি হবে।

গেট মেথডেও একই ভাবে যদি আমরা টোকেন পাস করি, তাহলে ঐ ইউজারের নোট গুলো দেখা যাবে। একই ভাবে আপডেট এবং ডিলেটও টোকেন ছাড়া করা যাবে না।

Leave a Comment