Project 2: Notes API + Flutter App

নিজের backend, নিজের Flutter app

রোডম্যাপের মাস ৪পড়তে প্রায় ৭ মিনিট

এই project এ তুমি প্রথমবার নিজের পুরো product বানাবে: নিজের backend, নিজের Flutter app, দুটো একসাথে কথা বলবে। এখন থেকে AI ব্যবহার করতে পারো, কিন্তু নিয়ম একটাই: AI এর লেখা প্রতিটা লাইন তুমি পড়বে আর বুঝবে, আর test তোমার ঠিক করা ক্ষেত্রগুলো যাচাই করবে। শেষে এটা তোমার portfolio এর প্রথম বড় project হবে।

#কী বানাবো: "নোটখাতা"

একটা note নেওয়ার app, যেটা ফোন আর web দুই জায়গাতেই চলে:

  • Email/password দিয়ে register আর login
  • Note তৈরি, পড়া, বদলানো, মুছে ফেলা
  • Pin করা note সবার উপরে
  • Note এ একটা ছবি যোগ করা
  • Title আর লেখায় খোঁজা
  • Tag দিয়ে ভাগ করা
  • Infinite scroll (cursor pagination)
  • Logout, আর সব device থেকে logout

#Non-functional requirement

বিষয় লক্ষ্য
Security অন্যের note কোনোভাবেই দেখা যাবে না (test দিয়ে প্রমাণিত)
Performance List API ৫০০০ note থাকলেও ১০০ ms এর নিচে (local এ)
দুর্বল network Retry এ duplicate note হবে না (idempotency key)
Token Access ১৫ মিনিট, refresh ৩০ দিন, rotation সহ
Test Backend এ coverage ৮০%+, সব permission এর test
Code quality Ruff, Pyright পরিষ্কার

#Architecture

Flutter app Dio + Riverpod/Bloc FastAPI auth, notes, tags, upload router → service → repo PostgreSQL Object storage ছবি (R2/S3/MinIO) HTTPS ছবি দেখা: সরাসরি storage/CDN থেকে
App শুধু API এর সাথে কথা বলে। ছবি upload API দিয়ে (ছোট বলে), কিন্তু দেখার সময় সরাসরি storage এর URL থেকে।

#API এর তালিকা

Method Path কাজ
POST /v1/auth/register নতুন account
POST /v1/auth/login Token জোড়া
POST /v1/auth/refresh নতুন token জোড়া (rotation)
POST /v1/auth/logout এই device এর refresh token বাতিল
POST /v1/auth/logout-all সব device
GET /v1/me নিজের profile
GET /v1/notes?cursor=&limit=&search=&tag= Note এর list
POST /v1/notes নতুন (Idempotency-Key header সহ)
GET /v1/notes/{id} একটা note
PATCH /v1/notes/{id} বদলানো
DELETE /v1/notes/{id} মুছে ফেলা (soft)
POST /v1/notes/{id}/image ছবি upload
GET /v1/tags নিজের সব tag

#Database

Text
users          (id, email UNIQUE, name, password_hash, is_active, created_at, updated_at)
refresh_tokens (id, user_id FK, token_hash UNIQUE, device_name, expires_at, revoked_at, created_at)
notes          (id, owner_id FK, title, content, is_pinned, image_key, idempotency_key,
                deleted_at, created_at, updated_at)
                UNIQUE (owner_id, idempotency_key)
                INDEX (owner_id, is_pinned, updated_at DESC)
tags           (id, owner_id FK, name)  UNIQUE (owner_id, name)
note_tags      (note_id FK, tag_id FK)  PRIMARY KEY (note_id, tag_id)

#কাজের ধাপ (প্রায় ৪ সপ্তাহ)

সপ্তাহ ১: Backend এর ভিত্তি

সপ্তাহ ২: Notes

  • Note এর CRUD, মালিকানা যাচাই
  • Cursor pagination, search, tag
  • Idempotency key
  • IDOR test সহ সব test

সপ্তাহ ৩: Flutter app

  • Login/register screen, token secure storage এ
  • Dio interceptor: token যোগ, 401 এ refresh, refresh ব্যর্থ হলে login এ ফেরা (Flutter সংযোগ অধ্যায় আগে একবার দেখে নিতে পারো)
  • Note list (infinite scroll, pull to refresh), detail, edit, search

সপ্তাহ ৪: ছবি, polish, README

  • ছবি upload আর দেখানো
  • Error গুলো সুন্দর করে দেখানো (error.code অনুযায়ী)
  • Empty state, loading state, offline এ বার্তা
  • README আর demo video

#Flutter দিকের কিছু খুঁটিনাটি

Dart (Flutter)
// Idempotency: button চাপার সময় key বানাও, retry তে একই key
final key = const Uuid().v4();
await dio.post('/v1/notes',
    data: note.toJson(),
    options: Options(headers: {'Idempotency-Key': key}));
Dart (Flutter)
// Error parse: সব endpoint এর error একই ধরনে আসে
class ApiError {
  final String code;
  final String message;
  final Map<String, String> fields;
  // ...
}

Android emulator থেকে Mac এর localhost: http://10.0.2.2:8000। আসল phone থেকে: Mac এর local IP (192.168.x.x:8000) আর uvicorn কে --host 0.0.0.0 দিয়ে চালাতে হবে।

#Review checklist (AI এর code সহ)

#শেষ হলে

  • GitHub এ দুইটা repo (backend, flutter) বা একটা monorepo
  • README তে: কী, কেন, architecture এর ছবি, কীভাবে চালাবে, API এর তালিকা, screenshot, কী শিখলে
  • ২ মিনিটের demo video: login, note তৈরি, ছবি, search, অন্য user দিয়ে প্রমাণ যে অন্যের note দেখা যায় না
  • Month 5 এ এটা Docker এ ভরে internet এ deploy করবে

#নিজেকে যাচাই করো

ছবি দেখার সময় কেন API দিয়ে না এনে সরাসরি storage/CDN এর URL ব্যবহার করা ভালো?

ছবি বড় আর বারবার দেখা হয়। API দিয়ে আনলে প্রতিবার app server এর worker আর bandwidth খরচ হয়। Storage বা CDN থেকে সরাসরি আনলে দ্রুত, সস্তা, আর app server এর চাপ কমে। ব্যক্তিগত ছবি হলে মেয়াদি signed URL ব্যবহার করা যায়।

Note এর table এ `(owner_id, idempotency_key)` UNIQUE কেন, শুধু `idempotency_key` না কেন?

Key টা client বানায়, তাই দুইজন আলাদা user এর key দৈবাৎ মিলতে পারে (খুব কম সম্ভাবনা হলেও)। User ভিত্তিক unique রাখলে একজনের key আরেকজনের কাজে বাধা দেয় না, আর একজন user অন্যের key আন্দাজ করে তার note পাওয়ার সুযোগও থাকে না।

চিহ্ন দিলে sidebar আর রোডম্যাপে এই অধ্যায়ের station ভরে যাবে।