npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dimski/socketeer

v0.2.0

Published

> [!CAUTION] > **BETA VERSION**: Interfaces may change, and errors may occur. Use with caution.

Readme

Socketeer 🚀

[!CAUTION] BETA VERSION: Interfaces may change, and errors may occur. Use with caution.

Modern, structured WebSocket framework for Node.js

Interactive Course

Socketeer is a TypeScript-first framework designed for building scalable and maintainable WebSocket applications. It enforces an explicit architecture without the magic of decorators, giving you full control over your application's flow.

Philosophy

  • Explicit over Implicit: No hidden magic. Everything is defined in your route configuration.
  • No Decorators: Pure TypeScript classes and objects. easier to debug and test.
  • Singleton Controllers: Controllers are singletons per route, perfect for stateful logic (like chat rooms).
  • Lifecycle Hooks: Granular control over every stage of a connection's life.

Community & Support

Key Features

  • 🏗️ Structured Architecture: Clear separation of concerns (Server, Routes, Controllers).
  • 🛠️ Pipes: Transform and validate data before it reaches your controller.
  • 🛡️ Guards: Secure your endpoints with authentication logic.
  • 🚦 Exception Filters: Centralized error handling.
  • 🔌 Context Awareness: Access ws instance, request data, and custom session state easily.

Get Started

This example demonstrates how to create a simple chat endpoint with parameter support.

🎓 Want a step-by-step guide? Check out the Interactive Course!

1. Installation

npm install @dimski/socketeer

2. Define a Controller

The controller handles the business logic. It implements lifecycle interfaces to react to events.

// src/EndpointController.ts
import { 
  Controller, 
  OnSocketInit, 
  OnSocketConnect, 
  OnSocketMessage, 
  OnSocketError, 
  OnSocketClose, 
  OnSocketDestroy 
} from '@dimski/socketeer';
import { SocketContext } from '@dimski/socketeer';

export class EndpointController extends Controller
  implements OnSocketInit, OnSocketConnect, OnSocketMessage, OnSocketError, OnSocketClose, OnSocketDestroy {

  // Called ONCE when the first client connects to this route
  async $onSocketInit() {
    const params = this.$getParams();
    console.log('Controller initialized with params:', params); // e.g., { id: '1' }
  }

  // Called for EVERY new connection
  async $onSocketConnect(context: SocketContext<any>) {
    console.log('New connection established');
    
    // Broadcast to all connected clients in this controller
    this.$forEachContext((ctx) => {
      // ctx.socket is the native WebSocket
    });
  }

  // Handle incoming messages
  async $onSocketMessage(message: unknown, context: SocketContext<any>) {
    console.log('Received:', message);
    
    // Send response to sender
    this.$send(context, { status: 'ok' }); 
    
    // Broadcast to everyone else
    this.$sendBroadcastMessage(message); 
  }

  async $onSocketError(err: Error, context: SocketContext<unknown>) {
    console.error('Socket error:', err);
  }

  async $onSocketClose(code: number, reason: string | Buffer, context: SocketContext<unknown>) {
    console.log('Client disconnected:', code);
  }

  // Called when the LAST client disconnects
  async $onSocketDestroy() {
    console.log('Controller destroyed - no more active clients');
  }
}

3. Configure Routes

The route definition links a URL path to a controller and configures pipes.

// src/routes.ts
import { Route, BufferToStringPipe, JsonParsePipe, JsonStringifyPipe } from '@dimski/socketeer';
import { EndpointController } from './EndpointController';

export const routes: Route[] = [
  {
    path: '/chat/:id', // Supports path parameters
    controller: EndpointController,
    // Pipes to process incoming messages (Buffer -> String -> JSON)
    requestMessagePipes: [
      new BufferToStringPipe(),
      new JsonParsePipe(),
    ],
    // Pipes to process outgoing messages (Object -> JSON String)
    responseMessagePipes: [
      new JsonStringifyPipe(),
    ],
  }
];

4. Start the Server

Initialize the Socketeer instance with your configuration.

// src/main.ts
import { Socketeer } from '@dimski/socketeer';
import { routes } from './routes';

const server = new Socketeer({
  port: 3200,
  routes,
  onInit: () => {
    console.log('==============================');
    console.log('Socketeer running on port 3200');
    console.log('==============================');
  }
});

