How We Built a Multi-App Platform From a Single Codebase
5 min readMohammad Shaker

How We Built a Multi-App Platform From a Single Codebase

Alphazed runs 7+ apps (Amal, Thurayya, Qais, KidElite) from one backend codebase and shared Flutter framework, each with its own config and content.

Engineering

Quick Answer

Alphazed runs 7+ apps (Amal, Thurayya, Qais, KidElite) from one backend codebase and shared Flutter framework, each with its own config and content.

Alphazed operates 7+ educational apps (Amal, Thurayya, Qais, KidElite, Alphazed School, Alphazed Montessori, and more) from a single backend codebase and a shared Flutter mobile framework. Each app gets its own database tables (prefixed), configuration, push notifications, email templates, and content — but shares authentication (AWS Cognito), analytics infrastructure, and core learning algorithms.

Backend: Runtime App Selection

How It Works

At deployment, an environment variable selects the app:

# Deploy Amal
export APP_NAME=amal
serverless deploy

# Deploy Thurayya
export APP_NAME=thurayya
serverless deploy

# Deploy Qais
export APP_NAME=qais
serverless deploy

Each deployment creates independent Lambda functions, API Gateway routes, and monitoring—but they all connect to the same backend codebase.

Three-Tier Config Priority

# src/config.py
import os

app_name = os.getenv('APP_NAME', 'amal')

# Tier 1: Exact app match
config = load_json(f'config/{app_name}.json')

# Tier 2: App family match (if exact config doesn't exist)
if not config:
    family = app_name.split('_')[0]  # 'amal_beta' → 'amal'
    config = load_json(f'config/{family}.json')

# Tier 3: Default
if not config:
    config = load_json('config/default.json')

Per-App Configuration (config/amal.json)

{
  "app_name": "amal",
  "app_id": "com.alphazed.amal",
  "database": {
    "table_prefix": "amal_"
  },
  "email": {
    "template_dir": "templates/amal",
    "from_address": "amal@alphazed.com",
    "from_name": "Amal Team"
  },
  "push_notifications": {
    "firebase_project": "alphazed-amal",
    "apns_certificate": "certs/amal.pem"
  },
  "content": {
    "s3_bucket": "amal-content-production",
    "curriculum_id": "amal_v3_arabic"
  },
  "feature_flags": {
    "enable_tajweed_feedback": false,
    "enable_noorani_qaida": false,
    "enable_juz_amma": false,
    "enable_creature_building": true,
    "enable_physics_games": true
  },
  "pricing": {
    "monthly_usd": 6.99,
    "yearly_usd": 67.99,
    "trial_days": 14
  }
}

Shared vs. Per-App Resources

Resource Shared Per-App Reason
Lambda code ✓ Yes ✗ No Single codebase, branching by APP_NAME
RDS instance ✓ Yes ✗ No (tables prefixed) Cost reduction, single backup
S3 content ✗ No ✓ Yes Each app has own curriculum
AWS Cognito ✓ Yes ✗ No (via app_id) Central auth, app_id differentiates
Analytics lake ✓ Yes ✗ No (partitioned by app) Unified pipeline, segregated data
Firebase messaging ✗ No ✓ Yes App-specific APNs + FCM

Frontend: Flutter Multi-App Architecture

Shared Core (packages/alphazed_common)

  • State management (Riverpod)
  • Audio handling
  • Animation library (Rive integration)
  • Design system (colors, typography, widgets)
  • Authentication flows
  • Analytics client

App-Specific Layers (apps/amal, apps/thurayya, etc.)

