فلٹر سے بیک اینڈ تک: ڈارٹ اور ڈارٹ فراگ کے ساتھ پروڈکشن گریڈ REST APIs کیسے بنائیں

ڈارٹ بیک اینڈ فریم ورک سپیکٹرم پر موجود ہیں۔ کم سے کم اختتام پر شیلف ہے، خام قدیم اور مکمل کنٹرول کے ساتھ۔ ہر چیز کو براہ راست جوڑیں۔ بالکل آخر میں سرور پوڈ ہے۔ یہ کوڈ جنریشن اور ملکیتی قوانین کے ساتھ ایک مکمل فریم ورک ہے۔ فریم ورک زیادہ تر ساختی فیصلے کرتا ہے۔

ڈارٹ فراگ بیچ میں کہیں گرتا ہے اور بہت سے فلٹر انجینئرز کے لیے یہ قدرتی شکل ہے۔

ڈارٹ فراگ ایک تیز، کم سے کم بیک اینڈ فریم ورک ہے جو شیلف کے اوپر بنایا گیا ہے، جو اصل میں ویری گڈ وینچرز کے ذریعہ بنایا گیا تھا اور اب آزادانہ طور پر برقرار رکھا گیا ہے۔ یہ Next.js اور Remix کے ذریعے مقبول کردہ فائل پر مبنی روٹنگ ماڈل کو لیتا ہے اور اسے ایک صاف CLI میں لپیٹتا ہے جو ڈویلپمنٹ سرورز، ہاٹ ری لوڈز، پروڈکشن بلڈز، اور Docker تخلیق کو باکس سے باہر ہینڈل کرتا ہے۔

بس اپنے پاتھ/ڈائریکٹری میں ایک ڈارٹ فائل لکھیں اور ایک onRequest فنکشن کا اخراج کریں اور Dart Frog آپ کے لیے روٹنگ کو سنبھالے گا۔ کوئی روٹر کنفیگریشن نہیں ہے، کوئی ہینڈلر رجسٹریشن نہیں ہے، کوئی ماؤنٹ نہیں ہے۔ فائل سسٹم روٹر ہے۔

اس آرٹیکل میں، آپ ڈارٹ فراگ کو صارف اور پروفائل مینجمنٹ REST API بنانے کے لیے استعمال کریں گے (اوپر لنک کردہ آرٹیکل میں وہی بنایا گیا ہے)، اسے PostgreSQL سے جوڑیں گے، JWT تصدیق شامل کریں گے، اور اسے Fly.io پر تعینات کریں گے۔

آخر تک، آپ کو Dart Frog کے روٹنگ ماڈل کے بارے میں گہری سمجھ ہو گی اور اس کی واضح تصویر ہو گی کہ یہ Shelf اور Serverpod کے مقابلے میں کہاں فٹ بیٹھتا ہے۔

انڈیکس

شرطیں

شروع کرنے سے پہلے، آپ کے پاس ہونا ضروری ہے:

  • ڈارٹ اور فلٹر کی نشوونما کے ساتھ آرام دہ واقفیت

  • REST API کے تصورات، اختتامی نکات، HTTP طریقوں اور اسٹیٹس کوڈز کو سمجھنا۔

  • ڈوکر ڈیسک ٹاپ انسٹال اور چل رہا ہے۔

  • تقسیم کے لیے Fly.io اکاؤنٹ

ڈارٹ فراگ شیلف اور سرور پوڈ سے کیسے مختلف ہے۔

یہ سمجھنا کہ دیگر دو فریم ورکس کے سلسلے میں ڈارٹ فراگ کہاں کھڑا ہے آپ کو ہر پروجیکٹ کے لیے صحیح انتخاب کرنے میں مدد ملے گی۔

شیلف ایک روٹر فراہم کرتا ہے اور ہینڈلر کو دستی طور پر ماؤنٹ کرتا ہے۔ فولڈر کی ساخت کا URL ڈھانچے سے کوئی تعلق نہیں ہے۔ فیصلہ کریں کہ کیا کہاں جاتا ہے۔

Serverpod اختتامی نقطہ کلاس کے نام اور طریقہ کے نام سے ایک راستہ تیار کرتا ہے۔ جب آپ کلاس کی وضاحت کرتے ہیں اور جنریٹر چلاتے ہیں، تو URL خود بخود اخذ ہو جائے گا۔

ڈارٹ فراگ فائل سسٹم کو براہ راست یو آر ایل ڈھانچے میں نقش کرتا ہے۔ routes/users/index.dart میں فائل /users اینڈ پوائنٹ بن جاتی ہے۔ پاتھ/صارفین/ میں فائلیں[id].dart /users/:id بن جاتا ہے۔ کوئی ترتیب، رجسٹریشن یا تخلیق کے مراحل نہیں ہیں۔ فائل راستہ ہے۔

یہ ماڈل Flutter انجینئرز کے لیے فوری طور پر بدیہی محسوس کرے گا جنہوں نے Next.js یا کوئی جدید ویب فریم ورک استعمال کیا ہے۔ ٹیم کے اندر تشریف لانا بھی بہت آسان ہے۔ جب آپ فولڈر کے ڈھانچے کو دیکھتے ہیں، تو آپ فوراً بتا سکتے ہیں کہ کون سے اختتامی نقطے ہیں۔

ایک اور بڑا فرق RequestContext ہے۔ جب شیلف کسی ہینڈلر کو خام درخواست بھیجتا ہے، تو ڈارٹ فراگ اسے ایک RequestContext میں لپیٹ دیتا ہے جو کہ درخواست اور مڈل ویئر کے ذریعے انجکشن کی گئی کسی بھی قدر کو پاس کرتا ہے۔ یہ ڈارٹ فراگ کا انحصار انجیکشن میکانزم ہے، اور یہ خوبصورت ہے۔

ڈارٹ میڑک کی تنصیب

ڈارٹ فراگ سی ایل آئی انسٹال کریں:

dart pub global activate dart_frog_cli

تنصیب کی تصدیق کریں۔

dart_frog --version

ایک پروجیکٹ بنائیں

dart_frog create user_profile_api
cd user_profile_api

ہاٹ ری لوڈ کے ساتھ ڈویلپمنٹ سرور شروع کریں۔

dart_frog dev

