Server Setup
Set up a Gluonic sync server in minutes with minimal configuration.
Prerequisites
- Node.js 18+
- Database (PostgreSQL, MySQL, SQLite, MongoDB, etc.)
- Prisma (recommended) or another ORM
Installation
# Core server package
pnpm add @gluonic/server
# Database adapter
pnpm add @gluonic/server-prisma # Prisma (available now)
# pnpm add @gluonic/server-typeorm # TypeORM (coming soon)
# pnpm add @gluonic/server-mongodb # MongoDB (coming soon)
# Auth helper (optional but recommended)
pnpm add @gluonic/auth-jwtBasic Setup
Step 1: Define Your Prisma Schema
Note: Gluonic currently supports Prisma. Adapters for TypeORM and MongoDB are coming soon. You can also build a custom adapter for any database.
SyncAction Table (Required)
First, add the SyncAction table to track changes:
// Required: SyncAction table tracks changes for delta sync
model SyncAction {
id BigInt @id @default(autoincrement())
orgId String
model String
modelId String
op String
patch String?
actorId String
clientTxId String?
createdAt DateTime @default(now())
@@index([orgId, id])
}This table is used by Gluonic automatically - you never query it directly.
Your Models
Gluonic needs to know which fields in each model are used for versioning (conflict detection) and ownership (data filtering). You have two options:
Option A: Auto-Discovery (Recommended)
Follow naming conventions and Gluonic detects fields automatically:
model User {
id String @id @default(cuid())
email String @unique
name String?
// Auto-discovery looks for these field names
orgId String // Ownership field (orgId, workspaceId, tenantId, or ownerId)
version Int @default(1) // Version field (must be named "version")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id
title String
content String
// Auto-discovery conventions
orgId String // Ownership (private model)
version Int @default(1) // Versioning
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Tag {
id String @id
name String
version Int @default(1) // Has version but NO orgId = public model
}Auto-discovery rules:
- Required: Field named
version - Optional: ONE field named
orgId,workspaceId,tenantId, orownerId- With ownership → Private (filtered by org)
- Without ownership → Public (everyone sees all)
- Multiple ownership fields → Error (use manual config)
Have existing schema with different field names? See Option B below for manual configuration.
Option B: Manual Configuration
Use any field names you want, then specify them in code:
model Post {
id String @id
organizationId String // Your custom ownership field
rev Int // Your custom version field
}const database = PrismaAdapter({
prisma,
models: {
post: {
ownership: 'organizationId', // Tell Gluonic which field
versioning: 'rev' // Tell Gluonic which field
}
}
})API Reference: PrismaAdapter - Complete configuration options
When to use:
- Auto-discovery: Starting fresh (simpler)
- Manual config: Existing schemas with different field names
Step 2: Create Sync Server
Now create and start your sync server:
import { SyncServer } from '@gluonic/server'
import { PrismaAdapter } from '@gluonic/server-prisma'
import { JWTAuth } from '@gluonic/auth-jwt'
import { prisma } from './db'
// Create database adapter (auto-discovers models with orgId + version fields)
const database = PrismaAdapter({ prisma })
// Create sync server with database and authentication
const server = SyncServer({
database,
auth: JWTAuth({ secret: process.env.JWT_SECRET })
})
// Start listening on port 3000
await server.listen({ port: 3000 })How auto-discovery works: The Prisma client instance contains embedded schema metadata (DMMF - Data Model Meta Format). When you run
prisma generate, your schema is compiled into the client.PrismaAdapterintrospects this metadata at runtime to discover models - no schema file path needed!
Prerequisites:
- Run
prisma generateafter defining your schema- Create
./db.tsthat exports your Prisma client singleton:// db.ts (Prisma singleton pattern) import { PrismaClient } from '@prisma/client' const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined } export const prisma = globalForPrisma.prisma ?? new PrismaClient() if (process.env.NODE_ENV !== 'production') { globalForPrisma.prisma = prisma }This prevents multiple instances during development hot reload. See Prisma’s best practices for more details.
API Reference: SyncServer · PrismaAdapter · JWTAuth
That’s it! Your sync server is now running with:
- ✅
GET /sync/v1/bootstrap- Initial data load - ✅
GET /sync/v1/delta?since=N- Incremental updates - ✅
GET /sync/v1/ws- WebSocket real-time sync - ✅
POST /sync/v1/tx- Mutations (create, update, delete) - ✅
GET /sync/v1/metrics- Server metrics
Next: Set up the client or continue reading to configure your server.
Configuration
Database
Gluonic works with any database through adapters.
Available now:
PrismaAdapter- Prisma ORM (@gluonic/server-prisma)
Coming soon:
TypeORMAdapter- TypeORM (@gluonic/server-typeorm)MongoAdapter- MongoDB (@gluonic/server-mongodb)- Or build your own adapter for any database
See Database Adapters guide for detailed configuration.
Authentication
All sync endpoints require authentication to secure your data.
Built-in JWT helper (recommended):
import { JWTAuth } from '@gluonic/auth-jwt'
auth: JWTAuth({
secret: process.env.JWT_SECRET,
expiresIn: '7d' // Optional: token expiration
})API Reference: JWTAuth - Complete authentication options
For custom authentication (OAuth, sessions, API keys), see the Authentication guide.
Real-Time Sync
Real-time WebSocket updates work out of the box with SyncServer.
Single Server (Default):
- WebSocket endpoint automatically available at
/sync/v1/ws - Clients connected to this server receive real-time updates
- No additional configuration needed
With Broadcaster (Recommended for Production):
Add a broadcaster when you need:
- Service isolation - Run Gluonic as a separate service from your main API
- Cross-service updates - Your main API can trigger sync updates
- Horizontal scaling - Multiple Gluonic pods sync with each other
import { RedisBroadcaster } from '@gluonic/broadcaster-redis'
const server = SyncServer({
database,
auth: JWTAuth({ secret: process.env.JWT_SECRET }),
broadcaster: RedisBroadcaster({ url: 'redis://localhost:6379' })
})How it works:
- Your main API writes data → publishes to Redis → all Gluonic pods receive → all WebSocket clients updated
- Gluonic pod writes data → publishes to Redis → all other pods receive → all WebSocket clients updated
Available broadcasters:
RedisBroadcasterfrom@gluonic/broadcaster-redis(available now)- More coming soon:
NATSBroadcaster,RabbitMQBroadcaster, etc.
API Reference: RedisBroadcaster - Broadcasting configuration
See Real-Time Sync guide for detailed setup and architecture.
Snapshots (Optional)
Cache bootstrap responses for 10x faster initial sync.
Snapshot adapters handle pre-computing and caching bootstrap payloads:
import { S3Snapshots } from '@gluonic/snapshots-s3'
const server = SyncServer({
database,
auth: JWTAuth({ secret: process.env.JWT_SECRET }),
snapshots: S3Snapshots({
bucket: 'my-app-snapshots',
intervalMinutes: 10
})
})Available snapshot adapters:
S3Snapshotsfrom@gluonic/snapshots-s3(available now)FileSnapshotsfrom@gluonic/snapshots-file(coming soon)RedisSnapshotsfrom@gluonic/snapshots-redis(coming soon)
API Reference: S3Snapshots - Snapshot caching configuration
See Snapshots guide for detailed configuration.
Complete Example
Here’s everything together - a production-ready sync server with all features:
import { SyncServer } from '@gluonic/server'
import { PrismaAdapter } from '@gluonic/server-prisma'
import { JWTAuth } from '@gluonic/auth-jwt'
import { RedisBroadcaster } from '@gluonic/broadcaster-redis'
import { S3Snapshots } from '@gluonic/snapshots-s3'
import { prisma } from './db'
// Create database adapter with auto-discovery
const database = PrismaAdapter({ prisma })
// Create sync server with all features
const server = SyncServer({
database,
auth: JWTAuth({ secret: process.env.JWT_SECRET }),
// Real-time broadcasting (optional)
broadcaster: RedisBroadcaster({ url: process.env.REDIS_URL }),
// Snapshot caching (optional)
snapshots: S3Snapshots({
bucket: process.env.SNAPSHOT_BUCKET,
intervalMinutes: 60
}),
// Performance tuning (optional)
maxDelta: 5000,
retentionDays: 30
})
// Start listening
await server.listen({
port: Number(process.env.PORT) || 3000,
host: '0.0.0.0'
})
console.log('🚀 Sync server running')API Reference: SyncServer · PrismaAdapter · JWTAuth · RedisBroadcaster · S3Snapshots
Environment Variables
# Database
DATABASE_URL="postgresql://user:pass@localhost:5432/myapp"
# Authentication
JWT_SECRET="your-super-secret-key-change-in-production"
# Optional: Redis (for real-time sync)
REDIS_URL="redis://localhost:6379"
# Optional: Snapshots (for caching)
SNAPSHOT_BUCKET="my-app-snapshots"
AWS_REGION="us-east-1"
# Server
PORT=3000
NODE_ENV=productionVerification
Test your server is running:
# Check server is up
curl http://localhost:3000/sync/v1/metrics
# Expected response:
{
"bootstrapCount": 0,
"deltaCount": 0,
"wsConnections": 0
}Next Steps
Essential:
- Set up the client - Connect your React app to this sync server
Enhance your setup:
- Real-Time Sync - Multi-pod deployment and cross-service broadcasting
- Snapshots - Performance optimization with cached bootstrap
- Deployment - Deploy to AWS, Vercel, or self-hosted
Explore:
- Database Adapters - TypeORM and MongoDB adapters (coming soon)
- Authentication - Detailed auth patterns
Advanced
Need more control?
- Build custom adapters for any database
- Implement custom authentication (OAuth, sessions, API keys) - see Authentication guide