Skip to main content

Technology Stack

CricTalk is built using a modern mobile development stack optimized for cross-platform performance and developer experience.

Frontend Stack

React Native

Version 0.81.5 - Core mobile framework

Expo

Version ~54.0 - Development platform and build tools

TypeScript

Version ~5.9.2 - Type safety and developer experience

NativeWind

Version 5.0 - Tailwind CSS for React Native

Key Dependencies

  • Expo Router (v6.0.22) - File-based routing system
  • React (v19.1.0) - UI framework
  • Zustand (v5.0.9) - State management
  • Zod (v4.3.6) - Schema validation
  • React Native Reanimated (v4.1.1) - Animations
  • Expo Notifications (v0.32.16) - Push notifications

Backend Stack

Appwrite

Backend-as-a-Service platform

Node.js

Runtime for serverless functions (Node 22)

Appwrite Services

  • Account - User authentication and session management
  • TablesDB - Database operations
  • Functions - Serverless compute
  • Storage - File and media management
  • Messaging - Push notification delivery
  • Teams - Group management capabilities

Application Structure

File-Based Routing

CricTalk uses Expo Router for navigation, providing a file-system based routing structure:
app/
├── _layout.tsx          # Root layout with auth & notifications
├── (auth)/              # Authentication flow
├── (tabs)/              # Main tab navigation
│   ├── index.tsx        # Home/Feed
│   ├── rooms.tsx        # Live chat rooms
│   ├── create.tsx       # Create posts
│   └── profile.tsx      # User profile
└── [dynamic]/           # Dynamic routes
The root layout (app/_layout.tsx) handles:
  • User Session Initialization - Fetches current user on mount
  • Zustand Store Hydration - Populates user state (username, email, favTeam, joinDate)
  • Notification Handler Setup - Configures notification behavior (sound, badge, banner)
  • Global Toast Messages - Renders toast notifications
export default function RootLayout() {
  const [user, setUser] = useState<Models.User<Models.Preferences> | null>(null);

  useEffect(() => {
    (async () => {
      const userData = await account.get();
      setUser(userData);
      setUsername(userData.name || userData.email.split("@")[0]);
      setFavTeam(userData.prefs?.favTeam || "None");
      setEmail(userData.email);
      setJoinDate(new Date(userData.$createdAt));
    })();
  }, []);

  // Notification handler configuration
  Notifications.setNotificationHandler({
    handleNotification: async () => ({
      shouldPlaySound: true,
      shouldSetBadge: true,
      shouldShowBanner: true,
      shouldShowList: true,
    }),
  });

  return (
    <>
      <Stack screenOptions={{ headerShown: false }} />
      <Toast />
    </>
  );
}

State Management

Zustand Stores

CricTalk uses Zustand for lightweight, performant state management.
Manages user profile and preferences.
type UserType = {
  username: string | null;
  email: string | null;
  favTeam: string | null;
  messageCount: number;
  joinDate: Date;
  setUsername: (username: string) => void;
  setEmail: (email: string) => void;
  setFavTeam: (favTeam: string) => void;
  setMessageCount: (count: number) => void;
  setJoinDate: (date: Date) => void;
};
Location: store/useUser.ts
Manages post feed state with optimistic updates.
type PostsType = {
  posts: Post[];
  setPosts: (posts: Post[]) => void;
  addPost: (post: Post) => void;
  addPosts: (posts: Post[]) => void;
  updatePost: (post: Partial<Post>) => void;
  deletePost: (postId: string) => void;
};
Features:
  • Automatic sorting by creation date
  • Duplicate prevention on batch adds
  • Optimistic UI updates for likes/views
Location: store/usePosts.ts
Manages comment state for individual posts.Location: store/useComments.ts
Manages live chat room state.Location: store/useRooms.ts

Backend Architecture

Appwrite Configuration

Project: Cric Talk
Endpoint: https://sgp.cloud.appwrite.io/v1
Region: Singapore (SGP)

Authentication Flow

1

Supported Methods

  • Email/Password
  • Email OTP (One-Time Password)
  • Magic URL (passwordless)
  • Phone authentication
  • Anonymous sessions
  • JWT tokens
2

Session Management

  • Session Duration: 365 days (31,536,000 seconds)
  • Max Sessions per User: 10
  • Rate Limiting: No global limit (custom implementation)
3

Security Settings

  • Password history: Disabled
  • Password dictionary check: Disabled
  • Personal data check: Disabled
  • Session alerts: Disabled

Serverless Functions

CricTalk uses Appwrite Functions for server-side logic, access control, and rate limiting.

posts-guard

Handles post CRUD operations, likes, and views with rate limiting

