Systems Hub
সবাই Prisma ব্যবহার করে,
কিন্তু সবাই একই ভুল করে
প্রতিটা route-এ আলাদা query লেখা,
N+1 problem, no cache, no error handling —
একটু বড় হলেই system আটকে যায়
Module Architecture buildQueryOptions() Redis Cache Zod Validation N+1 Fix
Professional Prisma শিখব আজকে
SLIDE 01

যে ভুলটা সবাই করে

Junior developer-এর সবচেয়ে বড় সমস্যা
ভুল pattern: প্রতিটা route-এ আলাদা করে একই কাজ
// route 1 const users = await prisma.user.findMany({ where: { role: req.query.role }, take: parseInt(req.query.limit) || 10, skip: parseInt(req.query.page) * 10, orderBy: { createdAt: 'desc' } }); // route 2 — একই জিনিস আবার লেখা! const courses = await prisma.course.findMany({ where: { status: req.query.status }, take: parseInt(req.query.limit) || 10, skip: parseInt(req.query.page) * 10, orderBy: { createdAt: 'desc' } });
সঠিক pattern: একবার লেখো, সব জায়গায় ব্যবহার করো
// একটাই function — সব route-এ কাজ করে const opts = buildQueryOptions(req.query); const users = await prisma.user.findMany({ ...opts, where: { role: req.query.role } }); const courses = await prisma.course.findMany({ ...opts, where: { status: req.query.status } }); // buildQueryOptions একবার লিখলেই // সব জায়গায় pagination + sort কাজ করে
SLIDE 02

Module-Based Architecture

প্রতিটা feature নিজের ঘরে থাকে

ফোল্ডার Structure

src/ modules/ user/ user.router.ts ← routes user.controller.ts ← req/res user.service.ts ← business logic user.schema.ts ← Zod validation course/ course.router.ts course.controller.ts course.service.ts auth/ auth.router.ts auth.service.ts

কেন এই structure?

  • user module → শুধু user-এর কাজ
  • course module → শুধু course-এর কাজ
  • Bug খুঁজতে পুরো codebase ঘাঁটতে হয় না
  • নতুন developer সহজে বুঝতে পারে
💡
উদাহরণ: রহিম user feature কাজ করে, করিম course feature কাজ করে — তারা কখনো conflict করে না।

Separation of Concerns

  • Controller: শুধু HTTP req/res handle
  • Service: business logic — test করা সহজ
  • Router: শুধু path + middleware
SLIDE 03

buildQueryOptions() — Reusable Query Builder

একবার লেখো, সব জায়গায় ব্যবহার করো
function buildQueryOptions(query: any) { const page = Math.max( parseInt(query.page) || 1, 1 ); const limit = Math.min( parseInt(query.limit) || 10, 100 ); const skip = (page - 1) * limit; const orderBy: any = {}; if (query.sortBy) { orderBy[query.sortBy] = query.sortOrder || 'desc'; } else { orderBy.createdAt = 'desc'; } return { take: limit, skip, orderBy }; } // ব্যবহার — যেকোনো model-এ const opts = buildQueryOptions(req.query); const users = await prisma.user.findMany({ ...opts, where: filters });

কী পাই?

  • Pagination: page, limit → skip, take
  • Sorting: sortBy, sortOrder
  • Max limit 100 — DoS attack বন্ধ
  • Default values — যদি না দেওয়া হয়
এখন GET /users?page=2&limit=20&sortBy=name একটাই function handle করে। course, video, comment — সব জায়গায় কাজ করবে।
limit=10000 দিলে কী হবে? Math.min(10000, 100) = 100। Server সুরক্ষিত।
SLIDE 04

Constants দিয়ে Behavior Control