اگر آپ http://localhost:8080 پر جاتے ہیں تو آپ کو پہلے سے طے شدہ استقبال کا جواب نظر آئے گا۔ ڈویلپمنٹ سرور فائل کی تبدیلیوں کو دیکھتا ہے اور خود بخود انہیں دوبارہ لوڈ کرتا ہے۔ تعمیر کے دوران دوبارہ شروع کرنے کی ضرورت نہیں ہے۔

پروجیکٹ کی ساخت کو سمجھیں۔

user_profile_api/
  routes/
    index.dart              ← GET /
  pubspec.yaml
  analysis_options.yaml

یہ مکمل ابتدائی ڈھانچہ ہے۔ صاف اور کم سے کم۔ ہم جو کچھ بھی شامل کریں گے وہ یہاں سے پھیل جائے گا۔

API کی تعمیر کے بعد، مجموعی ڈھانچہ مندرجہ ذیل ہے:

user_profile_api/
  routes/
    _middleware.dart         ← global middleware pipeline
    index.dart               ← GET /
    auth/
      login.dart             ← POST /auth/login
      register.dart          ← POST /auth/register
    users/
      index.dart             ← GET /users
      [id].dart              ← GET, PUT, DELETE /users/:id
      [id]/
        profile.dart         ← GET, POST, PUT /users/:id/profile
  lib/
    config/
      database.dart
      env.dart
    models/
      user.dart
      profile.dart
    repositories/
      user_repository.dart
      profile_repository.dart
    services/
      auth_service.dart
    middleware/
      auth_middleware.dart
      error_middleware.dart
  pubspec.yaml

روٹس/ فولڈر ڈارٹ فراگ پروجیکٹ کا دل ہے۔ lib/فولڈر روٹنگ درآمد کے لیے تمام مشترکہ منطق پر مشتمل ہے۔ یہ علیحدگی واضح اور جان بوجھ کر ہے۔ روٹنگ کے مسائل روٹس/ میں ہیں اور کاروباری منطق lib/ میں ہے۔

ڈارٹ فراگ کلیدی تصورات

فائل پر مبنی روٹنگ

روٹس/ ڈائرکٹری میں تمام .dart فائلیں روٹس ہیں۔ فائل کا راستہ URL کے راستے کا تعین کرتا ہے۔

فائل URL
path/index.dart /
path/users/index.dart /صارف
راستہ/صارف/[id].dart /user/:ID
path/auth/login.dart /توثیق/لاگ ان
راستہ/صارف/[id]/profile.dart /user/:ID/profile

ہر روٹ فائل کو ایک onRequest فنکشن ایکسپورٹ کرنا چاہیے۔

import 'package:dart_frog/dart_frog.dart';

Future onRequest(RequestContext context) async {
  return Response.json(body: {'message': 'Hello from Dart Frog'});
}

یہ ساری ڈیل ہے۔ ایک فنکشن، ایک فائل، ایک راستہ۔ جب آپ ڈارٹ_فروگ دیو یا ڈارٹ_فروگ بلڈ چلاتے ہیں تو ڈارٹ فراگ خود بخود اندرونی روٹنگ گلو بناتا ہے۔

RequestContext

RequestContext ایک ایسی چیز ہے جو تمام روٹ ہینڈلرز اور مڈل ویئر کو بھیجی جاتی ہے۔ یہ صرف ایک HTTP درخواست سے زیادہ ہے۔ درخواستوں اور مڈل ویئر کے ذریعہ داخل کردہ تمام اقدار کے لئے ایک کنٹینر۔

Future onRequest(RequestContext context) async {
  // The raw HTTP request
  final request = context.request;

  // HTTP method
  print(request.method); // GET, POST, etc.

  // Path parameters (for dynamic routes like [id].dart)
  final id = context.request.uri.pathSegments.last;

  // Query parameters
  final page = request.uri.queryParameters['page'];

  // Request body
  final body = await request.json() as Map;

  // Values injected by middleware
  final db = context.read();
  final currentUser = context.read();

  return Response.json(body: {'ok': true});
}

context.read() انحصار انجیکشن میکانزم ہے۔ مڈل ویئر اقدار فراہم کرتا ہے اور راستے انہیں استعمال کرتے ہیں۔ یہ راستہ صاف اور قابل آزمائش رکھتا ہے۔ روٹ ہینڈلر نہیں جانتا کہ ڈیٹا بیس کنکشن کیسے بنایا گیا اور اسے سیاق و سباق سے پڑھتا ہے۔

مڈل ویئر اور انحصار انجیکشن

کوئی راستہ نہیں _middleware.dart آل پاتھز فولڈر میں موجود فائلیں اس فولڈر اور اس کے سب فولڈرز کے تمام راستوں پر مڈل ویئر کا اطلاق کریں گی۔ کوئی راستہ نہیں _middleware.dart روٹ پاتھ/سطح پر یہ عالمی سطح پر لاگو ہوتا ہے۔

ڈارٹ فراگ کا مڈل ویئر سیاق و سباق میں اقدار کو انجیکشن کرنے کے لیے فراہم کنندہ پیٹرن کا استعمال کرتا ہے۔

import 'package:dart_frog/dart_frog.dart';

Handler middleware(Handler handler) {
  return handler.use(
    provider(
      (context) => DatabaseConnection.instance,
    ),
  );
}

ایک ہی فولڈر یا سب فولڈر میں موجود کسی بھی راستے کو context.read() پر کال کرکے جوڑا جا سکتا ہے۔ کوئی عالمی سنگلٹن نہیں ہے اور کوئی دستی پاسنگ نہیں ہے۔ سیاق و سباق اس کو پہنچاتا ہے۔

مڈل ویئر کے فنکشنز تصدیق کے لیے موزوں ہیں کیونکہ وہ روٹ ہینڈلر تک پہنچنے سے پہلے درخواستوں کو روک سکتے ہیں۔

Handler middleware(Handler handler) {
  return (context) async {
    final authHeader = context.request.headers['authorization'];

    if (authHeader == null) {
      return Response.json(
        statusCode: 401,
        body: {'error': 'Authorization required'},
      );
    }

    // Verify token and inject user
    final user = verifyToken(authHeader);
    return handler(context.provide(() => user));
  };
}

متحرک راستہ

نام کی فائل [id].dart سنگل پاتھ سیگمنٹ سے میل کھاتا ہے۔ ہینڈلر کے اندر، URL سے پیرامیٹرز نکالیں۔

Future onRequest(RequestContext context, String id) async {
  // id is automatically passed as a parameter for dynamic routes
  return Response.json(body: {'userId': id});
}

