Plugins

Extend JustScale with custom transports and plugins

JustScale's plugin system allows you to extend the framework with custom route factories, transports, and integrations. The core framework is transport-agnostic - HTTP, gRPC, WebSockets, and other protocols are implemented as plugins.

Understanding the Plugin System

JustScale uses TypeScript's module augmentation and a registry-based approach to extend functionality. Plugins can:

  • Add new route factory methods (like Get, Post, etc.)
  • Extend the route context with transport-specific properties
  • Register as cluster transports for protocol-specific serving
  • Hook into the app lifecycle for initialization

Creating Custom Route Factories

Route factories provide the DSL for defining routes. The HTTP plugin provides Get, Post, etc. You can create your own.

Files
src/plugins/sse.tsTypeScript
import type { RouteHandler, RouteDef } from "@justscale/core/plugin";
import { registerRouteFactory } from "@justscale/core/plugin";
import type { HttpMethod } from "@justscale/core/plugin";

// Step 1: Augment the RouteFactories interface
declare module "@justscale/core/plugin" {
  interface RouteFactories<TDeps> {
    // Add your custom route factory method
    SSE(path: string): SseRouteBuilder<TDeps>;
  }
}

// Step 2: Create the factory implementation
function createSseFactory() {
  return function SSE<TDeps, TPath extends string>(
    path: TPath
  ): SseRouteBuilder<TDeps, TPath> {
    return {
      handle(handler) {
        return {
          method: "GET",
          path,
          middlewares: [],
          guards: [],
          handler: async (ctx) => {
            // Set up SSE headers
            ctx.res.setHeader("Content-Type", "text/event-stream");
            ctx.res.setHeader("Cache-Control", "no-cache");
            ctx.res.setHeader("Connection", "keep-alive");

            // Create event stream
            const stream = new EventStream(ctx.res);
            await handler({ ...ctx, stream });
          },
        };
      },
    };
  };
}

// Register the factory
registerRouteFactory("SSE", createSseFactory());

Extending Route Context

Plugins can add transport-specific properties to route handlers by augmenting the RouteContext interface.

http-context.tsTypeScript
import type { ServerResponse, IncomingMessage } from "http";

declare module "@justscale/core/plugin" {
  interface RouteContext<TDeps, TParams> {
    // Add HTTP-specific properties
    res: ServerResponse;
    req: IncomingMessage;
    // These are now available in all route handlers
  }
}

The HTTP plugin uses this to add res and req. Your handlers can then access these properties with full type safety.

Creating Cluster Transports

Cluster transports integrate with createCluster() to handle protocol-specific serving. The HTTP transport is a good example.

Files
src/plugins/grpc-transport.tsTypeScript
import { registerClusterTransport } from "@justscale/cluster";
import type { ClusterTransportPlugin } from "./types";

const GrpcTransport: ClusterTransportPlugin = {
  name: "grpc",

  beforeControllerResolution(container, controllers) {
    // Register gRPC-specific services
    // e.g., reflection service, interceptors
  },

  onAppCreated(app) {
    // Optional: post-creation setup
  },

  async serve(app, config) {
    const server = new GrpcServer();

    // Register routes from app.controllers
    for (const controller of app.controllers) {
      for (const route of controller.routes) {
        server.register(route);
      }
    }

    await server.listen(config.port);

    return {
      stop: async () => {
        await server.close();
      },
    };
  },
};

// Register the transport
registerClusterTransport("grpc", GrpcTransport);

Real-World Example: HTTP Plugin

The @justscale/http package is a complete plugin implementation. Here's how it's structured:

http-plugin.tsTypeScript
import { registerRouteFactory } from "@justscale/core/plugin";
import { registerClusterTransport } from "@justscale/cluster";
import { Get, Post, Put, Delete, Patch } from "./factory";
import { HttpTransport } from "./transport";

// 1. Augment route factories
declare module "@justscale/core/plugin" {
  interface RouteFactories<TDeps> {
    Get: HttpFactory<TDeps>;
    Post: HttpFactory<TDeps>;
    Put: HttpFactory<TDeps>;
    Delete: HttpFactory<TDeps>;
    Patch: HttpFactory<TDeps>;
  }
}

// 2. Augment route context
declare module "@justscale/core/plugin" {
  interface RouteContext<TDeps, TParams> {
    res: JsonResponse;
    req: IncomingMessage;
  }
}

// 3. Register factories
registerRouteFactory("Get", Get);
registerRouteFactory("Post", Post);
registerRouteFactory("Put", Put);
registerRouteFactory("Delete", Delete);
registerRouteFactory("Patch", Patch);

// 4. Register as cluster transport
registerClusterTransport("http", HttpTransport);

Plugin Best Practices

  • Use module augmentation for type safety - Always augment interfaces rather than using any
  • Register early - Import and register plugins before creating your app
  • Namespace your additions - Prefix custom context properties to avoid conflicts
  • Document your plugin - Provide clear examples and type definitions
  • Test in isolation - Use createStandaloneApp for plugin testing
ℹ️

Info

Pro Tip: Study the source code of @justscale/http and @justscale/datastar for complete plugin examples. They demonstrate route factories, context augmentation, and cluster transport integration.