Managing Client Connections
Detect and respond to client connections and disconnections in real time from the backend.
Why Use Client Connections
You're building a feature that depends on knowing whether a user is online: a chat app showing presence indicators, a collaborative editor tracking active participants, or a game lobby displaying connected players.
Without connection tracking, you'd need to implement heartbeat polling or custom WebSocket logic. With Squid's client connection handler, you decorate a single function and react to state changes as they happen:
// Backend: react to connection changes
@clientConnectionStateHandler()
async onConnectionChange(clientId: ClientId, state: ClientConnectionState): Promise<void> {
if (state === 'DISCONNECTED') {
await this.squid.collection('presence').doc(clientId).update({ status: 'offline' });
}
}
// Frontend: check connection status
const isConnected = squid.connectionDetails().connected;
No polling. No custom WebSocket management. Just a decorator.
Overview
Client connections let your backend detect when clients connect to, disconnect from, or are removed from the Squid server. Each client is assigned a unique clientId on connection, which you can use to track presence, clean up resources, or trigger workflows.
When to use client connections
| Use Case | Recommendation |
|---|---|
| Track user online/offline status | Client connections |
| Clean up resources when a user leaves | Client connections |
| React to database changes | Use Triggers |
| Run code on a schedule | Use Schedulers |
How it works
- A client connects to Squid and is assigned a unique
clientId - Your backend handler is called with state
CONNECTED - If the client disconnects (e.g., closes the browser tab), the handler is called with
DISCONNECTED - After a period, if the client does not reconnect, Squid removes the
clientIdand calls the handler withREMOVED - If the client reconnects after removal, they receive a new
clientId
Quick Start
Prerequisites
- A Squid backend project initialized with
squid init-backend - The
@squidcloud/backendpackage installed
Step 1: Create a connection handler
Add a connection state handler to your service class:
import { SquidService, clientConnectionStateHandler } from '@squidcloud/backend';
import { ClientConnectionState, ClientId } from '@squidcloud/client';
export class ExampleService extends SquidService {
@clientConnectionStateHandler()
async onConnectionChange(clientId: ClientId, state: ClientConnectionState): Promise<void> {
console.log(`Client ${clientId} is now ${state}`);
}
}
Step 2: Start or deploy the backend
For local development, run the backend locally using the Squid CLI:
squid start
To deploy to the cloud, see deploying your backend.
Step 3: Observe connection status on the client
import { filter } from 'rxjs';
// Check if currently connected
const isConnected = squid.connectionDetails().connected;
// Get the current client's ID
const clientId = squid.connectionDetails().clientId;
// React to connection changes
squid
.connectionDetails()
.observeConnected()
.pipe(filter(Boolean))
.subscribe(() => {
console.log('Connected with client ID:', squid.connectionDetails().clientId);
});
Core Concepts
Connection states
Each client transitions through these states:
| State | Meaning |
|---|---|
CONNECTED | The client just connected to the server |
DISCONNECTED | The client disconnected, but Squid retains the clientId in case the client reconnects |
REMOVED | The client has been disconnected long enough that Squid discarded the clientId. The next connection will receive a new ID. |
Client IDs
Every client receives a unique clientId on connection. This ID is:
- Available on the frontend via
squid.connectionDetails().clientId - Available on the backend via
this.context.clientId(in any backend function, not just connection handlers) - Stable across brief disconnects (the same ID is retained until
REMOVED) - Not the same as a
userIdfrom your auth provider. You must map between them if needed.
The @clientConnectionStateHandler decorator
The decorated function receives two arguments:
| Parameter | Type | Description |
|---|---|---|
clientId | ClientId (string) | The ID of the client whose state changed |
connectionState | ClientConnectionState | One of 'CONNECTED', 'DISCONNECTED', or 'REMOVED' |
The function can return void or Promise<void>.
Frontend connection API
Access connection information via squid.connectionDetails():
| Property / Method | Return Type | Description |
|---|---|---|
.connected | boolean | Whether the client is currently connected |
.clientId | string | The unique client ID assigned to this connection |
.observeConnected() | Observable<boolean> | Emits true on connect and false on disconnect |
The Squid client establishes its WebSocket connection lazily, meaning it only connects to the server when an operation requires it (such as subscribing to a query with .snapshots()). Calling .connected or .observeConnected() alone does not initiate the connection. If you need to ensure the client is connected before checking status, first subscribe to data or perform an operation that triggers the connection.
Error Handling
Handler errors
If your @clientConnectionStateHandler function throws an error, it is logged server-side but does not affect the client's connection. Make sure to handle errors within your handler to avoid silent failures:
@clientConnectionStateHandler()
async onConnectionChange(clientId: ClientId, state: ClientConnectionState): Promise<void> {
try {
await this.updatePresence(clientId, state);
} catch (error) {
console.error(`Failed to update presence for ${clientId}:`, error);
}
}
Common issues
| Issue | Cause | Solution |
|---|---|---|
| Handler not called | Service not exported or backend not deployed | Ensure service is exported in service/index.ts and run squid deploy |
clientId changes unexpectedly | Client was removed after prolonged disconnect | Use REMOVED state to clean up, and re-map on CONNECTED |
observeConnected() throws | Client is in passive mode | Ensure the Squid client is initialized in active (default) mode |
Best Practices
- Map
clientIdtouserIdon connect. SinceclientIdis connection-scoped anduserIdis auth-scoped, insert a mapping when the client connects so you can look up users by connection. - Clean up on
REMOVED, not justDISCONNECTED. ADISCONNECTEDclient may reconnect with the sameclientId. Only delete resources when the state isREMOVED. - Keep handler logic fast. Connection state handlers run for every connect/disconnect across all clients. Avoid expensive operations or chain them asynchronously.
- Use authentication to identify users. The
clientIdalone does not tell you who the user is. Combine with auth to build presence features.
Code Examples
User presence tracking
This example tracks which users are online by mapping clientId to userId in a presence collection.
Frontend: register presence on connect
import { filter } from 'rxjs';
interface PresenceData {
userId: string;
status: 'online' | 'offline';
}
// When connected, insert a presence record linking clientId to userId
squid
.connectionDetails()
.observeConnected()
.pipe(filter(Boolean))
.subscribe(() => {
const clientId = squid.connectionDetails().clientId;
squid.collection<PresenceData>('presence').doc(clientId).insert({ userId: currentUserId, status: 'online' });
});
Backend: update presence on disconnect/removal
import { SquidService, clientConnectionStateHandler } from '@squidcloud/backend';
import { ClientConnectionState, ClientId } from '@squidcloud/client';
export class PresenceService extends SquidService {
@clientConnectionStateHandler()
async handlePresenceChange(clientId: ClientId, state: ClientConnectionState): Promise<void> {
const presenceRef = this.squid.collection('presence').doc(clientId);
if (state === 'DISCONNECTED') {
await presenceRef.update({ status: 'offline' });
} else if (state === 'REMOVED') {
await presenceRef.delete();
}
}
}
Frontend: query online users
// Get all online users
const onlineUsers = await squid.collection<PresenceData>('presence').query().eq('status', 'online').dereference().snapshot();
console.log(
'Online users:',
onlineUsers.map((u) => u.userId)
);
For more on querying, see Queries.
See Also
- Authentication - Identify users in your backend
- Triggers - React to database changes
- Database - Query and mutate collections