Dart Frog متحرک روٹ پیرامیٹرز کو onRequest کے اضافی دلائل کے طور پر پاس کرتا ہے۔ یہ یو آر ایل سے دستی طور پر تجزیہ کرنے سے زیادہ صاف ہے۔

ڈیٹا بیس کی ترتیبات

پوسٹگری ایس کیو ایل کے لیے ڈاکر کمپوز

پروجیکٹ روٹ میں docker-compose.yml بنائیں۔

version: '3.8'

services:
  postgres:
    image: postgres:16-alpine
    container_name: user_profile_db
    environment:
      POSTGRES_DB: user_profile_api
      POSTGRES_USER: dart_user
      POSTGRES_PASSWORD: dart_password
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dart_user -d user_profile_api"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:

ڈیٹا بیس شروع کریں۔

docker compose up -d

ماحولیات کی ترتیب

pubspec.yaml میں انحصار شامل کریں۔

dependencies:
  dart_frog: ^1.4.0
  dart_frog_auth: ^0.1.0
  postgres: ^3.3.0
  dart_jsonwebtoken: ^2.12.0
  bcrypt: ^1.1.3
  dotenv: ^4.1.0

dev_dependencies:
  dart_frog_cli: ^1.2.0
  test: ^1.24.0
  dart_frog_test: ^0.1.0

چلائیں ڈارٹ پب حاصل کریں۔

env بنائیں:

DB_HOST=localhost
DB_PORT=5432
DB_NAME=user_profile_api
DB_USER=dart_user
DB_PASSWORD=dart_password
JWT_SECRET=your_super_secret_key_change_this_in_production
JWT_EXPIRY_HOURS=24
PORT=8080

lib/config/env.dart بنائیں:

import 'package:dotenv/dotenv.dart';

class Env {
  static late final DotEnv _env;

  static void load() {
    _env = DotEnv(includePlatformEnvironment: true)..load();
  }

  static String get dbHost => _env['DB_HOST'] ?? 'localhost';
  static int get dbPort => int.parse(_env['DB_PORT'] ?? '5432');
  static String get dbName => _env['DB_NAME'] ?? 'user_profile_api';
  static String get dbUser => _env['DB_USER'] ?? 'dart_user';
  static String get dbPassword => _env['DB_PASSWORD'] ?? '';
  static String get jwtSecret => _env['JWT_SECRET'] ?? '';
  static int get jwtExpiryHours =>
      int.parse(_env['JWT_EXPIRY_HOURS'] ?? '24');
}

ڈیٹا بیس کنکشن مینیجر

lib/config/database.dart بنائیں:

import 'package:postgres/postgres.dart';
import 'env.dart';

class Database {
  static Connection? _connection;

  static Future get connection async {
    if (_connection != null) return _connection!;
    _connection = await Connection.open(
      Endpoint(
        host: Env.dbHost,
        port: Env.dbPort,
        database: Env.dbName,
        username: Env.dbUser,
        password: Env.dbPassword,
      ),
      settings: const ConnectionSettings(sslMode: SslMode.disable),
    );
    print('Database connected');
    return _connection!;
  }

  static Future runMigrations() async {
    final conn = await connection;
    await conn.execute('''
      CREATE TABLE IF NOT EXISTS users (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        email VARCHAR(255) UNIQUE NOT NULL,
        password_hash VARCHAR(255) NOT NULL,
        first_name VARCHAR(100) NOT NULL,
        last_name VARCHAR(100) NOT NULL,
        is_active BOOLEAN DEFAULT TRUE,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
        updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);

      CREATE TABLE IF NOT EXISTS profiles (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
        bio TEXT,
        avatar_url VARCHAR(500),
        phone VARCHAR(20),
        location VARCHAR(255),
        website VARCHAR(500),
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
        updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
        UNIQUE(user_id)
      );

      CREATE INDEX IF NOT EXISTS idx_profiles_user_id ON profiles(user_id);
    ''');
    print('Migrations applied');
  }
}

نقل مکانی

ڈارٹ فروگ پروجیکٹ میں ایک مین ڈارٹ انٹری پوائنٹ ہے جو ڈارٹ_فروگ کی تعمیر کے دوران بنایا گیا ہے۔ ترقیاتی سرورز کے لیے، پراجیکٹ کے داخلے کے مقام پر منتقلی بہترین طریقے سے چلائی جاتی ہے۔ پروجیکٹ روٹ میں main.dart بنائیں۔

import 'dart:io';
import 'package:dart_frog/dart_frog.dart';
import 'lib/config/database.dart';
import 'lib/config/env.dart';

Future run(Handler handler, InternetAddress ip, int port) async {
  Env.load();
  await Database.runMigrations();
  return serve(handler, ip, port);
}

یہ ایگزیکیوشن فنکشن ڈارٹ فراگ کا سرور لائف سائیکل ہک ہے۔ یہ سرور کی جانب سے درخواستوں کو قبول کرنے، ماحولیاتی متغیرات کو لوڈ کرنے اور منتقلی کو چلانے کے لیے صحیح جگہ فراہم کرنے سے پہلے چلتا ہے۔

ماڈل کی تعریف

ڈیٹا بیس کی تہہ تیار ہونے کے بعد، ہمیں آنے والے اور جانے والے ڈیٹا کی نمائندگی کرنے کے لیے ڈارٹ کلاسز کی ضرورت ہوتی ہے۔

یوزر ماڈل یوزر ٹیبل پر نقشہ بناتا ہے اور ڈیٹا بیس کی قطاروں اور ڈارٹ آبجیکٹ کے درمیان تبادلوں کو سنبھالتا ہے۔ پروفائل ماڈل پروفائل ٹیبل کے لئے ایک ہی کام کرتا ہے۔ دونوں ماڈل ایک ہی طرز کی پیروی کرتے ہیں۔ یعنی ڈیٹا بیس سے پڑھنے کے لیے ایک فیکٹری کنسٹرکٹر اور toJson کلائنٹ کو ڈیٹا واپس کیسے بھیجیں۔

براہ کرم اس کا حوالہ دیں۔ toJson صارف ماڈل جان بوجھ کر پاس ورڈ ہیش کو خارج کرتا ہے۔ API کے جوابات میں اسنادی ڈیٹا واپس نہیں کیا جانا چاہیے۔

lib/models/user.dart بنائیں:

class User {
  const User({
    required this.id,
    required this.email,
    required this.passwordHash,
    required this.firstName,
    required this.lastName,
    required this.isActive,
    required this.createdAt,
    required this.updatedAt,
  });

  final String id;
  final String email;
  final String passwordHash;
  final String firstName;
  final String lastName;
  final bool isActive;
  final DateTime createdAt;
  final DateTime updatedAt;

  factory User.fromRow(Map row) => User(
        id: row['id'] as String,
        email: row['email'] as String,
        passwordHash: row['password_hash'] as String,
        firstName: row['first_name'] as String,
        lastName: row['last_name'] as String,
        isActive: row['is_active'] as bool,
        createdAt: row['created_at'] as DateTime,
        updatedAt: row['updated_at'] as DateTime,
      );

  Map toJson() => {
        'id': id,
        'email': email,
        'firstName': firstName,
        'lastName': lastName,
        'isActive': isActive,
        'createdAt': createdAt.toIso8601String(),
        'updatedAt': updatedAt.toIso8601String(),
      };
}

lib/models/profile.dart بنائیں:

class Profile {
  const Profile({
    required this.id,
    required this.userId,
    this.bio,
    this.avatarUrl,
    this.phone,
    this.location,
    this.website,
    required this.createdAt,
    required this.updatedAt,
  });

  final String id;
  final String userId;
  final String? bio;
  final String? avatarUrl;
  final String? phone;
  final String? location;
  final String? website;
  final DateTime createdAt;
  final DateTime updatedAt;

  factory Profile.fromRow(Map row) => Profile(
        id: row['id'] as String,
        userId: row['user_id'] as String,
        bio: row['bio'] as String?,
        avatarUrl: row['avatar_url'] as String?,
        phone: row['phone'] as String?,
        location: row['location'] as String?,
        website: row['website'] as String?,
        createdAt: row['created_at'] as DateTime,
        updatedAt: row['updated_at'] as DateTime,
      );

  Map toJson() => {
        'id': id,
        'userId': userId,
        'bio': bio,
        'avatarUrl': avatarUrl,
        'phone': phone,
        'location': location,
        'website': website,
        'createdAt': createdAt.toIso8601String(),
        'updatedAt': updatedAt.toIso8601String(),
      };
}

مخزن کی تعمیر

ذخیرہ آپ کی درخواست اور ڈیٹا بیس کے درمیان رابطے کا واحد نقطہ ہے۔ روٹ ہینڈلر کے اندر ایس کیو ایل کو براہ راست لکھنے کے بجائے، ڈیٹا بیس کے تمام آپریشنز یہاں مرکزی ہیں۔ یہ آپ کے ہینڈلرز کو صاف رکھتا ہے اور آپ کے ڈیٹا تک رسائی کی منطق کو آزادانہ طور پر تلاش کرنے، برقرار رکھنے اور جانچنے میں آسان بناتا ہے۔

UserRepository صارف کی میز پر تمام کارروائیوں کو سنبھالتی ہے۔ ProfileRepository پروفائلز کے لیے بھی ایسا ہی کرتی ہے، userId کو بنیادی تلاش کی کلید کے طور پر استعمال کرتی ہے۔ اس کی وجہ یہ ہے کہ پروفائلز تک ہمیشہ کسی مخصوص صارف کے تناظر میں رسائی حاصل کی جاتی ہے۔

صارف ذخیرہ

lib/repositories/user_repository.dart بنائیں:

import 'package:postgres/postgres.dart';
import '../config/database.dart';
import '../models/user.dart';

class UserRepository {
  Future get _conn => Database.connection;

  Future> findAll() async {
    final conn = await _conn;
    final results = await conn.execute(
      'SELECT * FROM users WHERE is_active = TRUE ORDER BY created_at DESC',
    );
    return results.map((r) => User.fromRow(r.toColumnMap())).toList();
  }

  Future findById(String id) async {
    final conn = await _conn;
    final results = await conn.execute(
      Sql.named('SELECT * FROM users WHERE id = @id AND is_active = TRUE'),
      parameters: {'id': id},
    );
    if (results.isEmpty) return null;
    return User.fromRow(results.first.toColumnMap());
  }

  Future findByEmail(String email) async {
    final conn = await _conn;
    final results = await conn.execute(
      Sql.named('SELECT * FROM users WHERE email = @email'),
      parameters: {'email': email},
    );
    if (results.isEmpty) return null;
    return User.fromRow(results.first.toColumnMap());
  }

  Future create({
    required String email,
    required String passwordHash,
    required String firstName,
    required String lastName,
  }) async {
    final conn = await _conn;
    final results = await conn.execute(
      Sql.named('''
        INSERT INTO users (email, password_hash, first_name, last_name)
        VALUES (@email, @passwordHash, @firstName, @lastName)
        RETURNING *
      '''),
      parameters: {
        'email': email,
        'passwordHash': passwordHash,
        'firstName': firstName,
        'lastName': lastName,
      },
    );
    return User.fromRow(results.first.toColumnMap());
  }

  Future update({
    required String id,
    String? firstName,
    String? lastName,
  }) async {
    final conn = await _conn;
    final results = await conn.execute(
      Sql.named('''
        UPDATE users
        SET
          first_name = COALESCE(@firstName, first_name),
          last_name  = COALESCE(@lastName, last_name),
          updated_at = NOW()
        WHERE id = @id AND is_active = TRUE
        RETURNING *
      '''),
      parameters: {'id': id, 'firstName': firstName, 'lastName': lastName},
    );
    if (results.isEmpty) return null;
    return User.fromRow(results.first.toColumnMap());
  }

  Future delete(String id) async {
    final conn = await _conn;
    final results = await conn.execute(
      Sql.named('''
        UPDATE users SET is_active = FALSE, updated_at = NOW()
        WHERE id = @id AND is_active = TRUE
        RETURNING id
      '''),
      parameters: {'id': id},
    );
    return results.isNotEmpty;
  }
}

پروفائل اسٹور

lib/repositories/profile_repository.dart بنائیں:

import 'package:postgres/postgres.dart';
import '../config/database.dart';
import '../models/profile.dart';

class ProfileRepository {
  Future get _conn => Database.connection;

  Future findByUserId(String userId) async {
    final conn = await _conn;
    final results = await conn.execute(
      Sql.named('SELECT * FROM profiles WHERE user_id = @userId'),
      parameters: {'userId': userId},
    );
    if (results.isEmpty) return null;
    return Profile.fromRow(results.first.toColumnMap());
  }

