Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ body:
- '@nestjs-redis/kit'
- '@nestjs-redis/client'
- '@nestjs-redis/lock'
- '@nestjs-redis/socket.io-adapter'
- '@nestjs-redis/throttler-storage'
- '@nestjs-redis/health-indicator'
validations:
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ body:
- '@nestjs-redis/kit'
- '@nestjs-redis/client'
- '@nestjs-redis/lock'
- '@nestjs-redis/socket.io-adapter'
- '@nestjs-redis/throttler-storage'
- '@nestjs-redis/health-indicator'
- 'New package'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./test-output
files: ./client/lcov.info,./health-indicator/lcov.info,./lock/lcov.info,./throttler-storage/lcov.info
files: ./client/lcov.info,./health-indicator/lcov.info,./lock/lcov.info,./throttler-storage/lcov.info,./socket.io-adapter/lcov.info
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/compatibility-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ jobs:
fail-fast: false
matrix:
node-version: ['18', '20', '22']
package: ['client', 'health-indicator', 'throttler-storage', 'lock']
package:
[
'client',
'health-indicator',
'throttler-storage',
'lock',
'socket.io-adapter',
]
nestjs-version: ['9', '10', '11']
include:
- nestjs-version: '9'
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ NestJS Redis Toolkit is a cohesive set of utilities for Redis in NestJS applicat
- [@nestjs-redis/lock](packages/lock/README.md) — Distributed locking via Redlock
- [@nestjs-redis/throttler-storage](packages/throttler-storage/README.md) — Redis storage for NestJS Throttler
- [@nestjs-redis/health-indicator](packages/health-indicator/README.md) — Redis health checks for Terminus
- [@nestjs-redis/socket.io-adapter](packages/socket.io-adapter/README.md) — Redis-powered Socket.IO adapter for scalable WebSocket connections

## Quick Start