Magic number নয়, নাম দিয়ে বুঝাও
Magic numbers — কেউ বুঝবে না
// এই 900, 3600, 86400 কী? await redis.setex(key, 900, data); await redis.setex(key2, 3600, data2); await redis.setex(key3, 86400, data3); // limit 10, 100, 5 মানে কী? if (comment.length > 500) throw error; if (attempts > 5) blockUser();
Constants দিয়ে পরিষ্কার
const CACHE_TTL = { USER: 15 * 60, // 15 মিনিট COURSE: 60 * 60, // 1 ঘণ্টা ENROLLMENT: 24 * 60 * 60, // 1 দিন }; const LIMITS = { COMMENT_MAX_LENGTH: 500, LOGIN_ATTEMPTS: 5, PAGE_SIZE: 10, }; // এখন বুঝা যায় await redis.setex(key, CACHE_TTL.USER, data); if (attempts > LIMITS.LOGIN_ATTEMPTS) block();

Constants-এর সুবিধা

  • একজায়গায় বদলালে সব জায়গায় বদলায়
  • Code পড়লেই বোঝা যায় কী হচ্ছে
  • Bug খুঁজতে সহজ

উদাহরণ

  • CACHE_TTL.USER = 900 → শুধু একবার বদলাও
  • সব 35টা জায়গা automatically update
  • রহিম ভুলে 15 মিনিটের জায়গায় 900 লিখল → সমস্যা
  • CACHE_TTL.USER লিখলে ভুল হওয়ার সুযোগ নেই
SLIDE 05

Zod + Middleware — Input Validation

ভুল data আসার আগেই ধরো
// Schema define করো const createUserSchema = z.object({ name: z.string().min(2).max(50), email: z.string().email(), password: z.string().min(8), role: z.enum(['student','teacher']), }); // Reusable validation middleware function validate(schema: ZodSchema) { return (req, res, next) => { const result = schema.safeParse(req.body); if (!result.success) { return res.status(400).json({ error: 'Validation failed', issues: result.error.issues.map(i => ({ field: i.path.join('.'), message: i.message })) }); } req.body = result.data; // clean data next(); }; } // Route-এ ব্যবহার router.post('/users', validate(createUserSchema), createUser );

Zod কী করে?

  • রহিম যদি name-এ "A" দেয় → min(2) fail
  • Email ভুল format → email() fail
  • Password কম → min(8) fail
  • Controller কখনো ভুল data পায় না
Validation middleware → Controller-এ এসে দেখবে data ১০০% সঠিক। আর defensive check করতে হবে না।
⚠️
Validation ছাড়া: করিম name-এ 10,000 character দিল → DB crash, বা SQL injection attack!
SLIDE 06

Centralized Error Handling

সব error এক জায়গায় সামলাও
প্রতি route-এ try-catch — কোড ডুপ্লিকেট
// Custom Error class class AppError extends Error { statusCode: number; constructor(message: string, code: number) { super(message); this.statusCode = code; } } // Service-এ throw করো if (!user) throw new AppError( 'User পাওয়া যায়নি', 404 ); // Global error middleware app.use((err, req, res, next) => { if (err instanceof AppError) { return res.status(err.statusCode).json({ success: false, error: err.message }); } // Prisma error if (err.code === 'P2025') { return res.status(404).json({ error: 'Record পাওয়া যায়নি' }); } // Unknown error res.status(500).json({ error: 'Server error' }); });

সুবিধা

  • Service → শুধু throw করে
  • Controller → try-catch নেই
  • Error format সব জায়গায় একই
  • Prisma error → user-friendly message
রহিম যখন নেই এমন user খোঁজে: Service → throw AppError(404) → Global middleware → JSON response। Controller-এ এক লাইনও error handling নেই।
// Controller — কত clean! async function getUser(req, res) { const user = await userService .findById(req.params.id); res.json({ success: true, data: user }); // error হলে global middleware সামলাবে }
SLIDE 07

Redis Cache on Top of Prisma

একই query বারবার DB-তে না পাঠাও
class CourseService { async findById(id: string) { // Step 1: Redis-এ আছে? const cacheKey = `course:${id}`; const cached = await redis.get(cacheKey); if (cached) return JSON.parse(cached); // Step 2: DB থেকে নাও const course = await prisma.course.findUnique({ where: { id }, include: { teacher: true, lessons: { orderBy: { order: 'asc' } } } }); if (!course) throw new AppError( 'Course পাওয়া যায়নি', 404 ); // Step 3: Cache করো await redis.setex( cacheKey, CACHE_TTL.COURSE, JSON.stringify(course) ); return course; } async update(id: string, data: any) { const course = await prisma.course.update( { where: { id }, data } ); // Cache invalidate করো await redis.del(`course:${id}`); return course; } }

Cache Pattern

  • Read → Redis first, DB second
  • Write → DB update + Redis delete
  • Next read → fresh DB data cache হবে
১০,০০০ জন একই course দেখছে → ১টা DB query, ৯,৯৯৯টা Redis hit। Database-এর উপর চাপ নেই।
⚠️
Update করলে cache delete করতে ভুলবে না! নাহলে পুরনো data দেখাবে — stale cache bug।
SLIDE 08

একটা API Request-এর পুরো যাত্রা

GET /courses?page=1&limit=10 — কী কী হয়?

Router — URL match করে, middleware chain শুরু

authenticate middleware — JWT verify, Redis থেকে user নাও

validate middleware — Zod দিয়ে query params check (page, limit valid?)

Controller — buildQueryOptions() call করো, service-এ পাঠাও

Service — Redis check → miss → Prisma query → DB → result cache → return

Controller — paginated response format করো, JSON পাঠাও

💡
প্রতিটা layer একটাই কাজ করে। Error যেকোনো জায়গায় হলে Global Error Middleware সামলাবে। Controller সবসময় clean।
SLIDE 09

এই Patterns দিয়ে কী পেলাম?

Professional-grade Prisma + PostgreSQL

Maintainable

  • Module architecture → সব আলাদা
  • Constants → একবার বদলাও
  • Reusable functions → DRY code

Fast

  • Redis cache → DB load কম
  • buildQueryOptions() → optimize query
  • Pagination → full table scan নেই

Safe

  • Zod validation → ভুল data ঢোকে না
  • Centralized error → consistent response
  • AppError → debug সহজ
🎯
এই ৫টা pattern শিখলে তোমার Prisma code যেকোনো Senior developer দেখলে বলবে — "এই বান্দা জানে।"
সব একসাথে ধাপে ধাপে