  Future create({
    required String userId,
    String? bio,
    String? avatarUrl,
    String? phone,
    String? location,
    String? website,
  }) async {
    final conn = await _conn;
    final results = await conn.execute(
      Sql.named('''
        INSERT INTO profiles (user_id, bio, avatar_url, phone, location, website)
        VALUES (@userId, @bio, @avatarUrl, @phone, @location, @website)
        RETURNING *
      '''),
      parameters: {
        'userId': userId,
        'bio': bio,
        'avatarUrl': avatarUrl,
        'phone': phone,
        'location': location,
        'website': website,
      },
    );
    return Profile.fromRow(results.first.toColumnMap());
  }

  Future update({
    required String userId,
    String? bio,
    String? avatarUrl,
    String? phone,
    String? location,
    String? website,
  }) async {
    final conn = await _conn;
    final results = await conn.execute(
      Sql.named('''
        UPDATE profiles
        SET
          bio        = COALESCE(@bio, bio),
          avatar_url = COALESCE(@avatarUrl, avatar_url),
          phone      = COALESCE(@phone, phone),
          location   = COALESCE(@location, location),
          website    = COALESCE(@website, website),
          updated_at = NOW()
        WHERE user_id = @userId
        RETURNING *
      '''),
      parameters: {
        'userId': userId,
        'bio': bio,
        'avatarUrl': avatarUrl,
        'phone': phone,
        'location': location,
        'website': website,
      },
    );
    if (results.isEmpty) return null;
    return Profile.fromRow(results.first.toColumnMap());
  }
}

تصدیق کی خدمت

اس پروجیکٹ میں توثیق کا انتظام lib/services/ میں ایک سرشار AuthService کے ذریعے کیا جاتا ہے۔ یہاں ایک واضح ذمہ داری ہے۔ یعنی، کرپٹوگرافک آپریشنز جو تصدیق کو مضبوط بناتے ہیں: پاس ورڈ کو اسٹور کرنے سے پہلے ہیش کرنا، لاگ ان پر پاس ورڈ کی تصدیق کرنا، کامیابی پر دستخط شدہ JWT ٹوکن بنانا، اور محفوظ شدہ درخواست میں اس ٹوکن کی تصدیق کرنا۔

اس منطق کو روٹ ہینڈلرز میں تقسیم کرنے کے بجائے اپنی خدمت میں رکھنے کا مطلب ہے کہ آپ اسے مڈل ویئر کے ذریعے انجیکشن لگا سکتے ہیں اور اسے اپنی ایپ میں کہیں بھی صاف ستھرا استعمال کر سکتے ہیں۔

lib/services/auth_service.dart بنائیں:

import 'package:bcrypt/bcrypt.dart';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import '../config/env.dart';
import '../models/user.dart';

class AuthService {
  String hashPassword(String password) =>
      BCrypt.hashpw(password, BCrypt.gensalt());

  bool verifyPassword(String password, String hash) =>
      BCrypt.checkpw(password, hash);

  String generateToken(User user) {
    final jwt = JWT({
      'sub': user.id,
      'email': user.email,
      'iat': DateTime.now().millisecondsSinceEpoch ~/ 1000,
    });
    return jwt.sign(
      SecretKey(Env.jwtSecret),
      expiresIn: Duration(hours: Env.jwtExpiryHours),
    );
  }

  JWT? verifyToken(String token) {
    try {
      return JWT.verify(token, SecretKey(Env.jwtSecret));
    } catch (_) {
      return null;
    }
  }
}

مڈل ویئر

مڈل ویئر وہ جگہ ہے جہاں ڈارٹ فراگ کا انحصار انجیکشن ماڈل اپنا سب سے اہم کام کرتا ہے۔ ہر روٹ ہینڈلر کے اندر ریپوزٹریز اور خدمات کو فوری بنانے کے بجائے، آپ انہیں مڈل ویئر میں ایک بار تخلیق کرتے ہیں اور RequestContext کے ذریعے تمام ہینڈلرز کو نیچے کی طرف دستیاب کرتے ہیں۔

یہ سیکشن تین مڈل ویئرز کی وضاحت کرتا ہے: ایک ڈیٹا بیس مڈل ویئر جو ریپوزٹری اور توثیق کی خدمات کو انجیکشن کرتا ہے، ایک تصدیقی مڈل ویئر جو JWT ٹوکن کی توثیق کرتا ہے اور راستے کو محفوظ کرتا ہے، اور ایک ایرر مڈل ویئر جو غیر ہینڈل استثناء کو پکڑتا ہے اور پورے API میں ایک مستقل غلطی کا جواب دیتا ہے۔

ڈیٹا بیس مڈل ویئر

lib/middleware/database_middleware.dart بنائیں:

import 'package:dart_frog/dart_frog.dart';
import '../repositories/user_repository.dart';
import '../repositories/profile_repository.dart';
import '../services/auth_service.dart';

Middleware databaseMiddleware() {
  return (handler) {
    return handler
        .use(provider((_) => UserRepository()))
        .use(provider((_) => ProfileRepository()))
        .use(provider((_) => AuthService()));
  };
}

یہ مڈل ویئر ہر درخواست کے سیاق و سباق میں اسٹوریج اور توثیق کی خدمات کو انجیکشن کرتا ہے۔ راستہ اس طرح پڑھتا ہے: context.read() مجھے پرواہ نہیں ہے کہ یہ کیسے بنایا گیا تھا.

توثیق مڈل ویئر

lib/middleware/auth_middleware.dart بنائیں:

import 'dart:convert';
import 'package:dart_frog/dart_frog.dart';
import '../services/auth_service.dart';

Middleware authMiddleware() {
  return (handler) {
    return (context) async {
      final authHeader = context.request.headers['authorization'];

      if (authHeader == null || !authHeader.startsWith('Bearer ')) {
        return Response.json(
          statusCode: 401,
          body: {'error': 'Authorization header missing or malformed'},
        );
      }

      final token = authHeader.substring(7);
      final authService = context.read();
      final jwt = authService.verifyToken(token);

      if (jwt == null) {
        return Response.json(
          statusCode: 401,
          body: {'error': 'Invalid or expired token'},
        );
      }

      final userId = jwt.payload['sub'] as String;
      final userEmail = jwt.payload['email'] as String;

      return handler(
        context.provide>(
          () => {'userId': userId, 'userEmail': userEmail},
        ),
      );
    };
  };
}

