Socket Broker
A WebSocket broker application that handles topic-based messaging between frontend clients and backend services with API key authentication.
Features
- WebSocket Server (Port 3000): Handles client connections and topic subscriptions
- HTTP API (Port 8080): Allows backend services to publish messages to topics
- API Key Authentication: Secure access control for publishing messages
- Topic-based messaging: Clients subscribe to topics and receive messages when published
- Real-time communication: Instant message delivery to all subscribed clients
- Health monitoring: Built-in health checks and status endpoints
Architecture
Frontend Clients ←→ WebSocket Server (Port 3000)
↕
Backend Services ←→ HTTP API (Port 8080) [🔐 API Key Required]
Security
⚠️ IMPORTANT: The HTTP API endpoints that publish messages require a valid API key for authentication. This ensures that only authorized backend services can send messages to your WebSocket clients.
API Key Configuration
Set the API_KEY environment variable to secure your application:
# Set a strong, random API key
export API_KEY="your-secure-api-key-here"
# Or use in docker-compose
API_KEY=your-secure-api-key-here docker-compose up
Authentication Methods
The API accepts the API key in three ways:
-
Authorization Header (Recommended):
Authorization: Bearer your-api-key-here -
X-API-Key Header:
X-API-Key: your-api-key-here -
Query Parameter:
POST /publish?api_key=your-api-key-here
Quick Start
Using Docker
# Build the image
docker build -t socket-broker .
# Run the container with API key
docker run -d \
--name socket-broker \
-p 3000:3000 \
-p 8080:8080 \
-e API_KEY="your-secure-api-key" \
socket-broker
Using Docker Compose
# Copy environment file
cp env.example .env
# Edit .env and set your API key
nano .env
# Start the service
docker-compose up -d
Using Node.js directly
# Set API key
export API_KEY="your-secure-api-key"
# Install dependencies
npm install
# Start the application
npm start
Environment Variables
| Variable | Default | Description |
|---|---|---|
WS_PORT |
3000 |
WebSocket server port |
API_PORT |
8080 |
HTTP API server port |
API_KEY |
default-api-key-change-me |
REQUIRED: API key for authentication |
WebSocket API (Port 3000)
Connection
Connect to ws://localhost:3000 using Socket.IO client.
Events
Subscribe to a topic
socket.emit("subscribe", "user-notifications");
socket.on("subscribed", (data) => {
console.log(`Subscribed to ${data.topic}`);
});
Unsubscribe from a topic
socket.emit("unsubscribe", "user-notifications");
socket.on("unsubscribed", (data) => {
console.log(`Unsubscribed from ${data.topic}`);
});
Receive messages
socket.on("message", (data) => {
console.log(`Message from topic ${data.topic}:`, data.message);
console.log("Data:", data.data);
console.log("Timestamp:", data.timestamp);
});
Error handling
socket.on("error", (error) => {
console.error("Socket error:", error.message);
});
HTTP API (Port 8080)
Public Endpoints (No Authentication Required)
Health Check
GET /health
Response:
{
"status": "healthy",
"connections": 5,
"topics": 3,
"timestamp": "2024-01-01T12:00:00.000Z",
"apiKeyConfigured": true
}
List Topics
GET /topics
Response:
{
"topics": [
{
"topic": "user-notifications",
"subscribers": 2
},
{
"topic": "system-alerts",
"subscribers": 1
}
]
}
Get Topic Info
GET /topics/{topic}
Response:
{
"topic": "user-notifications",
"subscribers": 2
}
Protected Endpoints (API Key Required)
Response Codes
- 200 OK: Message published successfully to subscribers
- 204 No Content: Message published successfully but no subscribers for the topic
- 400 Bad Request: Invalid request (missing topic, message, or data)
- 401 Unauthorized: Invalid or missing API key
- 500 Internal Server Error: Server error
Publish Message
POST /publish
Authorization: Bearer your-api-key-here
Content-Type: application/json
{
"topic": "user-notifications",
"message": "New message received",
"data": { "userId": 123, "type": "email" }
}
Alternative endpoint:
POST /publish/{topic}
Authorization: Bearer your-api-key-here
Content-Type: application/json
{
"message": "New message received",
"data": { "userId": 123, "type": "email" }
}
Response:
{
"success": true,
"message": "Message published to topic 'user-notifications'",
"subscribers": 2,
"payload": {
"topic": "user-notifications",
"message": "New message received",
"data": { "userId": 123, "type": "email" },
"timestamp": "2024-01-01T12:00:00.000Z"
}
}
Note: If there are no subscribers for the topic, the API returns 204 No Content instead of an error. This indicates that the message was published successfully, but there were no recipients to receive it.
Authentication Errors
If you don't provide a valid API key, you'll get:
{
"error": "Unauthorized",
"message": "API key is required. Use Authorization header or x-api-key header or api_key query parameter."
}
Frontend Example (JavaScript)
<!DOCTYPE html>
<html>
<head>
<title>Socket Broker Client</title>
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
</head>
<body>
<h1>Socket Broker Client</h1>
<div id="status">Connecting...</div>
<div id="messages"></div>
<script>
const socket = io("http://localhost:3000");
socket.on("connect", () => {
document.getElementById("status").textContent = "Connected";
// Subscribe to a topic
socket.emit("subscribe", "user-notifications");
});
socket.on("subscribed", (data) => {
console.log("Subscribed:", data);
});
socket.on("message", (data) => {
const messagesDiv = document.getElementById("messages");
const messageElement = document.createElement("div");
messageElement.innerHTML = `
<strong>${data.topic}:</strong> ${data.message}
<br><small>${data.timestamp}</small>
`;
messagesDiv.appendChild(messageElement);
});
socket.on("disconnect", () => {
document.getElementById("status").textContent = "Disconnected";
});
</script>
</body>
</html>
Backend Example (Python)
import requests
import json
import os
# Get API key from environment variable
API_KEY = os.getenv('API_KEY', 'your-api-key-here')
def publish_message(topic, message, data=None):
url = "http://localhost:8080/publish"
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
payload = {
"topic": topic,
"message": message,
"data": data
}
response = requests.post(url, json=payload, headers=headers)
return response.json()
# Example usage
result = publish_message(
topic="user-notifications",
message="New email received",
data={"userId": 123, "emailId": "abc123"}
)
print(result)
Backend Example (Node.js)
const axios = require("axios");
const API_KEY = process.env.API_KEY || "your-api-key-here";
async function publishMessage(topic, message, data = null) {
try {
const response = await axios.post(
"http://localhost:8080/publish",
{
topic,
message,
data,
},
{
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
}
);
return response.data;
} catch (error) {
console.error(
"Error publishing message:",
error.response?.data || error.message
);
throw error;
}
}
// Example usage
publishMessage("user-notifications", "New notification", { userId: 123 })
.then((result) => console.log("Message published:", result))
.catch((error) => console.error("Failed to publish:", error));
Docker Compose Example
version: "3.8"
services:
socket-broker:
build: .
ports:
- "3000:3000"
- "8080:8080"
environment:
- NODE_ENV=production
- WS_PORT=3000
- API_PORT=8080
- API_KEY=${API_KEY:-default-api-key-change-me}
restart: unless-stopped
healthcheck:
test:
[
"CMD",
"node",
"-e",
"require('http').get('http://localhost:8080/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
env_file:
- .env
Monitoring
The application provides several monitoring endpoints:
/health- Overall health status and API key configuration status/topics- List of active topics and subscriber counts/topics/{topic}- Specific topic information
Security Best Practices
-
Generate Strong API Keys: Use cryptographically secure random strings
# Generate a secure API key openssl rand -base64 32 -
Environment Variables: Never hardcode API keys in your code
# Good export API_KEY="$(openssl rand -base64 32)" # Bad export API_KEY="my-secret-key-123" -
Rotate Keys Regularly: Change your API keys periodically
-
Limit Access: Only share API keys with trusted backend services
-
HTTPS in Production: Use HTTPS/WSS in production environments
-
Network Security: Consider using internal networks for backend communication
Troubleshooting
Common Issues
- 401 Unauthorized: Check that you're providing the correct API key
- Port conflicts: Ensure ports 3000 and 8080 are available
- CORS errors: Check that the frontend origin is allowed
- Connection refused: Verify the container is running and ports are exposed
Testing Authentication
Test your API key authentication:
# Test without API key (should fail with 401)
curl -X POST http://localhost:8080/publish \
-H "Content-Type: application/json" \
-d '{"topic":"test","message":"hello"}'
# Test with correct API key but no subscribers (should succeed with 204)
curl -X POST http://localhost:8080/publish \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{"topic":"test","message":"hello"}'
# Test with correct API key and subscribers (should succeed with 200)
curl -X POST http://localhost:8080/publish \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{"topic":"test","message":"hello"}'
Logs
Check application logs for detailed information:
docker logs socket-broker
Health Check
Verify the application is healthy and API key is configured:
curl http://localhost:8080/health