comments-guard

Manages comment creation, updates, and deletions

rooms-guard

Controls chat room creation and management

room-message-guard

Handles real-time chat message operations

notifications-guard

Fetches user-specific notifications

user-push-token-guard

Manages push notification tokens

leaderboard-guard

Handles leaderboard data and rankings

Function Specifications

  • Runtime: Node.js 22
  • Resources: 0.5 vCPU, 512 MB RAM
  • Timeout: 15 seconds
  • Execution: Available to any authenticated user
  • Scopes: users.read, tables.read, tables.write, rows.read, rows.write
Handles all post-related operations through a single serverless function:Actions:
  • create - Create new post (rate limited: 10/min)
  • update - Update existing post (rate limited: 10/min)
  • delete - Delete post (rate limited: 10/min)
  • like - Toggle like on post (unlimited)
  • view - Increment view count (unlimited)
Rate Limiting Implementation:
async function rateLimitCheck(activity) {
  const windowKey = Math.floor(Date.now() / 60000); // 1-minute window
  
  const userActivity = await tablesDB.listRows({
    databaseId: CRIC_TALK_DATABASE_ID,
    tableId: RATE_LIMIT_TABLE_ID,
    queries: [
      Query.equal('userId', userId),
      Query.equal('activity', activity),
      Query.equal('windowKey', windowKey),
    ],
  });

  if (userActivity.rows.length === 0) {
    // Create new rate limit entry
    await tablesDB.createRow({
      data: { userId, activity, windowKey, activityCount: 1 }
    });
    return;
  }

  const row = userActivity.rows[0];
  if (row.activityCount >= 10) {
    throw new Error('Rate limit exceeded: Too many requests in a short period.');
  }

  // Increment activity count
  await tablesDB.incrementRowColumn({
    rowId: row.$id,
    column: 'activityCount',
    value: 1,
  });
}
Authorization:
  • User ID extracted from x-appwrite-user-id header
  • Update/Delete operations verify post ownership
  • Row-level permissions enforce access control

Real-Time Features

Appwrite Realtime

CricTalk leverages Appwrite’s real-time subscriptions for live updates:
  • Live Chat Rooms - Real-time message streaming
  • Post Feed Updates - New posts appear instantly
  • Notification Delivery - In-app notifications
  • Like/Comment Counters - Live engagement metrics

Implementation Pattern

import { client } from '@/libs/appwrite';

// Subscribe to real-time events
const unsubscribe = client.subscribe(
  'databases.{databaseId}.tables.{tableId}.rows',
  (response) => {
    // Handle create, update, delete events
    if (response.events.includes('databases.*.rows.*.create')) {
      // Add new row to state
    }
  }
);

Push Notifications Architecture

Expo Notifications Setup

1

Device Token Registration

On app launch, request notification permissions and register device token with Appwrite.
2

Token Storage

Tokens stored in users table under pushTokens array field (supports multiple devices per user).
3

Notification Handler

Global handler configured in _layout.tsx controls notification presentation:
  • Show banner when app is open
  • Play sound on receive
  • Update app badge count
  • Add to notification list
4

Delivery via Appwrite Messaging

Backend functions trigger push notifications using Appwrite Messaging service.

Notification Types

  • New Comment - Someone comments on your post
  • Post Like - Someone likes your post
  • Room Message - New message in joined chat room
  • System Alerts - App announcements and updates

Rate Limiting Implementation

Sliding Window Algorithm

CricTalk implements custom rate limiting using the rateLimit table: Window Duration: 1 minute (60,000ms)
Max Requests: 10 per activity per user per window

Rate-Limited Activities

create_post

10/min

update_post

10/min

delete_post

10/min

create_comment

10/min

create_room

10/min

send_message

10/min

How It Works

  1. Window Key Calculation: windowKey = Math.floor(Date.now() / 60000)
  2. Activity Lookup: Query rateLimit table for user + activity + windowKey
  3. First Request: Create new row with activityCount = 1
  4. Subsequent Requests: Increment activityCount if < 10, else reject
  5. Window Expiry: Old windows naturally expire as windowKey changes
This implementation allows precise control per activity type while maintaining simplicity. The activityCount column has a max constraint of 60 at the database level.

Database Design

See Database Schema for complete table structures, relationships, and indexes. Database: cric_talk (ID: 695761d00008fd927f78) Tables:
  • posts - User posts with engagement metrics
  • comments - Post comments
  • rooms - Live chat rooms for matches
  • roomMessaage - Chat messages (note: typo in original schema)
  • users - Extended user profiles
  • notifications - In-app notifications
  • rateLimit - Rate limiting tracking

Build docs developers (and LLMs) love