خرابی مڈل ویئر

lib/middleware/error_middleware.dart بنائیں:

import 'package:dart_frog/dart_frog.dart';

Middleware errorMiddleware() {
  return (handler) {
    return (context) async {
      try {
        return await handler(context);
      } on FormatException catch (e) {
        return Response.json(
          statusCode: 400,
          body: {'error': 'Invalid request body: ${e.message}'},
        );
      } catch (e, stackTrace) {
        print('Unhandled error: \(e\n\)stackTrace');
        return Response.json(
          statusCode: 500,
          body: {'error': 'An internal server error occurred'},
        );
      }
    };
  };
}

راستے کی تعمیر

اب جب کہ ہمارے پاس اپنا ماڈل، ریپوزٹری، توثیق کی خدمت، اور مڈل ویئر سبھی تیار ہیں، ہم اپنا روٹ ہینڈلر بنا سکتے ہیں۔

ڈارٹ فراگ میں، روٹس/ فولڈر میں ہر فائل ایک خود ساختہ اختتامی نقطہ ہے۔ راستے براہ راست انحصار کا انتظام نہیں کرتے ہیں۔ اس کے بجائے، مڈل ویئر اسے پڑھتا ہے جو پہلے ہی سیاق و سباق میں انجیکشن کیا گیا ہے اور مناسب اسٹور یا سروس کے طریقہ کار کو کال کرتا ہے۔

یہ سیکشن راستوں کے تین گروہوں کا احاطہ کرتا ہے: رجسٹریشن اور لاگ ان کے لیے توثیق کے راستے، CRUD آپریشنز کے لیے صارف کے راستے، اور پروفائل کے راستے جو صارف ID کے نیچے رکھے گئے ہیں۔

سرٹیفیکیشن کا راستہ

path/auth/register.dart بنائیں:

import 'package:dart_frog/dart_frog.dart';
import '../../lib/repositories/user_repository.dart';
import '../../lib/services/auth_service.dart';

Future onRequest(RequestContext context) async {
  if (context.request.method != HttpMethod.post) {
    return Response.json(statusCode: 405, body: {'error': 'Method not allowed'});
  }

  final body = await context.request.json() as Map;
  final email = body['email'] as String?;
  final password = body['password'] as String?;
  final firstName = body['firstName'] as String?;
  final lastName = body['lastName'] as String?;

  if (email == null || password == null ||
      firstName == null || lastName == null) {
    return Response.json(
      statusCode: 400,
      body: {'error': 'email, password, firstName, and lastName are required'},
    );
  }

  if (password.length < 8) {
    return Response.json(
      statusCode: 400,
      body: {'error': 'Password must be at least 8 characters'},
    );
  }

  final userRepo = context.read();
  final authService = context.read();

  final existing = await userRepo.findByEmail(email);
  if (existing != null) {
    return Response.json(
      statusCode: 409,
      body: {'error': 'An account with this email already exists'},
    );
  }

  final user = await userRepo.create(
    email: email,
    passwordHash: authService.hashPassword(password),
    firstName: firstName,
    lastName: lastName,
  );

  return Response.json(
    statusCode: 201,
    body: {
      'user': user.toJson(),
      'token': authService.generateToken(user),
    },
  );
}

path/auth/login.dart بنائیں:

import 'package:dart_frog/dart_frog.dart';
import '../../lib/repositories/user_repository.dart';
import '../../lib/services/auth_service.dart';

Future onRequest(RequestContext context) async {
  if (context.request.method != HttpMethod.post) {
    return Response.json(statusCode: 405, body: {'error': 'Method not allowed'});
  }

  final body = await context.request.json() as Map;
  final email = body['email'] as String?;
  final password = body['password'] as String?;

  if (email == null || password == null) {
    return Response.json(
      statusCode: 400,
      body: {'error': 'email and password are required'},
    );
  }

  final userRepo = context.read();
  final authService = context.read();
  final user = await userRepo.findByEmail(email);

  if (user == null || !authService.verifyPassword(password, user.passwordHash)) {
    return Response.json(
      statusCode: 401,
      body: {'error': 'Invalid email or password'},
    );
  }

  return Response.json(
    body: {
      'user': user.toJson(),
      'token': authService.generateToken(user),
    },
  );
}

صارف کا راستہ

path/users/index.dart بنائیں:

import 'package:dart_frog/dart_frog.dart';
import '../../lib/repositories/user_repository.dart';

Future onRequest(RequestContext context) async {
  if (context.request.method != HttpMethod.get) {
    return Response.json(statusCode: 405, body: {'error': 'Method not allowed'});
  }

  final userRepo = context.read();
  final users = await userRepo.findAll();

  return Response.json(
    body: users.map((u) => u.toJson()).toList(),
  );
}

راستہ/صارف/تخلیق[id].dart:

import 'package:dart_frog/dart_frog.dart';
import '../../lib/repositories/user_repository.dart';

Future onRequest(RequestContext context, String id) async {
  final userRepo = context.read();

  switch (context.request.method) {
    case HttpMethod.get:
      return _getUser(userRepo, id);
    case HttpMethod.put:
      return _updateUser(context, userRepo, id);
    case HttpMethod.delete:
      return _deleteUser(userRepo, id);
    default:
      return Response.json(
        statusCode: 405,
        body: {'error': 'Method not allowed'},
      );
  }
}

Future _getUser(UserRepository repo, String id) async {
  final user = await repo.findById(id);
  if (user == null) {
    return Response.json(statusCode: 404, body: {'error': 'User not found'});
  }
  return Response.json(body: user.toJson());
}

Future _updateUser(
  RequestContext context,
  UserRepository repo,
  String id,
) async {
  final body = await context.request.json() as Map;
  final user = await repo.update(
    id: id,
    firstName: body['firstName'] as String?,
    lastName: body['lastName'] as String?,
  );
  if (user == null) {
    return Response.json(statusCode: 404, body: {'error': 'User not found'});
  }
  return Response.json(body: user.toJson());
}

Future _deleteUser(UserRepository repo, String id) async {
  final deleted = await repo.delete(id);
  if (!deleted) {
    return Response.json(statusCode: 404, body: {'error': 'User not found'});
  }
  return Response.json(statusCode: 204, body: null);
}

