Build an instant-messaging app
This tutorial builds a small but complete messaging backend with PBVex. It combines authentication, application profiles, contacts, conversation membership, authorization, realtime messages, and file attachments into one coherent application.
The tutorial focuses on backend contracts and framework-neutral client calls. You can use the resulting generated API from the core client, React hooks, or Svelte runes.
What you will build
By the end, the application supports:
- signing in with an application account;
- creating a PBVex profile for the authenticated identity;
- finding users by handle and adding private contacts;
- starting direct or group conversations;
- allowing only current members to read and send messages;
- allowing senders to edit their own messages;
- subscribing to message updates over realtime;
- uploading attachments through short-lived URLs;
- returning attachment download URLs only to conversation members.
Tutorial map
- Data model — define every table and index used by the application.
- Authentication and profiles — sign in and connect an auth identity to an application profile.
- Contacts — search profiles, add contacts, and return hydrated contact cards.
- Conversations and permissions — create membership and enforce participant/admin rules.
- Messages and realtime — send, paginate, edit, and subscribe to messages.
- Attachments — upload files, attach them to messages, and authorize downloads.
Each page builds on the previous one. Keep the final schema in one pbvex/schema.ts file even though the walkthrough introduces its tables by feature.
Prerequisites
Complete the Quickstart first. You should have:
- a running PBVex server;
- an application auth collection named
userswith at least one enabled sign-in method; - a TypeScript project initialized with
pbvex init; pbvexand@pbvex/clientinstalled;- a deployment token available outside source control.
The tutorial uses password authentication in short examples and works the same way after OTP, OAuth2, or MFA establishes the identity.
Project layout
The finished backend uses this structure:
pbvex/
├── schema.ts
├── profiles.ts
├── contacts.ts
├── conversations.ts
├── messages.ts
├── attachments.ts
└── lib/
├── identity.ts
├── membership.ts
└── validators.tsAfter changing schema or function exports, regenerate the typed contract:
pbvex codegen
pbvex typecheck
pbvex build --checkEvery exported query, mutation, and action in this tutorial declares args and returns because it is a stable deployed boundary. Ordinary local helpers such as requireIdentity and requireMembership are TypeScript implementation details and do not need runtime validators.
The request flow
The application deliberately keeps authorization on the server:
authenticated client
|
v
public PBVex function
|
+-- resolve identity
+-- verify owner/membership/role
+-- perform bounded indexed reads or transactional writes
v
typed result or subscription updateThe client never supplies its own owner, sender, or administrator identity. It supplies resource IDs and user input; the function derives the actor from ctx.auth and checks current database state.
Start with the complete data model.
After finishing the messaging backend, the standalone FakePayment tutorial shows how to add checkout, webhooks, and premium attachments as an optional extension.