Expand Down Expand Up @@ -94,9 +95,10 @@ import { RedisModule } from '@nestjs-redis/client';
| -------------------------------------------------------------------------------------------------- | ------- | ------ | ----- |
| [`@nestjs-redis/kit`](https://www.npmjs.com/package/@nestjs-redis/kit) | 18+ | 9+ | 5+ |
| [`@nestjs-redis/client`](https://www.npmjs.com/package/@nestjs-redis/client) | 18+ | 9+ | 5+ |
| [`@nestjs-redis/lock`](https://www.npmjs.com/package/@nestjs-redis/lock) | 18+ | 9+ | 5+ |
| [`@nestjs-redis/throttler-storage`](https://www.npmjs.com/package/@nestjs-redis/throttler-storage) | 18+ | 9+ | 5+ |
| [`@nestjs-redis/health-indicator`](https://www.npmjs.com/package/@nestjs-redis/health-indicator) | 18+ | 9+ | 5+ |
| [`@nestjs-redis/lock`](https://www.npmjs.com/package/@nestjs-redis/lock) | 18+ | 9+ | 5+ |
| [`@nestjs-redis/socket.io-adapter`](https://www.npmjs.com/package/@nestjs-redis/socket.io-adapter) | 18+ | 9+ | 5+ |

All packages support NestJS 9.x, 10.x, and 11.x.

Expand Down
3 changes: 2 additions & 1 deletion packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@
"@nestjs-redis/client": "workspace:*",
"@nestjs-redis/health-indicator": "workspace:*",
"@nestjs-redis/throttler-storage": "workspace:*",
"@nestjs-redis/lock": "workspace:*"
"@nestjs-redis/lock": "workspace:*",
"@nestjs-redis/socket.io-adapter": "workspace:*"
},
"engines": {
"node": ">=18.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from '@nestjs-redis/client';
export { RedisHealthIndicator } from '@nestjs-redis/health-indicator';
export * from '@nestjs-redis/throttler-storage';
export * from '@nestjs-redis/lock';
export * from '@nestjs-redis/socket.io-adapter';
22 changes: 22 additions & 0 deletions packages/socket.io-adapter/.spec.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"jsc": {
"target": "es2017",
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"decoratorMetadata": true,
"legacyDecorator": true
},
"keepClassNames": true,
"externalHelpers": true,
"loose": true
},
"module": {
"type": "es6"
},
"sourceMaps": true,
"exclude": []
}
174 changes: 174 additions & 0 deletions packages/socket.io-adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<div align="center">

<img src="https://raw.githubusercontent.com/CSenshi/nestjs-redis/main/docs/images/logo.png" alt="NestJS Redis Toolkit Logo" width="200" height="200">

# @nestjs-redis/socket.io-adapter

Redis-powered Socket.IO adapter for NestJS enabling scalable WebSocket connections across multiple instances.

[![npm version](https://badge.fury.io/js/%40nestjs-redis%2Fsocket.io-adapter.svg)](https://www.npmjs.com/package/@nestjs-redis/socket.io-adapter)
[![npm downloads](https://img.shields.io/npm/dm/@nestjs-redis/socket.io-adapter.svg)](https://www.npmjs.com/package/@nestjs-redis/socket.io-adapter)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
[![NestJS](https://img.shields.io/badge/NestJS-9%2B-red.svg)](https://nestjs.com/) [![Redis](https://img.shields.io/badge/Redis-5+-red.svg)](https://redis.io/)

</div>

---

## Features

- **Horizontal scaling**: Connect clients to any server instance
- **Redis pub/sub**: Automatic event distribution across instances
- **Lifecycle management**: Redis connections are managed automatically
- **Works with existing connections**: Integrates seamlessly with `@nestjs-redis/client`
- **Type-safe**: Full TypeScript support
- **Production-ready**: Built on the official Socket.IO Redis adapter

## Installation

```bash
npm install @nestjs-redis/socket.io-adapter
# or
yarn add @nestjs-redis/socket.io-adapter
# or
pnpm add @nestjs-redis/socket.io-adapter
```

## Quick Start

### Setup with existing Redis connection (Recommended)

```typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { RedisModule } from '@nestjs-redis/client';

@Module({
imports: [
RedisModule.forRoot({
options: { url: 'redis://localhost:6379' },
}),
],
})
export class AppModule {}
```

```typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { setupRedisAdapter } from '@nestjs-redis/socket.io-adapter';
import { AppModule } from './app.module';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

// Setup Redis adapter for Socket.IO
await setupRedisAdapter(app);

await app.listen(3000);
}
bootstrap();
```

### Multiple Redis connections

If you have multiple Redis connections, specify which one to use:

```typescript
// app.module.ts
@Module({
imports: [
RedisModule.forRoot({
options: { url: 'redis://localhost:6379' },
}),
RedisModule.forRoot({
connectionName: 'websockets',
options: { url: 'redis://websockets:6379' },
}),
],
})
export class AppModule {}
```

```typescript
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);

// Use the 'websockets' Redis connection
await setupRedisAdapter(app, 'websockets');

await app.listen(3000);
}
```

## The Problem

When scaling your NestJS application horizontally with multiple instances, WebSocket connections become a challenge. By default, Socket.IO connections are tied to a single server instance, which means:

- Events sent from one server instance won't reach clients connected to other instances
- Real-time features break when users connect to different servers
- Load balancing becomes complex as you need sticky sessions

## The Solution

This package provides a Redis-backed Socket.IO adapter that uses Redis pub/sub to synchronize events across all server instances. When a server emits an event, it's published to Redis and distributed to all other server instances, ensuring all clients receive the event regardless of which server they're connected to.

## How It Works

1. **Redis Pub/Sub**: The adapter creates two Redis connections - one for publishing and one for subscribing
2. **Event Distribution**: When a server emits an event, it's published to a Redis channel
3. **Cross-Instance Delivery**: All server instances subscribe to the same channels and forward events to their connected clients
4. **Automatic Management**: Connection lifecycle is handled automatically by the adapter

## API

### `setupRedisAdapter(app, redisToken?)`

Sets up the Redis adapter for the NestJS application.

- `app`: NestJS application instance
- `redisToken` (optional): Redis connection name (defaults to the default connection)

### `RedisIoAdapter`

The underlying Socket.IO adapter class that handles Redis connections.

## Architecture

```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Server 1 │ │ Server 2 │ │ Server 3 │
│ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────┐ │
│ │Client │ │ │ │Client │ │ │ │Client │ │
│ └───────┘ │ │ └───────┘ │ │ └───────┘ │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└──────────────────┼──────────────────┘
┌─────▼─────┐
│ Redis │
│ Pub/Sub │
└───────────┘
```

## Learn More

- [NestJS WebSocket Adapter Documentation](https://docs.nestjs.com/websockets/adapter)
- [Socket.IO Redis Adapter](https://socket.io/docs/v4/redis-adapter/)
- [Redis Pub/Sub](https://redis.io/docs/manual/pubsub/)

## Links

- Root repo: [CSenshi/nestjs-redis](https://github.com/CSenshi/nestjs-redis)
- Issues: [GitHub Issues](https://github.com/CSenshi/nestjs-redis/issues)
- Discussions: [GitHub Discussions](https://github.com/CSenshi/nestjs-redis/discussions)

## Contributing

Please see the [root contributing guidelines](https://github.com/CSenshi/nestjs-redis#contributing).

## License

MIT © [CSenshi](https://github.com/CSenshi)
22 changes: 22 additions & 0 deletions packages/socket.io-adapter/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import baseConfig from '../../eslint.config.mjs';

export default [
...baseConfig,
{
files: ['**/*.json'],
rules: {
'@nx/dependency-checks': [
'error',
{
ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}'],
ignoredDependencies: [
'redis', // redis is imported only for typing via import type statement
],
},
],
},
languageOptions: {
parser: await import('jsonc-eslint-parser'),
},
},
];
21 changes: 21 additions & 0 deletions packages/socket.io-adapter/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* eslint-disable */
import { readFileSync } from 'fs';

// Reading the SWC compilation config for the spec files
const swcJestConfig = JSON.parse(
readFileSync(`${__dirname}/.spec.swcrc`, 'utf-8'),
);

// Disable .swcrc look-up by SWC core because we're passing in swcJestConfig ourselves
swcJestConfig.swcrc = false;

export default {
displayName: 'socket.io-adapter',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['@swc/jest', swcJestConfig],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: 'test-output/jest/coverage',
};
80 changes: 80 additions & 0 deletions packages/socket.io-adapter/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"name": "@nestjs-redis/socket.io-adapter",
"version": "0.0.1",
"license": "MIT",
"author": "Saba Pochkhua <saba.pochkhua@gmail.com> (https://github.com/CSenshi)",
"description": "Redis-powered Socket.IO adapter for NestJS enabling scalable WebSocket connections",
"keywords": [
"nestjs",
"redis",
"socket.io",
"websockets",
"adapter",
"scalable",
"typescript",
"node-redis"
],
"homepage": "https://github.com/CSenshi/nestjs-redis/tree/main/packages/socket.io-adapter",
"main": "./src/index.js",
"module": "./src/index.js",
"types": "./src/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": "./src/index.ts",
"types": "./src/index.d.ts",
"import": "./src/index.js",
"default": "./src/index.js"
}
},
"files": [
"src",
"!**/*.tsbuildinfo"
],
"nx": {
"name": "socket.io-adapter",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/packages/socket.io-adapter",
"tsConfig": "packages/socket.io-adapter/tsconfig.lib.json",
"packageJson": "packages/socket.io-adapter/package.json",
"main": "packages/socket.io-adapter/src/index.ts",
"assets": [
"packages/socket.io-adapter/*.md",
"LICENSE"
]
}
}
}
},
"dependencies": {
"@nestjs/platform-socket.io": "^11.1.6",
"@socket.io/redis-adapter": "^8.3.0",
"tslib": "^2.8.0"
},
"devDependencies": {
"@nestjs-redis/client": "0.10.1",
"@nestjs/testing": "^11.0.0",
"@nestjs/websockets": "^11.1.6",
"redis": "^5.0.0",
"socket.io-client": "^4.0.0"
},
"peerDependencies": {
"@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0",
"@nestjs/core": "^9.0.0 || ^10.0.0 || ^11.0.0"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/CSenshi/nestjs-redis",
"directory": "packages/socket.io-adapter"
}
}
Loading