دیکھیں کہ onRequest اپنے دوسرے پیرامیٹر کے طور پر اسٹرنگ آئی ڈی کو کیسے لیتا ہے۔ ڈارٹ فراگ خود بخود ہینڈلر کو متحرک راستے کے حصوں کو منتقل کرتا ہے۔ context.request.method میں سوئچز تمام HTTP طریقوں کو ایک فائل میں ہینڈل کرتے ہیں، CRUD اینڈ پوائنٹس کے لیے ایک محاوراتی ڈارٹ فراگ پیٹرن۔

پروفائل کا راستہ

راستہ/صارف/تخلیق[id]/profile.dart:

import 'package:dart_frog/dart_frog.dart';
import '../../../lib/repositories/user_repository.dart';
import '../../../lib/repositories/profile_repository.dart';

Future onRequest(RequestContext context, String id) async {
  final userRepo = context.read();
  final profileRepo = context.read();

  final user = await userRepo.findById(id);
  if (user == null) {
    return Response.json(statusCode: 404, body: {'error': 'User not found'});
  }

  switch (context.request.method) {
    case HttpMethod.get:
      return _getProfile(profileRepo, id);
    case HttpMethod.post:
      return _createProfile(context, profileRepo, id);
    case HttpMethod.put:
      return _updateProfile(context, profileRepo, id);
    default:
      return Response.json(
        statusCode: 405,
        body: {'error': 'Method not allowed'},
      );
  }
}

Future _getProfile(ProfileRepository repo, String userId) async {
  final profile = await repo.findByUserId(userId);
  if (profile == null) {
    return Response.json(statusCode: 404, body: {'error': 'Profile not found'});
  }
  return Response.json(body: profile.toJson());
}

Future _createProfile(
  RequestContext context,
  ProfileRepository repo,
  String userId,
) async {
  final existing = await repo.findByUserId(userId);
  if (existing != null) {
    return Response.json(
      statusCode: 409,
      body: {'error': 'Profile already exists for this user'},
    );
  }

  final body = await context.request.json() as Map;
  final profile = await repo.create(
    userId: userId,
    bio: body['bio'] as String?,
    avatarUrl: body['avatarUrl'] as String?,
    phone: body['phone'] as String?,
    location: body['location'] as String?,
    website: body['website'] as String?,
  );
  return Response.json(statusCode: 201, body: profile.toJson());
}

Future _updateProfile(
  RequestContext context,
  ProfileRepository repo,
  String userId,
) async {
  final body = await context.request.json() as Map;
  final profile = await repo.update(
    userId: userId,
    bio: body['bio'] as String?,
    avatarUrl: body['avatarUrl'] as String?,
    phone: body['phone'] as String?,
    location: body['location'] as String?,
    website: body['website'] as String?,
  );
  if (profile == null) {
    return Response.json(statusCode: 404, body: {'error': 'Profile not found'});
  }
  return Response.json(body: profile.toJson());
}

مڈل ویئر پائپ لائن کنکشن

روٹ اور مڈل ویئر دونوں بنائے گئے ہیں، لیکن ابھی تک جڑے نہیں ہیں۔ ڈارٹ فراگ میں، کنکشن اس کے ذریعے بنائے جاتے ہیں: _middleware.dart یہ وہ فائلیں ہیں جو حکمت عملی کے ساتھ پاتھ/فولڈرز میں رکھی گئی ہیں۔

جائزہ لینے کے لیے: _middleware.dart روٹ لیول پر فائلیں پروجیکٹ کے تمام راستوں پر لاگو ہوتی ہیں۔ کوئی راستہ نہیں _middleware.dart ذیلی فولڈرز کے اندر صرف اس فولڈر کے نیچے والے راستوں پر لاگو ہوتا ہے۔ یہ آپ کو فولڈر کے دائرہ کار پر ٹھیک ٹھیک کنٹرول کرنے کی اجازت دیتا ہے، جہاں مڈل ویئر دستی رجسٹریشن یا بڑھتے ہوئے بغیر چلتا ہے۔

بنانا routes/_middleware.dart عالمی مڈل ویئر جو تمام راستوں پر لاگو ہوتا ہے:

import 'package:dart_frog/dart_frog.dart';
import '../lib/middleware/database_middleware.dart';
import '../lib/middleware/error_middleware.dart';

Handler middleware(Handler handler) {
  return handler
      .use(databaseMiddleware())
      .use(errorMiddleware());
}

بنانا routes/users/_middleware.dart تصدیق کے ساتھ صارف کے تمام راستوں کو محفوظ بنانے کے لیے:

import 'package:dart_frog/dart_frog.dart';
import '../../lib/middleware/auth_middleware.dart';

Handler middleware(Handler handler) {
  return handler.use(authMiddleware());
}

یہ ڈارٹ فراگ ماڈل کے سب سے خوبصورت حصوں میں سے ایک ہے۔ path/users/_middleware.dart فائل خود بخود path/users/ کے تحت تمام راستوں پر تصدیق کا اطلاق کرتی ہے، بشمول path/users/index.dart اور path/users/۔[id]ڈارٹ اور پاتھ/صارفین/[id]/profile.dart. روٹس/auth/ کے تحت توثیق کے راستے صارفین/ فولڈر سے باہر ہیں اور اس لیے غیر متاثر ہیں۔

کوئی دستی مڈل ویئر ماؤنٹنگ، محفوظ راستے کا انتظام، اور پاتھ گروپ کنفیگریشن نہیں ہے۔ فولڈر کا ڈھانچہ کام کرتا ہے۔

API ٹیسٹنگ

سرور کے چلنے اور تمام راستے منسلک ہونے کے ساتھ، آپ شروع سے آخر تک پورے بہاؤ کو دیکھ سکتے ہیں۔ ڈویلپمنٹ سرور شروع کریں اور ہر اختتامی نقطہ کو ترتیب سے چلائیں۔ آپ ٹوکن حاصل کرنے کے لیے پہلے صارف کو رجسٹر کرتے ہیں، اور پھر اس ٹوکن کو محفوظ راستے پر استعمال کرتے ہیں۔ نیچے دی گئی کمانڈ میں {userId} کو رجسٹریشن کے جواب میں واپس کی گئی اصل ID سے بدل دیں۔

ڈویلپمنٹ سرور شروع کریں۔

dart_frog dev
# Server is now running at: http://localhost:8080

صارف کی رجسٹریشن:

curl http://localhost:8080/auth/register \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "email": "seyi@example.com",
    "password": "securepassword",
    "firstName": "Seyi",
    "lastName": "Dev"
  }'

جواب:

