Skip to main content

Ship a Realtime Team Board App with SQLite – No Backend Required

Build and scale a realtime mobile app with secure authentication, row-level SQLite access, live presence updates, and cloud storage - using Calljmp and the open-source Team Board app. No backend code required.

blog preview 7

Build Realtime Mobile Apps Without Backend Complexity

Building production-ready mobile apps usually means stitching together multiple backend services: auth, storage, pub/sub, access control, and a database. Even with modern platforms, you're often left managing API keys, writing server logic, and securing infrastructure.

Calljmp changes that. It’s a backend-as-a-service (BaaS) that runs on Cloudflare’s edge, offering a SQLite-compatible database, built-in auth, realtime sync, and secure storage - no backend code required.

In this post, we’ll walk through Team Board, an open-source mobile app built with Calljmp. It demonstrates everything from secure login and live reactions to file uploads and realtime presence - all with zero server setup.

What is Team Board?

Team Board is a realtime collaborative post board for teams, built with React Native (Expo) and powered entirely by Calljmp.

It showcases how to build a secure, scalable, realtime mobile app using only frontend logic and Calljmp's mobile-first SDKs.

  • Authentication: Email/password login with session tokens
  • App Attestation: Verifies client integrity
  • Row-Level Security (RLS): Role- and tag-based access control
  • Realtime Pub/Sub: Presence and event updates via WebSocket
  • SQLite Database: Full SQL support with built-in security
  • Storage: File upload with signed public URLs

Here’s a quick breakdown of what the app does:

  1. Users sign up and log in via email/password
  2. Realtime board shows posts with reactions syncing across devices
  3. Online presence updates live via pub/sub
  4. Users upload avatars and edit profiles
  5. Role-based rules ensure users can only modify their own content

All data access is securely filtered using RLS, and all realtime interactions are handled by Calljmp’s platform - no server-side logic necessary.

Watch the Demo

Here’s the Team Board app in action - realtime sync, live reactions, and presence updates across devices:

Key Features Implemented with Calljmp

Calljmp’s SDK and backend let you focus entirely on your mobile app logic. Here's how Team Board implements core features - without needing to write or deploy server-side code.

1. Secure Authentication (No API Keys)

Calljmp’s built-in email/password auth includes session management, scoped tokens, and secure defaults. No OAuth setup, no server routes.

const { data, error } = await calljmp.users.auth.email.authenticate({
  email,
  password,
  tags: ['role:member'],
  policy: UserAuthenticationPolicy.CreateNewOnly,
});

Tokens are scoped to the app and verified by the edge - no API keys needed.

2. Realtime Pub/Sub and Presence Tracking

Calljmp’s realtime pub/sub system enables live updates and presence visibility - without setting up WebSocket infrastructure.

import { RealtimeSubscription, User } from '@calljmp/react-native';

const PresenceTopic = 'users.presence';

interface PresenceData {
  user: User;
  online: boolean;
}

const publishPresence = async (online: boolean) => {
  await calljmp.realtime.publish<PresenceData>({
    topic: PresenceTopic,
    data: { user, online },
  });
};

const presenceSubscription = await calljmp.realtime
  .observe<PresenceData>(PresenceTopic)
  .on('data', (_topic, data) => {
    if (data.online) {
      if (user) {
        publishPresence(true);
      }
      setUsersOnline(prev =>
        prev.some(u => u.id === data.user.id) ? prev : [...prev, data.user]
      );
    } else {
      setUsersOnline(prev => prev.filter(user => user.id !== data.user.id));
    }
  })
  .subscribe();

Subscribe and react to online users in real-time with one API.

3. Full SQL Support with Row-Level Security

With Calljmp, you can write complex SQL queries directly in your app. Joins, aggregates, pagination - all supported.

But unlike most direct-access systems, Calljmp applies access controls automatically using Row-Level Security (RLS), so you never expose unauthorized data - even in complex queries.