apps/
  ├── amal/
  │   ├── lib/
  │   │   ├── main.dart (app entry point)
  │   │   ├── config/
  │   │   │   ├── curriculum.json (Amal's lesson structure)
  │   │   │   ├── colors.dart (Amal's theme)
  │   │   │   └── characters.json (avatar customization)
  │   │   └── screens/ (Amal-specific screens)
  │   └── pubspec.yaml (Amal dependencies)
  │
  ├── thurayya/
  │   ├── lib/
  │   │   ├── main.dart (different entry point)
  │   │   ├── config/
  │   │   │   ├── curriculum.json (Juz Amma structure)
  │   │   │   ├── colors.dart (Thurayya's theme)
  │   │   │   └── characters.json (avatar customization)
  │   │   └── screens/ (Thurayya-specific screens)
  │   └── pubspec.yaml (Thurayya dependencies)
  │
  └── packages/
      └── alphazed_common/ (shared code)

Build-Time Configuration

# Build Amal
flutter build apk --dart-define=APP_NAME=amal --dart-define=CURRICULUM=amal_v3

# Build Thurayya
flutter build apk --dart-define=APP_NAME=thurayya --dart-define=CURRICULUM=thurayya_juzamma

At build time, each app gets its own curriculum data, colors, and configuration baked in. Single code repository, multiple compiled binaries.

Deployment: Independent Stacks

Serverless Framework Configuration

# serverless.yml
service: alphazed-backend

custom:
  pythonRequirements:
    dockerizePip: true
  app_name: ${env:APP_NAME}

functions:
  # These functions are deployed for every APP_NAME value
  get_user:
    handler: src/handlers/user.get_user
    events:
      - http:
          path: app/{app_name}/user/{user_id}
          method: get
  
  content_duo:
    handler: src/handlers/content.generate_content_duo
    timeout: 20
    events:
      - http:
          path: app/{app_name}/content_duo
          method: post

resources:
  Resources:
    # Each app gets its own CloudWatch log group
    ${self:custom.app_name}LogGroup:
      Type: AWS::Logs::LogGroup
      Properties:
        LogGroupName: /aws/lambda/alphazed-${self:custom.app_name}
        RetentionInDays: 30

When you deploy with APP_NAME=amal, CloudFormation creates:

  • Lambda functions prefixed alphazed-amal-*
  • API Gateway routes under /amal/*
  • CloudWatch logs under /aws/lambda/alphazed-amal
  • Independent scaling and monitoring

Why This Matters

For Product Teams

  • New app launch: weeks instead of months (share infrastructure)
  • Feature parity: bug fixes and algorithm improvements apply to all apps instantly
  • A/B testing: easily test features on one app before rolling to others

For Engineering

  • Single codebase: one set of tests, one CI/CD pipeline
  • Shared learning algorithms: spaced repetition and content mixing benefit all apps
  • Operational efficiency: one team manages all infrastructure

For Cost

  • Shared RDS: fraction of the cost of 7 independent databases
  • Shared Cognito: one auth provider for all apps
  • Shared analytics: one pipeline serving 7 apps
  • Estimated cost savings: $40,000-60,000/year vs. separate backends

Challenges and Solutions

Challenge 1: Database schema conflicts

  • Amal needs amal_user_memory table
  • Thurayya needs thurayya_user_memory table (different fields for Quran memorization)
  • Solution: Migrations are app-aware. migrations/001_amal_user_memory.sql only runs when APP_NAME=amal

Challenge 2: Different feature sets

  • Thurayya has tajweed feedback; Amal doesn't
  • Amal has physics games; Thurayya doesn't
  • Solution: Feature flags in config (enable_tajweed_feedback, enable_physics_games)

Challenge 3: Independent scaling

  • Amal sees traffic spike; Thurayya is quiet
  • Shared Lambda pool means they compete for concurrency
  • Solution: CloudWatch scaling policies per app. If amal-* Lambdas hit concurrency limit, auto-scale independently

FAQ

Q: Doesn't sharing one codebase create coupling between apps? A: No. Each app's code is in separate directories (apps/amal, apps/thurayya). Shared code is in packages/alphazed_common and explicitly versioned. Tight coupling is a design smell — we catch it in code review.

Q: What if one app needs a breaking API change? A: We version APIs per app: /amal/v1/*, /thurayya/v1/*. Apps can upgrade independently. Old versions run for 12 months, giving app teams time to update.

Q: Can apps share users? A: Not by default. Each app has its own user table (prefixed). If a parent wants to subscribe to both Amal and Thurayya, they create separate accounts (or we could add a "family" linking feature later).

Related Articles