Documentation


Server (Socketeer)

The Socketeer class is the main entry point of the application. It initializes the WebSocket server and manages the routing of incoming connections.

Configuration (SocketeerConfig)

| Property | Type | Description | |----------|------|-------------| | port | number | The port on which the WebSocket server will listen. | | routes | Route[] | An array of route definitions. | | onInit | () => void | Lifecycle hook called when the server successfully starts. | | onConnect | () => void | Lifecycle hook called when a new client connects (global). | | connectGuards | CanActivateConnect[] | Global guards that run for every connection attempt. | | prefixPath | string | Optional prefix for all routes (e.g., /ws). | | errorFilter | ErrorFilter | Custom global error handler. | | rateLimit | RateLimitConfig | Global rate limiting configuration. |

Helper Methods

  • notifyPath(path: string, data: T): Sends an internal event to a specific path. This is useful for inter-controller communication without a direct reference to the controller instance.

Rate Limit Configuration

The RateLimitConfig object defines the rules for rate limiting connections and requests.

| Property | Type | Description | |----------|------|-------------| | maxConnections | number | Maximum number of concurrent connections allowed to the server or specific route. | | maxRequests | object | Configuration for request rate limiting. | | maxRequests.window | number | The time window in milliseconds (e.g., 1000 for 1 second). | | maxRequests.counter | number | The maximum number of requests allowed within the window. |

rateLimit: {
  maxConnections: 100, // Max 100 concurrent users
  maxRequests: {
    window: 1000, // 1 second
    counter: 5    // Max 5 messages per second
  }
}

Routes

Routes define how URL paths map to Controllers. Socketeer supports parameterized paths, similar to Express or NestJS.

Configuration (Route)

| Property | Type | Description | |----------|------|-------------| | path | string | The URL path (e.g., /chat, /room/:roomId). | | controller | Controller Class | The class definition of the controller to handle this route. | | connectGuards | CanActivateConnect[] | Guards specific to this route. | | requestMessagePipes | PipeTransform[] | Pipes that process incoming messages before they reach the controller. | | responseMessagePipes | PipeTransform[] | Pipes that process outgoing messages before they are sent to the client. | | rateLimit | RateLimitConfig | Rate limiting specific to this route (overrides global). |

Path Parameters

Parameters are defined with a colon :. They are accessible inside the controller via this.$getParams().

{
  path: '/channel/:channelId/user/:userId',
  controller: ChannelController
}

In the controller:

const { channelId, userId } = this.$getParams();

Controllers (Business Logic)

Controllers are the heart of your application. They handle incoming messages, manage connection state, and execute business logic.

Lifecycle Hooks

Socketeer controllers have a rich lifecycle. Implementing these interfaces allows you to tap into key events.

| Hook | Interface | Description | |------|-----------|-------------| | $onSocketInit | OnSocketInit | Called once when the first client connects to this route. Use for setting up shared resources. | | $onSocketConnect | OnSocketConnect | Called for each new connection. | | $onSocketMessage | OnSocketMessage | Called when a client sends a message. | | $onSocketError | OnSocketError | Called on socket errors. | | $onSocketClose | OnSocketClose | Called when a client disconnects. | | $onSocketDestroy | OnSocketDestroy | Called when the last client disconnects. Use for cleanup. |

API Methods

The Controller base class provides several protected methods to manage your application.

  • this.$getParams(): Returns route parameters (e.g., :id).
  • this.$getQueryParams(): Returns URL query parameters.
  • this.$send(context, data): Sends a transformed message to a specific client.
  • this.$sendBroadcastMessage(data): Sends a message to all clients currently connected to this controller instance.
  • this.$forEachContext(callback): Iterates over all connected clients.
  • this.$findContext(callback): Finds a specific client context.
  • this.$notifyPath(path, data): Sends data to another controller via the internal event bus.
  • this.$subscribePathNotifications(callback): Listens for internal events.

Guards (Authentication)

Guards are used to determine whether a connection should be allowed. They are executed before the connection is fully established and before the controller's $onSocketConnect hook.

Implementing a Guard

A guard must implement the CanActivateConnect interface.

import { CanActivateConnect, SocketContext } from '@dimski/socketeer';