const { data, error } = await calljmp.database.query({
  sql: `
    SELECT
      posts.id,
      posts.title,
      posts.content,
      posts.created_at,
      users.id AS author_id,
      users.name AS author_name,
      users.avatar AS author_avatar,
      users.tags AS author_tags,
      users.created_at AS author_created_at,
      COALESCE(SUM(CASE WHEN reactions.type = 'heart' THEN 1 ELSE 0 END), 0) AS heart_reactions,
      COALESCE(SUM(CASE WHEN reactions.type = 'thumbsUp' THEN 1 ELSE 0 END), 0) AS thumbs_up_reactions,
      COALESCE(SUM(CASE WHEN reactions.type = 'heart' AND reactions.user_id = ? THEN 1 ELSE 0 END), 0) AS user_reacted_heart,
      COALESCE(SUM(CASE WHEN reactions.type = 'thumbsUp' AND reactions.user_id = ? THEN 1 ELSE 0 END), 0) AS user_reacted_thumbs_up
    FROM posts
    JOIN users ON posts.author = users.id
    LEFT JOIN reactions ON posts.id = reactions.post_id
    GROUP BY posts.id
    ORDER BY posts.created_at DESC
    LIMIT ? OFFSET ?`,
  params: [user?.id || 0, user?.id || 0, pageSize, pageOffset],
});

No need to manually filter on user_id. RLS is enforced by the backend on every query - so even if you forget to scope, unauthorized rows are never returned.

4. Realtime Database Observation

Calljmp lets you subscribe to realtime changes directly at the table level. This isn’t just event-based pub/sub - it’s native observation on your SQL tables.

Here’s how Team Board reacts to inserts and deletes on the posts table:

const subscription = await calljmp.database
  .observe<{
    id: number;
    author: number;
    title: string;
    content: string;
    created_at: string;
  }>('posts')
  .on('insert', async event => {
    const newPosts: Post[] = [];
    for (const row of event.rows) {
      // parse and build post...
    }
    setPosts(prevPosts => [...newPosts, ...prevPosts]);
  })
  .on('delete', event => {
    setPosts(prevPosts =>
      prevPosts.filter(post => !event.rows.some(row => row.id === post.id))
    );
  })
  .subscribe();

The .observe() API hooks directly into your database table. No need for polling or external sync services. Changes are reflected across all devices - securely and instantly.

Access controls still apply: users only observe changes to rows they are permitted to see, thanks to built-in RLS enforcement.

5. Upload and Serve Public Avatars

Upload and serve images securely using Calljmp’s storage API.

const name = `avatar-${Date.now()}.jpg`;
const type = image.mimeType || 'image/jpeg';

// Step 1: Upload to storage
const { data: file, error: fileError } = await calljmp.storage.upload({
  content: {
    uri: image.uri,
    name: image.fileName || name,
    type,
  },
  bucket: 'media',
  key: `${user.id}/${name}`,
  description: 'User avatar',
  type,
});

// Step 2: Create a public signed URL
const { data: signed, error: signError } = await calljmp.storage.signPublicUrl({
  bucket: 'media',
  key: file.key,
  cacheTtl: 28 * 24 * 60 * 60, // 28 days
  // expiresIn: undefined, // never expires
});

// Step 3: Update the user profile
const { data: newUser, error: updateError } = await calljmp.users.update({
  avatar: signed.url,
});

No custom server routes needed. Just upload, sign, and update the avatar field. When the avatar is updated, Calljmp can broadcast a change in realtime - updating the header avatar across devices instantly.

Why Developers Love Calljmp

  • Built-in SQLite database with full SQL control
  • Realtime sync and presence without extra infra
  • RLS-enforced security on every query
  • No backend code or API gateways
  • Predictable, usage-based pricing

How Calljmp Compares to Firebase & Supabase

We took a real application - a mobile chat platform with 200K MAUs, 20K DAUs, 5K concurrent connections, 12M messages/month, and heavy real-time usage and analyzed backend pricing across Firebase, Supabase, and Calljmp. Check the result in the table below.

FirebaseSupabaseCalljmp
Monthly active users$735.00$325.00$0.00
Database (with 28 days PITR)$211.41$510.00$188.20
Storage$18.27$8.40$50.16
Cloud functions$160.23$196.00$588.00
Real-time$0.00$1,267.50$51.00
Bandwidth$453.28$74.70$0.00
Total$1,578.19$2,381.60$877.36

Calljmp offers predictable pricing with no hidden costs - perfect for mobile apps that scale fast or run on limited budgets.

Scale Fast. Pay Less. Sleep Better.

Calljmp is the fastest way to build secure

Try the Team Board App Yourself

Explore the full source code on GitHub and see how easy it is to build realtime apps with secure access and zero server logic.

Check out the open-source Team Board demo app and try it yourself.

More from our blog

Continue reading with more insights, tutorials, and stories from the world of mobile development.