{
  "user": {
    "id": "uuid-here",
    "email": "seyi@example.com",
    "firstName": "Seyi",
    "lastName": "Dev",
    "isActive": true,
    "createdAt": "2025-01-01T00:00:00.000Z",
    "updatedAt": "2025-01-01T00:00:00.000Z"
  },
  "token": "eyJhbGci..."
}

لاگ ان:

curl http://localhost:8080/auth/login \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"email": "seyi@example.com", "password": "securepassword"}'

تمام صارفین حاصل کریں:

curl http://localhost:8080/users \
  -H "Authorization: Bearer eyJhbGci..."

مخصوص صارفین حاصل کریں:

curl http://localhost:8080/users/{userId} \
  -H "Authorization: Bearer eyJhbGci..."

اپنا پروفائل بنائیں:

curl http://localhost:8080/users/{userId}/profile \
  -X POST \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{
    "bio": "Flutter engineer turned backend developer",
    "location": "Lagos, Nigeria",
    "website": "https://example.com"
  }'

صارف کی تازہ کارییں:

curl http://localhost:8080/users/{userId} \
  -X PUT \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{"firstName": "Oluwaseyi"}'

صارف کو حذف کریں:

curl http://localhost:8080/users/{userId} \
  -X DELETE \
  -H "Authorization: Bearer eyJhbGci..."

تعیناتی

مقامی طور پر ہر چیز کی جانچ کرنے کے بعد، آخری مرحلہ API لائیو کو نافذ کرنا ہے۔ ڈارٹ فراگ یہ کام آسانی سے کرتا ہے۔ ایک واحد CLI کمانڈ ایک پروڈکشن کے لیے تیار ڈاکر فائل بناتی ہے اور اسے وہاں سے Fly.io پر تعینات کرتی ہے، جہاں آپ کی ایپ ایک نظم شدہ PostgreSQL ڈیٹا بیس کے ساتھ کنٹینرائزڈ سروس کے طور پر چلتی ہے۔

پیداوار کی تعمیر

ڈارٹ فراگ ایک ہی کمانڈ کے ساتھ پروڈکشن کے لیے تیار ڈوکر سیٹ اپ بناتا ہے۔

dart_frog build

یہ ایک بلڈ/ ڈائریکٹری بنائے گا جس پر مشتمل ہے:

build/
  bin/
    server.dart         ← compiled entry point
  Dockerfile            ← production Dockerfile
  pubspec.yaml
  pubspec.lock

تیار کردہ ڈاکر فائل ایک ملٹی اسٹیج بلڈ ہے، جہاں پہلا مرحلہ مقامی بائنریز میں مرتب ہوتا ہے اور دوسرا مرحلہ کم سے کم ڈیبین امیج پر چلتا ہے۔ آپ کو یہ خود لکھنے کی ضرورت نہیں ہے۔

Fly.io پر تعینات کریں۔

مرحلہ 1 – تصدیق:

fly auth login

مرحلہ 2 – بلڈ ڈائرکٹری سے شروع کریں۔

cd build
fly launch

فلائی آپ کی ڈاکر فائل کا پتہ لگائے گی اور آپ کو اسے کنفیگر کرنے کا اشارہ کرے گی۔ درخواست پر ایک PostgreSQL ڈیٹا بیس بناتا ہے۔

مرحلہ 3 – راز قائم کریں:

fly secrets set JWT_SECRET="your_production_jwt_secret"
fly secrets set JWT_EXPIRY_HOURS="24"

مرحلہ 4 – تعینات کریں:

fly deploy

مرحلہ 5 – تصدیق کریں:

curl https://your-app-name.fly.dev/auth/register \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","password":"password123","firstName":"Seyi","lastName":"Dev"}'

نتیجہ

Dart Frog شیلف کے خام کنٹرول اور Serverpod کے مکمل ان پٹ کے درمیان چوکور بیٹھا ہے۔ یہ JavaScript ایکو سسٹم میں ثابت شدہ فائل پر مبنی روٹنگ ماڈل کا استعمال کرتا ہے اور زبان کی طاقت سے سمجھوتہ کیے بغیر اسے صاف ستھرا انداز میں ڈارٹ میں لاتا ہے۔

روٹنگ ماڈل اس کی سب سے طاقتور خصوصیت ہے۔ پاتھ/فولڈر کو دیکھنے سے آپ کو API کے بارے میں سب کچھ پتہ چل جائے گا، بشمول کون سے اینڈ پوائنٹس موجود ہیں، وہ کیسے گروپ کیے گئے ہیں، اور کون سا مڈل ویئر کس سیکشن پر لاگو ہوتا ہے۔ یہ شفافیت کوڈبیس کو نیویگیٹ کرنے میں آسان، آن بورڈنگ میں آسان، اور کوڈبیس کے بڑھنے کے بارے میں سوچنے میں آسان بناتی ہے۔

انحصار انجیکشن کے لیے RequestContext اور فراہم کنندہ کے نمونے اچھی طرح سے سوچے گئے ہیں۔ مڈل ویئر کو انجکشن لگایا جاتا ہے، راستہ استعمال کیا جاتا ہے، اور دونوں کے درمیان کچھ نہیں بہتا ہے. فولڈر اسکوپڈ مڈل ویئر خاص طور پر صاف ستھرا ہے، اور آپ کے API کے پورے حصے کو محفوظ بنانا اتنا ہی آسان ہے جتنا کہ صحیح فولڈر میں _middleware.dart فائل رکھنا۔

فلٹر انجینئرز بنانے والے APIs کے لیے جن کو کلائنٹ کی متعدد اقسام کی خدمت کرنے، معیاری REST کنونشنز پر عمل پیرا ہونے، یا موجودہ فرنٹ اینڈ انفراسٹرکچر کے ساتھ صاف طور پر مربوط ہونے کی ضرورت ہوتی ہے، Dart Frog ایک ایسی عملی میٹھی جگہ پر پہنچتا ہے جہاں نہ تو شیلف اور نہ ہی Serverpod قدرتی طور پر پہنچ سکتے ہیں۔

ڈارٹ اب لفظ کے حقیقی معنوں میں ایک مکمل اسٹیک زبان ہے۔ Flutter ایپ سے لے کر اسے چلانے والے سرور تک، وہی ٹیم، وہی زبان، اور وہی اصول لاگو ہوتے ہیں۔

مبارک کوڈنگ!

اوپر تک سکرول کریں۔