backend-websocket
Skill1 starsChanged 10 months agoMITView on GitHub
Add WebSocket support with authentication to the backend. Use when asked to "add websocket", "add real-time", "add ws support", or "create websocket endpoint".
- Installs packages
In this repository14 other files it gives its agents.
- AGENTS.mdAGENTS.md
- skill-creatorSkill
- backend-ai-agentSkill
- backend-ai-toolsSkill
- backend-auth-clerkSkill
- backend-compressionSkill
- backend-metricsSkill
- backend-model-creationSkill
- backend-route-creationSkill
- backend-scaffoldingSkill
- frontend-auth-clerkSkill
- frontend-hooks-creationSkill
- frontend-scaffoldingSkill
- new-project-scaffoldingSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 278
Always use structured messages:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Backend WebSocket Support
This skill adds WebSocket support to a Koa backend with JWT authentication.
Overview
WebSocket connections require:
- JWT authentication as the first message
- Structured message format for all communication
- Proper cleanup on disconnect
Installation
npm install koa-websocket short-uuid @types/koa-websocketImplementation
Step 1: Enable WebSocket Support
Update apps/backend/src/main.ts:
import Koa from 'koa';
import websockify from 'koa-websocket';
import short from 'short-uuid';
// Create websocket-enabled Koa app
const app = websockify(new Koa());
// ... rest of app setup ...Step 2: Create Auth Middleware
Create apps/backend/src/middleware/wsAuth.ts:
import Koa from 'koa';
import short from 'short-uuid';
import { createLogger } from '../lib/logger';
const log = createLogger('ws-auth');
type WsNext = (ctx: Koa.Context) => Promise<void>;
// Your JWT verification function
async function verifyToken(token: string): Promise<{ uid: string; email: string }> {
// Implement your JWT verification logic
// This should throw if token is invalid
throw new Error('Implement verifyToken');
}
/**
* WebSocket authentication middleware
* Requires JWT token as first message
*/
export async function wsAuthMiddleware(ctx: Koa.Context, next: WsNext): Promise<void> {
// Generate unique ID for this connection
ctx.websocket['_id'] = short.generate();
log.debug({ connId: ctx.websocket['_id'] }, 'WebSocket connection received, waiting for auth');
// First message must be authentication
ctx.websocket.once('message', async (message) => {
try {
const data = JSON.parse(message.toString());
if (data.type !== 'login' || !data.token) {
log.warn({ connId: ctx.websocket['_id'] }, 'Invalid login message');
ctx.websocket.send(JSON.stringify({
type: 'error',
message: 'First message must be login with token',
}));
ctx.websocket.close();
return;
}
// Verify JWT token
const user = await verifyToken(data.token);
ctx.state.user = user;
log.info({ connId: ctx.websocket['_id'], uid: user.uid }, 'WebSocket authenticated');
ctx.websocket.send(JSON.stringify({
type: 'auth',
message: 'Authenticated',
}));
// Remove this listener and proceed to route handlers
ctx.websocket.removeAllListeners('message');
return next(ctx);
} catch (err) {
log.error({ err, connId: ctx.websocket['_id'] }, 'WebSocket auth failed');
ctx.websocket.send(JSON.stringify({
type: 'error',
message: 'Authentication failed',
}));
ctx.websocket.close();
}
});
}Step 3: Create WebSocket Route
Create apps/backend/src/routes/websocket/chat.ts:
import Router from '@koa/router';
import { createLogger } from '../../lib/logger';
const log = createLogger('ws-chat');
const router = new Router();
// Message type definitions
type IncomingMessage =
| { type: 'chat'; message: string }
| { type: 'typing'; isTyping: boolean }
| { type: 'ping' };
type OutgoingMessage =
| { type: 'chat'; message: string; from: string; timestamp: number }
| { type: 'typing'; userId: string; isTyping: boolean }
| { type: 'pong' }
| { type: 'error'; message: string };
router.all('/chat', async (ctx) => {
const user = ctx.state.user;
const connId = ctx.websocket['_id'];
log.info({ connId, uid: user.uid }, 'Chat WebSocket connected');
// Handle incoming messages
ctx.websocket.on('message', async (rawMessage) => {
try {
const message: IncomingMessage = JSON.parse(rawMessage.toString());
switch (message.type) {
case 'chat':
// Process chat message
const response: OutgoingMessage = {
type: 'chat',
message: `Echo: ${message.message}`,
from: 'server',
timestamp: Date.now(),
};
ctx.websocket.send(JSON.stringify(response));
break;
case 'typing':
// Handle typing indicator
log.debug({ connId, isTyping: message.isTyping }, 'Typing status');
break;
case 'ping':
ctx.websocket.send(JSON.stringify({ type: 'pong' }));
break;
default:
ctx.websocket.send(JSON.stringify({
type: 'error',
message: 'Unknown message type',
}));
}
} catch (err) {
log.error({ err, connId }, 'Error processing message');
ctx.websocket.send(JSON.stringify({
type: 'error',
message: 'Invalid message format',
}));
}
});
// Handle disconnect
ctx.websocket.on('close', () => {
log.info({ connId, uid: user.uid }, 'Chat WebSocket disconnected');
// Clean up any resources (e.g., remove from active users)
});
// Handle errors
ctx.websocket.on('error', (err) => {
log.error({ err, connId }, 'WebSocket error');
});
});
export default router;Step 4: Mount WebSocket Routes
Update apps/backend/src/main.ts:
import { wsAuthMiddleware } from './middleware/wsAuth';
import chatWsRoutes from './routes/websocket/chat';
import mount from 'koa-mount';
// WebSocket middleware (authentication)
app.ws.use(wsAuthMiddleware);
// Mount WebSocket routes
app.ws.use(mount('/ws', chatWsRoutes.middleware()));Client-Side Usage
Connection Flow
class WebSocketClient {
private ws: WebSocket | null = null;
private authenticated = false;
connect(url: string, token: string): Promise<void> {
return new Promise((resolve, reject) => {
this.ws = new WebSocket(url);
this.ws.onopen = () => {
// Send authentication message
this.ws!.send(JSON.stringify({
type: 'login',
token: token,
}));
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (!this.authenticated) {
if (message.type === 'auth') {
this.authenticated = true;
resolve();
} else if (message.type === 'error') {
reject(new Error(message.message));
}
return;
}
// Handle other messages
this.handleMessage(message);
};
this.ws.onerror = (error) => {
reject(error);
};
});
}
send(message: object): void {
if (!this.authenticated || !this.ws) {
throw new Error('Not connected');
}
this.ws.send(JSON.stringify(message));
}
private handleMessage(message: any): void {
// Handle incoming messages
console.log('Received:', message);
}
}
// Usage
const client = new WebSocketClient();
await client.connect('wss://api.example.com/ws/chat', jwtToken);
client.send({ type: 'chat', message: 'Hello!' });Message Format Convention
Always use structured messages:
// Incoming (client -> server)
{
type: 'message_type',
// ... payload fields
}
// Outgoing (server -> client)
{
type: 'message_type',
// ... payload fields
timestamp?: number // optional, for ordering
}
// Error responses
{
type: 'error',
message: 'Human readable error message'
}Best Practices
1. Always Authenticate First
// Server: reject if first message isn't login
if (data.type !== 'login') {
ctx.websocket.close();
return;
}2. Generate Connection IDs
ctx.websocket['_id'] = short.generate();
// Use in all logs for tracing3. Type Your Messages
type IncomingMessage =
| { type: 'chat'; message: string }
| { type: 'ping' };
// Use discriminated unions for type safety4. Handle Cleanup
ctx.websocket.on('close', () => {
// Remove from active connections
// Cancel any pending operations
// Clean up subscriptions
});5. Add Heartbeat/Ping
// Client sends ping periodically
setInterval(() => {
ws.send(JSON.stringify({ type: 'ping' }));
}, 30000);
// Server responds with pong
case 'ping':
ctx.websocket.send(JSON.stringify({ type: 'pong' }));
break;File Structure
apps/backend/src/
├── middleware/
│ └── wsAuth.ts # WebSocket authentication
├── routes/
│ └── websocket/
│ ├── chat.ts # Chat WebSocket routes
│ └── notifications.ts # Other WS routes
└── main.ts # Mount WS routesChecklist
- Install dependencies:
npm install koa-websocket short-uuid - Wrap app with
websockify()in main.ts - Create
middleware/wsAuth.tsfor authentication - Implement
verifyToken()with your JWT logic - Create WebSocket route files in
routes/websocket/ - Mount routes with
app.ws.use(mount(...)) - Test connection flow with client
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

