Convex Integration
Real-time backend platform for live data sync, serverless functions, and file storage
Overview
Convex is a real-time backend platform that powers MemberPulse's live features including real-time notifications, live dashboards, collaborative features, and instant data synchronization across all connected clients.
Instant data updates across all clients
Backend logic without infrastructure
Integrated file storage with CDN
Why Convex?
| Feature | Benefit |
|---|---|
| Real-time by Default | All queries automatically update when data changes |
| ACID Transactions | Strong consistency guarantees for data integrity |
| TypeScript Native | End-to-end type safety from database to UI |
| Serverless | No infrastructure to manage, scales automatically |
| Built-in Auth | Integrates with existing auth providers |
Prerequisites
Before configuring Convex:
- Create a Convex account at convex.dev
- Install the Convex CLI:
npm install -g convex - Initialize your project:
npx convex dev
Configuration
Environment Variables
# Backend
CONVEX_DEPLOYMENT=prod:memberpulse-xxxxx
CONVEX_DEPLOY_KEY=prod:xxxxxxxxxxxxxxxxxxxx
# Frontend
NEXT_PUBLIC_CONVEX_URL=https://memberpulse-xxxxx.convex.cloud
Acceptance Criteria
Frontend
- Environment Variables workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Environment Variables as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Platform Settings
Navigate to Admin Portal > Platform Settings > Integrations > Convex
| Setting | Type | Required | Description |
|---|---|---|---|
| Deployment URL | URL | Yes | Convex deployment URL |
| Deploy Key | Secret | Yes | Production deploy key |
| Enable Real-time | Toggle | Yes | Enable real-time features |
| Sync Interval | Number | No | Fallback polling interval (ms) |
Acceptance Criteria
Frontend
- Platform Settings workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Platform Settings as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Architecture
Data Flow
graph LR
A[React Client] -->|Subscribe| B[Convex Cloud]
B -->|Real-time Updates| A
C[Symfony API] -->|Mutations| B
B -->|Webhooks| C
B -->|Store| D[(Convex DB)]
B -->|Files| E[Convex Storage]
Acceptance Criteria
Frontend
- Data Flow workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Data Flow as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Hybrid Architecture
MemberPulse uses a hybrid architecture:
| Data Type | Storage | Reason |
|---|---|---|
| Core Data | PostgreSQL (Symfony) | GDPR compliance, complex queries |
| Real-time Data | Convex | Live updates, collaboration |
| Files | Convex Storage | CDN delivery, easy uploads |
| Sessions | Convex | Real-time presence |
Acceptance Criteria
Frontend
- Hybrid Architecture workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Hybrid Architecture as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Real-time Features
Live Notifications
// convex/notifications.ts
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
export const list = query({
args: { userId: v.string() },
handler: async (ctx, args) => {
return await ctx.db
.query("notifications")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.filter((q) => q.eq(q.field("read"), false))
.order("desc")
.take(50);
},
});
export const markAsRead = mutation({
args: { notificationId: v.id("notifications") },
handler: async (ctx, args) => {
await ctx.db.patch(args.notificationId, { read: true });
},
});
// React component
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
function NotificationBell() {
const notifications = useQuery(api.notifications.list, {
userId: currentUser.id
});
const markAsRead = useMutation(api.notifications.markAsRead);
return (
<DropdownMenu>
<Badge count={notifications?.length || 0}>
<BellIcon />
</Badge>
<DropdownContent>
{notifications?.map((n) => (
<NotificationItem
key={n._id}
notification={n}
onRead={() => markAsRead({ notificationId: n._id })}
/>
))}
</DropdownContent>
</DropdownMenu>
);
}
Acceptance Criteria
Frontend
- Live Notifications workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Live Notifications as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Live Dashboard Metrics
// convex/metrics.ts
export const dashboardStats = query({
args: { organizationId: v.string() },
handler: async (ctx, args) => {
const [activeUsers, pendingApprovals, recentActivity] = await Promise.all([
ctx.db
.query("presence")
.withIndex("by_org", (q) => q.eq("organizationId", args.organizationId))
.filter((q) => q.gt(q.field("lastSeen"), Date.now() - 300000))
.collect(),
ctx.db
.query("approvals")
.withIndex("by_org_status", (q) =>
q.eq("organizationId", args.organizationId).eq("status", "pending")
)
.collect(),
ctx.db
.query("activity")
.withIndex("by_org", (q) => q.eq("organizationId", args.organizationId))
.order("desc")
.take(20),
]);
return {
activeUsersCount: activeUsers.length,
pendingApprovalsCount: pendingApprovals.length,
recentActivity,
};
},
});
Acceptance Criteria
Frontend
- Live Dashboard Metrics workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Live Dashboard Metrics as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Real-time Presence
Show who's online and what they're viewing:
// convex/presence.ts
export const heartbeat = mutation({
args: {
userId: v.string(),
organizationId: v.string(),
currentPage: v.string(),
},
handler: async (ctx, args) => {
const existing = await ctx.db
.query("presence")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.unique();
if (existing) {
await ctx.db.patch(existing._id, {
lastSeen: Date.now(),
currentPage: args.currentPage,
});
} else {
await ctx.db.insert("presence", {
...args,
lastSeen: Date.now(),
});
}
},
});
export const getOnlineUsers = query({
args: { organizationId: v.string() },
handler: async (ctx, args) => {
return await ctx.db
.query("presence")
.withIndex("by_org", (q) => q.eq("organizationId", args.organizationId))
.filter((q) => q.gt(q.field("lastSeen"), Date.now() - 60000))
.collect();
},
});
Acceptance Criteria
Frontend
- Real-time Presence workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Real-time Presence as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
File Storage
Uploading Files
// convex/files.ts
export const generateUploadUrl = mutation(async (ctx) => {
return await ctx.storage.generateUploadUrl();
});
export const saveFile = mutation({
args: {
storageId: v.id("_storage"),
fileName: v.string(),
fileType: v.string(),
uploadedBy: v.string(),
organizationId: v.string(),
},
handler: async (ctx, args) => {
return await ctx.db.insert("files", {
...args,
uploadedAt: Date.now(),
});
},
});
// React upload component
function FileUploader() {
const generateUploadUrl = useMutation(api.files.generateUploadUrl);
const saveFile = useMutation(api.files.saveFile);
const handleUpload = async (file: File) => {
// Get upload URL
const uploadUrl = await generateUploadUrl();
// Upload file to Convex storage
const result = await fetch(uploadUrl, {
method: "POST",
headers: { "Content-Type": file.type },
body: file,
});
const { storageId } = await result.json();
// Save file metadata
await saveFile({
storageId,
fileName: file.name,
fileType: file.type,
uploadedBy: currentUser.id,
organizationId: currentOrg.id,
});
};
return <DropZone onDrop={handleUpload} />;
}
Acceptance Criteria
Frontend
- Uploading Files workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Uploading Files as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Serving Files
// convex/files.ts
export const getFileUrl = query({
args: { storageId: v.id("_storage") },
handler: async (ctx, args) => {
return await ctx.storage.getUrl(args.storageId);
},
});
Acceptance Criteria
Frontend
- Serving Files workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Serving Files as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Scheduled Functions
Cleanup Jobs
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
// Clean up old presence records every 5 minutes
crons.interval(
"cleanup presence",
{ minutes: 5 },
internal.presence.cleanup
);
// Clean up old notifications daily
crons.daily(
"cleanup notifications",
{ hourUTC: 3, minuteUTC: 0 },
internal.notifications.cleanup
);
export default crons;
// convex/presence.ts
export const cleanup = internalMutation({
handler: async (ctx) => {
const stalePresence = await ctx.db
.query("presence")
.filter((q) => q.lt(q.field("lastSeen"), Date.now() - 3600000))
.collect();
for (const record of stalePresence) {
await ctx.db.delete(record._id);
}
},
});
Acceptance Criteria
Frontend
- Cleanup Jobs workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Cleanup Jobs as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Webhooks to Symfony
Sync real-time data back to the main database:
// convex/sync.ts
import { httpAction } from "./_generated/server";
export const syncToSymfony = httpAction(async (ctx, request) => {
const { event, data } = await request.json();
// Forward to Symfony webhook endpoint
const response = await fetch(process.env.SYMFONY_WEBHOOK_URL!, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Convex-Signature": generateSignature(data),
},
body: JSON.stringify({ event, data }),
});
return new Response(null, { status: response.status });
});
Schema Definition
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
notifications: defineTable({
userId: v.string(),
organizationId: v.string(),
type: v.string(),
title: v.string(),
message: v.string(),
link: v.optional(v.string()),
read: v.boolean(),
createdAt: v.number(),
})
.index("by_user", ["userId"])
.index("by_org", ["organizationId"]),
presence: defineTable({
userId: v.string(),
organizationId: v.string(),
currentPage: v.string(),
lastSeen: v.number(),
})
.index("by_user", ["userId"])
.index("by_org", ["organizationId"]),
activity: defineTable({
organizationId: v.string(),
userId: v.string(),
action: v.string(),
entityType: v.string(),
entityId: v.string(),
metadata: v.optional(v.any()),
createdAt: v.number(),
})
.index("by_org", ["organizationId"])
.index("by_user", ["userId"]),
files: defineTable({
storageId: v.id("_storage"),
fileName: v.string(),
fileType: v.string(),
uploadedBy: v.string(),
organizationId: v.string(),
uploadedAt: v.number(),
})
.index("by_org", ["organizationId"])
.index("by_user", ["uploadedBy"]),
});
Authentication
Convex integrates with your existing auth:
// convex/auth.config.ts
export default {
providers: [
{
domain: process.env.AUTH_DOMAIN,
applicationID: process.env.AUTH_CLIENT_ID,
},
],
};
// React provider setup
import { ConvexProviderWithAuth } from "convex/react";
function App() {
return (
<AuthProvider>
<ConvexProviderWithAuth client={convex} useAuth={useAuthFromProvider}>
<YourApp />
</ConvexProviderWithAuth>
</AuthProvider>
);
}
Deployment
Development
# Start local development
npx convex dev
Acceptance Criteria
Frontend
- Development workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Development as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Production
# Deploy to production
npx convex deploy --prod
Acceptance Criteria
Frontend
- Production workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Production as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Environment Setup
# Set environment variables
npx convex env set SYMFONY_WEBHOOK_URL https://api.memberpulse.com/webhooks/convex
npx convex env set AUTH_DOMAIN your-auth-domain.com
Acceptance Criteria
Frontend
- Environment Setup workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Environment Setup as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Monitoring
Convex Dashboard
Access at dashboard.convex.dev:
- Real-time function logs
- Database explorer
- Storage usage
- Performance metrics
- Error tracking
Acceptance Criteria
Frontend
- Convex Dashboard workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Convex Dashboard as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Metrics to Monitor
| Metric | Target | Alert Threshold |
|---|---|---|
| Query latency (p50) | < 50ms | > 200ms |
| Mutation latency (p50) | < 100ms | > 500ms |
| Active connections | - | > 10,000 |
| Storage usage | - | > 80% quota |
| Function errors | < 0.1% | > 1% |
Acceptance Criteria
Frontend
- Metrics to Monitor workflow is implemented in the UI as described.
Backend / API
- Backend behavior supports Metrics to Monitor as documented.
Permissions
- Access is restricted per the Capabilities matrix on this page (or equivalent role rules).
Business Rules
- All business rules for this feature are enforced.
Error Handling
- Error states return clear messages and appropriate HTTP status codes.
Troubleshooting
Real-time updates not working
- Verify
NEXT_PUBLIC_CONVEX_URLis correct - Check browser console for WebSocket errors
- Ensure user is authenticated
- Verify query is subscribed (not just called once)
File uploads failing
- Check file size limits (default 20MB)
- Verify storage quota not exceeded
- Ensure
generateUploadUrlis called before upload - Check CORS configuration if uploading from browser
Functions timing out
- Convex functions have 60-second timeout
- Break large operations into smaller chunks
- Use scheduled functions for long-running tasks
- Check for infinite loops in queries
Security
Never expose your Convex deploy key in client-side code. Use environment variables for all sensitive configuration.
- All data encrypted at rest and in transit
- Row-level security via authentication
- Automatic input validation via schema
- Rate limiting built-in
- GDPR-compliant data handling
Features
Convex Integration
Acceptance Criteria
Frontend
- Developer-facing configuration and usage is documented and internally consistent.
Backend / API
- Convex implementation matches the rules and contracts described on this page.
Permissions
- Tenant scoping and access controls are enforced as described.
Business Rules
- Domain rules/invariants are enforced as described.
Error Handling
- Access violations and validation failures produce deterministic errors.