Skip to content

Chat App

Build a full real-time chat application — authentication, rooms, messages, file uploads, and role-based permissions — using Pulsabase.

Mobile (Flutter)

Native mobile chat with Flutter + Pulsabase Dart SDK. View on GitHub →

Authentication, database models with foreign keys, Row-Level Security, real-time table subscriptions, custom pub/sub channels, and file storage — all wired together with less than 20 lines of SDK calls.

AreaCallsWhat it does
Auth3Sign up, sign in, get current user
Database6Create room, list rooms, send and paginate messages
Real-Time2Subscribe to table publications (INSERT / DELETE)
Channels3Subscribe, send, and broadcast to a named room channel
Storage3Upload, list, and generate presigned URL for attachments
Total~17A complete chat — production-ready

Messages are inserted via the SDK, persisted in PostgreSQL, and immediately pushed to all subscribers over WebSocket — no polling, no extra setup.

Terminal window
git clone https://github.com/pulsabase/demo-chat-web
cd demo-chat-web
npm install
pulsabase login
pulsabase init # links to your Dashboard project
pulsabase db:sync # creates tables, constraints, RLS policies
# Edit src/lib/config.ts with your anon_key
npm run dev
models/Message.ts
import { primary, text, foreignId, ModelSchema } from 'pulsabase';
export default class Message {
@primary
id: string;
@foreignId({ references: 'id', on: 'rooms', onDelete: 'CASCADE' })
room_id: string;
@foreignId({ references: 'id', on: 'users', onDelete: 'CASCADE' })
sender_id: string;
@text(false)
content: string;
static schema: ModelSchema = {
tableName: 'messages',
publication: true, // every mutation is pushed to subscribers
timestamps: true,
policies: [
{ action: ['SELECT'], role: 'authenticated' },
{ action: ['INSERT'], role: 'authenticated' },
],
};
}

The demo uses two real-time techniques in parallel:

Subscribes to INSERT and DELETE events on the messages table. Every client in the room receives new messages automatically, regardless of sender.

pb.from(Message).on('INSERT', 'DELETE').listen((change) => {
if (change.action === 'INSERT' && change.data.room_id === selectedRoom.id) {
addMessageToUI(change.data);
} else if (change.action === 'DELETE') {
removeMessageFromUI(change.data.id);
}
});

For sub-second delivery without waiting for the full database round-trip, messages are also broadcast directly to a named channel.

const channel = pb.channel(`room:${selectedRoom.id}`);
channel.on('message', (payload) => {
addMessageToUI(payload);
});
await channel.subscribe();
// Insert to DB, then broadcast instantly
const saved = await pb.from(Message).insert({ content, room_id });
await channel.send('message', saved.data);