Best Practices
This guide covers recommended patterns and practices for building robust real-time applications with Concorde.
Schema Design
Keep Schemas Simple and Clear
typescript
// ✅ Good: Clear, specific field names
const messageSchema = type({
messageId: 'string',
content: 'string',
senderId: 'string',
timestamp: 'number',
type: "'text' | 'image' | 'file'"
})
// ❌ Avoid: Ambiguous or overly generic names
const badSchema = type({
id: 'string',
data: 'object',
info: 'string'
})Use Union Types for Enums
typescript
// ✅ Good: Explicit union types
const notificationSchema = type({
type: "'info' | 'warning' | 'error' | 'success'",
priority: "'low' | 'medium' | 'high'",
category: "'system' | 'user' | 'security'"
})
// ❌ Avoid: Generic strings without constraints
const badNotificationSchema = type({
type: 'string',
priority: 'string',
category: 'string'
})Validate Data Constraints
typescript
// ✅ Good: Specific validation rules
const userSchema = type({
userId: 'string>0', // Non-empty string
email: 'email', // Built-in email validation
age: 'number>=13', // Minimum age requirement
username: 'string>=3<=20' // Length constraints
})Registry Organization
Shared Registry Module
Create a shared registry that both client and server can import:
typescript
// registry.ts
import { RegistryBuilder } from '@matfire/concorde'
import { addAuthChannels } from './features/auth/channels'
import { addChatChannels } from './features/chat/channels'
export const registry = new RegistryBuilder()
.let(addAuthChannels)
.let(addChatChannels)
.build()
// Or if you prefer object spreading:
import { authChannels } from './features/auth/channels'
import { chatChannels } from './features/chat/channels'
export const registry = {
...authChannels,
...chatChannels
} as constFeature-Based Organization
For large applications, organize channels by feature:
typescript
// features/auth/channels.ts
export const authChannels = {
'auth-{userId}': {
name: 'auth-{userId}' as const,
events: {
'session-expired': type(/* ... */),
'login-attempt': type(/* ... */)
}
}
} as const
// features/chat/channels.ts
export const chatChannels = {
'chat-{roomId}': {
name: 'chat-{roomId}' as const,
events: {
message: type(/* ... */),
'user-joined': type(/* ... */)
}
}
} as const
// registry.ts
import { authChannels } from './features/auth/channels'
import { chatChannels } from './features/chat/channels'
export const registry = {
...authChannels,
...chatChannels
} as const satisfies Record<string, ChannelDef>Error Handling Patterns
Comprehensive Error Handling
typescript
// server/pusher-service.ts
import { createServer } from '@matfire/concorde/server'
import { registry } from '../registry'
class PusherService {
private server = createServer(registry, pusher)
async sendNotification(
channel: string,
event: string,
data: unknown
): Promise<boolean> {
try {
await this.server.trigger(channel, event, data)
return true
} catch (error) {
this.handleError(error, { channel, event, data })
return false
}
}
private handleError(error: Error, context: any) {
if (error.message.includes('Trying to send invalid data')) {
// Validation error - log and potentially alert developers
console.error('Validation error:', error.message, context)
this.logToMonitoring('validation_error', { error, context })
} else if (error.message.includes('Unknown event')) {
// Configuration error - critical alert
console.error('Schema error:', error.message, context)
this.logToMonitoring('schema_error', { error, context })
} else {
// Network/Pusher error - retry might help
console.error('Pusher error:', error.message, context)
this.logToMonitoring('pusher_error', { error, context })
}
}
private logToMonitoring(type: string, data: any) {
// Send to your monitoring service
}
}Client-Side Error Boundaries
typescript
// client/pusher-client.ts
import { createClient } from '@matfire/concorde/client'
import { registry } from '../registry'
class PusherClient {
private client = createClient(registry, pusher)
private subscriptions = new Map()
safeSubscribe(channelName: string) {
try {
if (this.subscriptions.has(channelName)) {
return this.subscriptions.get(channelName)
}
const channel = this.client.subscribe(channelName)
this.subscriptions.set(channelName, channel)
return channel
} catch (error) {
console.error(`Failed to subscribe to ${channelName}:`, error)
return null
}
}
safeBind(channelName: string, event: string, handler: Function) {
const channel = this.safeSubscribe(channelName)
if (!channel) return
try {
channel.bind(event, handler)
} catch (error) {
console.error(`Failed to bind ${event} on ${channelName}:`, error)
}
}
cleanup() {
this.client.disconnect()
this.subscriptions.clear()
}
}Performance Optimization
Connection Management
typescript
// Use a singleton pattern for Pusher instances
class PusherManager {
private static instance: PusherManager
private pusherClient?: any
private concorideClient?: any
static getInstance(): PusherManager {
if (!PusherManager.instance) {
PusherManager.instance = new PusherManager()
}
return PusherManager.instance
}
getClient() {
if (!this.pusherClient) {
this.pusherClient = new Pusher(config.pusherKey, config.pusherOptions)
this.concorideClient = createClient(registry, this.pusherClient)
}
return this.concorideClient
}
disconnect() {
if (this.pusherClient) {
this.pusherClient.disconnect()
this.pusherClient = null
this.concorideClient = null
}
}
}Subscription Management
typescript
// React hook for managing subscriptions
import { useEffect, useRef } from 'react'
function useRealtimeSubscription(channelName: string, events: Record<string, Function>) {
const channelRef = useRef<any>(null)
useEffect(() => {
const client = PusherManager.getInstance().getClient()
channelRef.current = client.subscribe(channelName)
// Bind all events
Object.entries(events).forEach(([event, handler]) => {
channelRef.current.bind(event, handler)
})
return () => {
// Cleanup on unmount
Object.keys(events).forEach(event => {
channelRef.current?.unbind(event)
})
}
}, [channelName, events])
return channelRef.current
}
// Usage
function ChatComponent({ roomId }: { roomId: string }) {
const handlers = useMemo(() => ({
message: (data: any) => console.log('New message:', data),
'user-joined': (data: any) => console.log('User joined:', data)
}), [])
useRealtimeSubscription(`chat-${roomId}`, handlers)
return <div>Chat Room</div>
}Security Considerations
Data Sanitization
typescript
// Always validate and sanitize data before triggering events
function sanitizeMessage(content: string): string {
return content
.trim()
.slice(0, 1000) // Limit length
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // Remove scripts
}
async function sendChatMessage(roomId: string, content: string, userId: string) {
const sanitizedContent = sanitizeMessage(content)
if (!sanitizedContent) {
throw new Error('Message content is required')
}
await server.trigger(
{ template: 'chat-{roomId}', params: { roomId } },
'message',
{
content: sanitizedContent,
userId,
timestamp: Date.now()
}
)
}Rate Limiting
typescript
// Implement rate limiting for client actions
class RateLimiter {
private counts = new Map<string, { count: number; resetTime: number }>()
isAllowed(key: string, limit: number, windowMs: number): boolean {
const now = Date.now()
const current = this.counts.get(key)
if (!current || now > current.resetTime) {
this.counts.set(key, { count: 1, resetTime: now + windowMs })
return true
}
if (current.count >= limit) {
return false
}
current.count++
return true
}
}
const rateLimiter = new RateLimiter()
async function sendMessage(userId: string, content: string) {
if (!rateLimiter.isAllowed(`message:${userId}`, 10, 60000)) {
throw new Error('Rate limit exceeded')
}
// Send message...
}Testing Strategies
Mock Registry for Tests
typescript
// test-utils/mock-registry.ts
import { type } from 'arktype'
import { RegistryBuilder } from '@matfire/concorde'
export const mockRegistry = new RegistryBuilder()
.channel('test-channel', {
'test-event': type({
id: 'string',
data: 'string'
})
})
.build()
export const createMockPusher = () => ({
subscribe: vi.fn(() => ({
bind: vi.fn(),
unbind: vi.fn(),
trigger: vi.fn()
})),
disconnect: vi.fn(),
trigger: vi.fn()
})Integration Tests
typescript
// __tests__/integration.test.ts
describe('Real-time Communication', () => {
it('should handle end-to-end message flow', async () => {
const mockPusherServer = createMockPusher()
const mockPusherClient = createMockPusher()
const server = createServer(registry, mockPusherServer)
const client = createClient(registry, mockPusherClient)
// Test the complete flow
const channel = client.subscribe('chat-room')
const handler = vi.fn()
channel.bind('message', handler)
await server.trigger('chat-room', 'message', {
messageId: 'msg-123',
content: 'Hello world',
userId: 'user-456',
timestamp: Date.now()
})
expect(mockPusherServer.trigger).toHaveBeenCalledWith(
'chat-room',
'message',
expect.objectContaining({
content: 'Hello world'
})
)
})
})Deployment Considerations
Environment Configuration
typescript
// config/pusher.ts
interface PusherConfig {
key: string
cluster: string
appId?: string
secret?: string
forceTLS?: boolean
}
const pusherConfig: PusherConfig = {
key: process.env.PUSHER_KEY!,
cluster: process.env.PUSHER_CLUSTER!,
appId: process.env.PUSHER_APP_ID,
secret: process.env.PUSHER_SECRET,
forceTLS: process.env.NODE_ENV === 'production'
}
export default pusherConfigGraceful Shutdown
typescript
// server/graceful-shutdown.ts
class GracefulShutdown {
private pusherService: PusherService
constructor(pusherService: PusherService) {
this.pusherService = pusherService
process.on('SIGTERM', this.shutdown.bind(this))
process.on('SIGINT', this.shutdown.bind(this))
}
private async shutdown() {
console.log('Gracefully shutting down...')
// Stop accepting new requests
// Wait for pending operations
await this.pusherService.cleanup()
process.exit(0)
}
}Monitoring and Observability
Metrics Collection
typescript
// monitoring/metrics.ts
interface PusherMetrics {
messagesTriggered: number
validationErrors: number
connectionErrors: number
subscriptions: number
}
class MetricsCollector {
private metrics: PusherMetrics = {
messagesTriggered: 0,
validationErrors: 0,
connectionErrors: 0,
subscriptions: 0
}
incrementMessageTriggered() {
this.metrics.messagesTriggered++
}
incrementValidationError() {
this.metrics.validationErrors++
}
getMetrics(): PusherMetrics {
return { ...this.metrics }
}
reset() {
Object.keys(this.metrics).forEach(key => {
(this.metrics as any)[key] = 0
})
}
}Next Steps
- Check out the complete API Reference
- Learn about Dynamic Channels for advanced use cases