export class AuthGuard implements CanActivateConnect {
  
  canActivate(context: SocketContext): boolean {
    const token = context.queryParams['token'];
    
    if (validateToken(token)) {
      return true;
    }
    
    return false; // Connection will be rejected with AccessDeniedException
  }
}

Global vs Route Guards

  • Global Guards: Defined in SocketeerConfig.connectGuards. Run for every connection.
  • Route Guards: Defined in Route.connectGuards. Run only for that specific route.

Pipes (Transformation & Validation)

Pipes transform input data to a desired output. They can also be used for validation, throwing an exception if the data is incorrect.

Built-in Pipes

Socketeer comes with several built-in pipes:

| Pipe | Description | |------|-------------| | BufferToStringPipe | Converts incoming Buffer to string. | | JsonParsePipe | Parses a JSON string into an object. | | JsonStringifyPipe | Stringifies an object into a JSON string. |

Creating a Custom Pipe

Implement the PipeTransform interface.

import { PipeTransform, SocketContext, BadRequestException } from '@dimski/socketeer';

export class ToUpperCasePipe implements PipeTransform {
  transform(value: any, context: SocketContext): string {
    if (typeof value !== 'string') {
      throw new BadRequestException('Expected string!');
    }
    return value.toUpperCase();
  }
}

Usage

Pipes can be applied to:

  • Incoming Messages (requestMessagePipes): Transform what the client sends before it reaches $onSocketMessage.
  • Outgoing Messages (responseMessagePipes): Transform what you send via $send before it reaches the client.

Exception Filters

Exception filters handle errors thrown during the application lifecycle (in guards, pipes, or controllers).

Default Behavior

By default, Socketeer uses a built-in ErrorFilter that:

  1. Checks if the error is an instance of SocketeerException.
  2. If yes, it closes the connection with the exception's code and message.
  3. If no, it logs the error to the console (and keeps the connection open, usually).

Custom Exception Filter

You can provide a custom filter in SocketeerConfig.

import { ErrorFilter } from '@dimski/socketeer';
import * as WebSocket from 'ws';

export class MyErrorFilter extends ErrorFilter {
  handleError(err: unknown, ws?: WebSocket) {
    // Custom logging logic
    console.error('Custom Error Handler:', err);
    
    // Call default behavior if needed
    super.handleError(err, ws);
  }
}

Context (SocketContext)

The SocketContext wrapper provides access to the underlying WebSocket connection and request data.

| Property | Type | Description | |----------|------|-------------| | socket | WebSocket | The native ws WebSocket instance. | | request | IncomingMessage | The initial HTTP request that established the connection. | | payload | T | A generic property to store session data (e.g., user profile). |

// Storing data in context during connection
context.payload = { userId: 123, role: 'admin' };

// Accessing it later
const user = context.payload;

Exceptions & Response Codes

Socketeer uses standard exception classes that map to specific close codes.

| Exception | Close Code | Description | |-----------|------------|-------------| | BadRequestException | 4400 | Invalid data sent by client. | | UnauthorizedException | 4401 | Authentication required. | | AccessDeniedException | 4403 | Authentication passed, but permission denied. | | NotFoundException | 4404 | Route not found. | | RateLimitException | 4429 | Too many requests. | | InternalServerErrorException | 4500 | Generic server error. | | BadGatewayException | 4502 | Upstream error. | | ServiceUnavailableException | 4503 | Server overloaded or maintenance. |

You can throw these exceptions anywhere in your application (Guards, Pipes, Controllers).

throw new NotFoundException('Chat room does not exist');

Notifications (Internal Event Bus)

The Notifier system allows controllers to communicate with each other without direct coupling. This is useful for system-wide events like "User X came online" or "System maintenance starting".

Publishing an Event

From a controller:

this.$notifyPath('/system/alerts', { type: 'maintenance', time: '10m' });

From the server instance:

socketeer.notifyPath('/chat/general', { type: 'announcement', text: 'Hello!' });

Subscribing to Events

In a controller, you can listen for events targeting its path.

this.$subscribePathNotifications((event) => {
  if (event.type === 'maintenance') {
     this.$sendBroadcastMessage({ systemParam: 'shutdown' });
  }
});

🚀 Ready to dive deeper? Take the Interactive Course!