Deep Dive: Components
Technical reference for BaseComponent—the foundation for creating reusable, pluggable features in Ignis. Components are powerful containers that can group together multiple providers, services, controllers, repositories, and even entire mini-applications into a single, redistributable module.
File: packages/core/src/base/components/base.ts
Quick Reference
| Feature | Benefit |
|---|---|
| Encapsulation | Bundle feature bindings (services, controllers) into single class |
| Lifecycle Management | Auto-called binding() method during startup |
| Default Bindings | Self-contained with automatic DI registration |
| Idempotent Configure | configure() is safe to call multiple times — runs binding() only once |
| Controller Transports | RestComponent and GrpcComponent handle controller discovery per transport |
Component Directory Structure
A well-organized component follows a consistent directory structure that separates concerns and makes the codebase maintainable.
Simple Component
src/components/health-check/
├── index.ts # Barrel exports (re-exports everything)
├── component.ts # Component class with binding logic
├── controller.ts # Controller class(es)
└── common/
├── index.ts # Barrel exports for common/
├── keys.ts # Binding key constants
├── types.ts # Interfaces and type definitions
├── constants.ts # Static class constants (optional)
└── rest-paths.ts # Route path constants (optional)Complex Component (with services, models, strategies)
src/components/auth/
├── index.ts
├── authenticate/
│ ├── index.ts
│ ├── component.ts
│ ├── common/
│ │ ├── index.ts
│ │ ├── keys.ts
│ │ ├── types.ts
│ │ └── constants.ts
│ ├── controllers/
│ │ ├── index.ts
│ │ └── auth.controller.ts
│ ├── services/
│ │ ├── index.ts
│ │ └── jwt-token.service.ts
│ └── strategies/
│ ├── index.ts
│ ├── jwt.strategy.ts
│ └── basic.strategy.ts
└── models/
├── index.ts
├── entities/
│ └── user-token.model.ts
└── requests/
├── sign-in.schema.ts
└── sign-up.schema.tsController Transport Component
Transport components live under src/components/controller/ and are instantiated directly by the application during registerControllers() — they are not registered via this.component().
src/components/controller/
├── index.ts # Barrel re-exports (REST only; gRPC excluded from barrel)
├── rest/
│ ├── rest.component.ts
│ └── common/
│ └── types.ts # IRestComponentConfig, RestBindingKeys
└── grpc/
├── grpc.component.ts
└── common/
└── types.ts # IGrpcComponentConfig, GrpcBindingKeysThe common/ Directory
The common/ directory contains shared definitions that are used throughout the component. Every component should have this directory with at least keys.ts and types.ts.
1. Binding Keys (keys.ts)
Binding keys are string constants used to register and retrieve values from the DI container. They follow the pattern @app/[component]/[feature].
// src/components/health-check/common/keys.ts
export class HealthCheckBindingKeys {
static readonly HEALTH_CHECK_OPTIONS = '@app/health-check/options';
}For components with multiple features:
// src/components/auth/authenticate/common/keys.ts
export class AuthenticateBindingKeys {
static readonly AUTHENTICATE_OPTIONS = '@app/authenticate/options';
static readonly JWT_OPTIONS = '@app/authenticate/jwt/options';
}Naming Convention:
- Class name:
[Feature]BindingKeys - Key format:
@app/[component]/[feature]or@app/[component]/[sub-feature]/[name]
2. Types (types.ts)
Define all interfaces and type aliases that the component exposes or uses internally.
// src/components/health-check/common/types.ts
export interface IHealthCheckOptions {
restOptions: { path: string };
}For complex components with service interfaces:
// src/components/auth/authenticate/common/types.ts
import { Context } from 'hono';
import { AnyObject, ValueOrPromise } from '@venizia/ignis-helpers';
// Options interface for the component
export interface IAuthenticateOptions {
jwtOptions?: IJWTTokenServiceOptions;
basicOptions?: IBasicTokenServiceOptions;
restOptions?: {
useAuthController?: boolean;
controllerOpts?: TDefineAuthControllerOpts;
};
}
// Service options interface
export interface IJWTTokenServiceOptions {
jwtSecret: string;
applicationSecret: string;
getTokenExpiresFn: () => ValueOrPromise<number>;
}
// Service contract interface
export interface IAuthService<
SIRQ = AnyObject,
SIRS = AnyObject,
> {
signIn(context: Context, opts: SIRQ): Promise<SIRS>;
signUp(context: Context, opts: SIRQ): Promise<SIRS>;
}
// Auth user type
export interface IAuthUser {
userId: string;
[extra: string | symbol]: any;
}Naming Conventions:
- Interfaces:
Iprefix (e.g.,IHealthCheckOptions,IAuthService) - Type aliases:
Tprefix (e.g.,TDefineAuthControllerOpts)
3. Constants (constants.ts)
Use static classes (not enums) for constants that need type extraction and validation.
// src/components/auth/authenticate/common/constants.ts
export class Authentication {
// Strategy identifiers
static readonly STRATEGY_BASIC = 'basic';
static readonly STRATEGY_JWT = 'jwt';
// Token types
static readonly TYPE_BASIC = 'Basic';
static readonly TYPE_BEARER = 'Bearer';
// Context keys
static readonly CURRENT_USER = 'auth.current.user';
static readonly SKIP_AUTHENTICATION = 'authentication.skip';
}With validation (for user-configurable values):
// src/components/swagger/common/constants.ts
import { TConstValue } from '@venizia/ignis-helpers';
export class DocumentUITypes {
static readonly SWAGGER = 'swagger';
static readonly SCALAR = 'scalar';
// Set for O(1) validation
static readonly SCHEME_SET = new Set([this.SWAGGER, this.SCALAR]);
// Validation helper
static isValid(value: string): boolean {
return this.SCHEME_SET.has(value);
}
}
// Extract union type: 'swagger' | 'scalar'
export type TDocumentUIType = TConstValue<typeof DocumentUITypes>;4. REST Paths (rest-paths.ts)
Define route path constants for controllers.
// src/components/health-check/common/rest-paths.ts
export class HealthCheckRestPaths {
static readonly ROOT = '/';
static readonly PING = '/ping';
static readonly METRICS = '/metrics';
}5. Barrel Exports (index.ts)
Every folder should have an index.ts that re-exports its contents:
// src/components/health-check/common/index.ts
export * from './keys';
export * from './rest-paths';
export * from './types';
// src/components/health-check/index.ts
export * from './common';
export * from './component';
export * from './controller';BaseComponent Class
Abstract class for all components - structures resource binding and lifecycle management.
Class Signature
abstract class BaseComponent<ConfigurableOptions extends object = {}>
extends BaseHelper
implements IConfigurable<ConfigurableOptions>BaseComponent extends BaseHelper (scoped logging via this.logger) and implements IConfigurable (the configure() method).
Constructor Options
The super() constructor in your component can take the following options:
| Option | Type | Description |
|---|---|---|
scope | string | Required. A unique name for the component, typically MyComponent.name. Used for logging. |
initDefault | { enable: false } | { enable: true; container: Container } | If enable is true, the bindings defined below will be automatically registered with the provided container (usually the application instance) when configure() is called — but only if they are not already bound. Defaults to { enable: false }. |
bindings | Record<string | symbol, Binding> | An object where keys are binding keys and values are Binding instances. These are the default services, values, or providers that your component offers. Defaults to {}. |
Properties
| Property | Type | Access | Description |
|---|---|---|---|
bindings | Record<string | symbol, Binding> | protected | Default bindings the component provides. Can be set in constructor or assigned directly in the constructor body. |
initDefault | TInitDefault | protected | Controls whether default bindings are auto-registered to a container. |
isConfigured | boolean | protected | Guard flag — prevents configure() from running more than once. |
Methods
| Method | Signature | Description |
|---|---|---|
binding() | abstract binding(): ValueOrPromise<void> | Abstract. Override this to register services, controllers, and other resources. Called by configure(). |
configure(opts?) | async configure(opts?: ConfigurableOptions): Promise<void> | Entry point. Calls initDefaultBindings() (if enabled), then binding(). Idempotent — skips if already configured. |
initDefaultBindings(opts) | protected initDefaultBindings(opts: { container: Container }): void | Iterates this.bindings and registers each into the container if not already bound. |
Lifecycle Flow
- Application Instantiates Component: When you call
this.component(MyComponent)in your application, the DI container creates an instance of your component. - Constructor Runs: Your component's constructor calls
super(), setting up its scope and defining its defaultbindings. - Application Calls
configure(): During theregisterComponentsphase, the application resolves each component and callsconfigure(). configure()Executes: ChecksisConfiguredguard (idempotent). IfinitDefault.enableistrue, registers default bindings into the container. Then callsbinding().binding()Runs: Your override registers controllers, services, reads options from the container, and performs any additional setup.
Controller Transport Components
Controller transport components are a special category. Unlike regular components registered via this.component(), transport components are instantiated and configured directly by BaseApplication.registerControllers(). They are responsible for discovering controller bindings and mounting them onto the application router.
Transport Configuration
The application config transports controls which transports are enabled. Defaults to ['rest'].
// In your application constructor
super({
scope: MyApplication.name,
config: {
// ...
transports: [ControllerTransports.REST, ControllerTransports.GRPC],
},
});The ControllerTransports constants:
export class ControllerTransports {
static readonly REST = 'rest';
static readonly GRPC = 'grpc';
}How registerControllers() Works
During the application lifecycle, registerControllers() iterates the configured transports and creates the corresponding component:
// Simplified from BaseApplication.registerControllers()
const transports = this.configs.transports ?? [ControllerTransports.REST];
for (const transport of transports) {
switch (transport) {
case ControllerTransports.REST: {
const restComponent = new RestComponent(this);
await restComponent.configure();
break;
}
case ControllerTransports.GRPC: {
const grpcComponent = new GrpcComponent(this);
await grpcComponent.configure();
break;
}
}
}If gRPC controllers are discovered but the 'grpc' transport is not in the transports config, the application logs an error warning for each one.
RestComponent
File: packages/core/src/components/controller/rest/rest.component.ts
Discovers all controller bindings tagged with BindingNamespaces.CONTROLLER, skips any whose metadata has transport === ControllerTransports.GRPC, and configures the rest as REST controllers.
export class RestComponent extends BaseComponent {
constructor(private application: BaseApplication) {
super({
scope: RestComponent.name,
initDefault: { enable: true, container: application },
bindings: {
[RestBindingKeys.REST_COMPONENT_OPTIONS]: Binding.bind<IRestComponentConfig>({
key: RestBindingKeys.REST_COMPONENT_OPTIONS,
}).toValue(DEFAULT_OPTIONS),
},
});
}
override async binding(): Promise<void> { /* ... */ }
}Binding loop:
- Fetches all controller bindings not yet configured (tracked via a
Set<string>). - For each binding, reads controller metadata from
MetadataRegistry. - Skips bindings with
transport === ControllerTransports.GRPC. - Validates that
metadata.pathis present (throws if missing). - Resolves the controller instance from the DI container, calls
instance.configure(), and mounts it:router.route(metadata.path, instance.getRouter()). - Re-fetches bindings after each configure to pick up dynamically added controllers.
Config types:
export interface IRestComponentConfig {}
export class RestBindingKeys {
static readonly REST_COMPONENT_OPTIONS = '@app/rest/options';
}GrpcComponent
File: packages/core/src/components/controller/grpc/grpc.component.ts
Discovers all controller bindings tagged with BindingNamespaces.CONTROLLER, skips any whose metadata does not have transport === ControllerTransports.GRPC, and configures gRPC controllers.
export class GrpcComponent extends BaseComponent {
constructor(private application: BaseApplication) {
super({
scope: GrpcComponent.name,
initDefault: { enable: true, container: application },
bindings: {
[GrpcBindingKeys.GRPC_COMPONENT_OPTIONS]: Binding.bind<IGrpcComponentConfig>({
key: GrpcBindingKeys.GRPC_COMPONENT_OPTIONS,
}).toValue(DEFAULT_OPTIONS),
},
});
}
override async binding(): Promise<void> { /* ... */ }
}Binding loop (same dynamic re-fetch pattern as RestComponent):
- Fetches all controller bindings not yet configured.
- Skips bindings with
transport !== ControllerTransports.GRPC. - Validates
metadata.pathis present. - Resolves the gRPC controller instance. Skips if the instance has no
servicedefinition (logs a warning). - Sets
instance.basePathfrom the application's project configs. - Calls
instance.configure()and mounts:router.route(metadata.path, instance.getRouter()). - Re-fetches bindings to pick up dynamically added controllers.
Config types:
export interface IGrpcComponentConfig {
interceptors?: unknown[];
}
export class GrpcBindingKeys {
static readonly GRPC_COMPONENT_OPTIONS = '@app/grpc/options';
}Note:
GrpcComponentis excluded from the barrel export atsrc/components/controller/index.ts. Import it directly from@venizia/ignis/components/controller/grpcif needed.
Component Implementation Patterns
Basic Component
// src/components/health-check/component.ts
import { BaseApplication, BaseComponent, inject, CoreBindings, Binding, ValueOrPromise } from '@venizia/ignis';
import { HealthCheckBindingKeys, IHealthCheckOptions } from './common';
import { HealthCheckController } from './controller';
// 1. Define default options
const DEFAULT_OPTIONS: IHealthCheckOptions = {
restOptions: { path: '/health' },
};
export class HealthCheckComponent extends BaseComponent {
constructor(
// 2. Inject the application instance
@inject({ key: CoreBindings.APPLICATION_INSTANCE })
private application: BaseApplication,
) {
super({
scope: HealthCheckComponent.name,
// 3. Enable automatic binding registration
initDefault: { enable: true, container: application },
// 4. Define default bindings
bindings: {
[HealthCheckBindingKeys.HEALTH_CHECK_OPTIONS]: Binding.bind<IHealthCheckOptions>({
key: HealthCheckBindingKeys.HEALTH_CHECK_OPTIONS,
}).toValue(DEFAULT_OPTIONS),
},
});
}
// 5. Configure resources in binding()
override binding(): ValueOrPromise<void> {
// Read options (may have been overridden by user)
const healthOptions = this.application.get<IHealthCheckOptions>({
key: HealthCheckBindingKeys.HEALTH_CHECK_OPTIONS,
isOptional: true,
}) ?? DEFAULT_OPTIONS;
// Register controller with dynamic path
Reflect.decorate(
[controller({ path: healthOptions.restOptions.path })],
HealthCheckController,
);
this.application.controller(HealthCheckController);
}
}Component with Services
// src/components/auth/authenticate/component.ts
import { BaseApplication, BaseComponent, inject, CoreBindings, Binding, ValueOrPromise } from '@venizia/ignis';
import { getError } from '@venizia/ignis-helpers';
import { AuthenticateBindingKeys, IAuthenticateOptions, IBasicTokenServiceOptions, IJWTTokenServiceOptions } from './common';
import { BasicTokenService, JWTTokenService } from './services';
import { defineAuthController } from './controllers';
const DEFAULT_OPTIONS: IAuthenticateOptions = {
restOptions: {
useAuthController: false,
},
};
export class AuthenticateComponent extends BaseComponent {
constructor(
@inject({ key: CoreBindings.APPLICATION_INSTANCE })
private application: BaseApplication,
) {
super({
scope: AuthenticateComponent.name,
initDefault: { enable: true, container: application },
bindings: {
[AuthenticateBindingKeys.AUTHENTICATE_OPTIONS]: Binding.bind<IAuthenticateOptions>({
key: AuthenticateBindingKeys.AUTHENTICATE_OPTIONS,
}).toValue(DEFAULT_OPTIONS),
},
});
}
// Validate at least one auth option is provided
private validateOptions(opts: IAuthenticateOptions): void {
if (!opts.jwtOptions && !opts.basicOptions) {
throw getError({
message: '[AuthenticateComponent] At least one of jwtOptions or basicOptions must be provided',
});
}
}
// Configure JWT authentication if jwtOptions is provided
private defineJWTAuth(opts: IAuthenticateOptions): void {
if (!opts.jwtOptions) return;
this.application
.bind<IJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS })
.toValue(opts.jwtOptions);
this.application.service(JWTTokenService);
}
// Configure Basic authentication if basicOptions is provided
private defineBasicAuth(opts: IAuthenticateOptions): void {
if (!opts.basicOptions) return;
this.application
.bind<IBasicTokenServiceOptions>({ key: AuthenticateBindingKeys.BASIC_OPTIONS })
.toValue(opts.basicOptions);
this.application.service(BasicTokenService);
}
// Configure auth controllers if enabled
private defineControllers(opts: IAuthenticateOptions): void {
if (!opts.restOptions?.useAuthController) return;
// Auth controller requires JWT for token generation
if (!opts.jwtOptions) {
throw getError({
message: '[defineControllers] Auth controller requires jwtOptions to be configured',
});
}
this.application.controller(defineAuthController(opts.restOptions.controllerOpts));
}
override binding(): ValueOrPromise<void> {
const options = this.application.get<IAuthenticateOptions>({
key: AuthenticateBindingKeys.AUTHENTICATE_OPTIONS,
});
this.validateOptions(options);
this.defineJWTAuth(options);
this.defineBasicAuth(options);
this.defineControllers(options);
}
}Component with Factory Controllers
When controllers need to be dynamically configured:
// src/components/static-asset/component.ts
override binding(): ValueOrPromise<void> {
const componentOptions = this.application.get<TStaticAssetsComponentOptions>({
key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
});
// Create multiple controllers from configuration
for (const [key, opt] of Object.entries(componentOptions)) {
this.application.controller(
AssetControllerFactory.defineAssetController({
controller: opt.controller,
storage: opt.storage,
helper: opt.helper,
}),
);
this.application.logger.info(
'[binding] Asset storage bound | Key: %s | Type: %s',
key,
opt.storage,
);
}
}Component without initDefault (Manual Binding Assignment)
Some components skip initDefault and assign this.bindings directly in the constructor body:
// src/components/swagger/component.ts
export class SwaggerComponent extends BaseComponent {
constructor(
@inject({ key: CoreBindings.APPLICATION_INSTANCE }) private application: BaseApplication,
) {
super({ scope: SwaggerComponent.name });
this.bindings = {
[SwaggerBindingKeys.SWAGGER_OPTIONS]: Binding.bind<ISwaggerOptions>({
key: SwaggerBindingKeys.SWAGGER_OPTIONS,
}).toValue(DEFAULT_SWAGGER_OPTIONS),
};
}
override async binding() {
// Read options, configure OpenAPI doc endpoint, UI endpoint, etc.
}
}In this pattern, initDefault defaults to { enable: false }, so the bindings are defined but not auto-registered. The component manages its own option reading and setup in binding().
Built-in Components
Registered via this.component() (Standard Components)
| Component | Key Features |
|---|---|
| HealthCheckComponent | GET /health (default path, configurable). Registers HealthCheckController with GET / and POST /ping endpoints. |
| SwaggerComponent | OpenAPI doc at /doc/openapi.json, UI at /doc/explorer (Scalar by default, Swagger UI also supported). Auto-populates app info and server URL. Registers JWT and Basic security schemes. |
| AuthenticateComponent | JWT and/or Basic auth strategies. Optional auth controller (signIn/signUp). Token services (JWTTokenService, BasicTokenService). |
| AuthorizationComponent | Casbin-based RBAC, permission mapping, authorize() middleware. |
| RequestTrackerComponent | Registers Hono requestId() middleware and a RequestSpyMiddleware for x-request-id header tracking and request body parsing. |
Excluded from Barrel (Import Directly)
| Component | Import Path | Key Features |
|---|---|---|
| StaticAssetComponent | @venizia/ignis/components/static-asset | File upload/download CRUD, MinIO/Disk storage. |
| MailComponent | @venizia/ignis/components/mail | Nodemailer/Mailgun transporters, Direct/BullMQ/InternalQueue executors. |
| SocketIOComponent | @venizia/ignis/components/socket-io | Socket.IO server with Redis adapter for horizontal scaling. |
| WebSocketComponent | @venizia/ignis/components/websocket | WebSocket support. |
Controller Transport Components (Not Registered via this.component())
| Component | When Used |
|---|---|
| RestComponent | Instantiated by registerControllers() when transports includes 'rest' (the default). Discovers and mounts REST controllers. |
| GrpcComponent | Instantiated by registerControllers() when transports includes 'grpc'. Discovers and mounts gRPC controllers (ConnectRPC over HTTP). |
Exposing and Consuming Component Options
Pattern 1: Override Before Registration
The most common pattern - override options before registering the component:
// src/application.ts
import { HealthCheckComponent, HealthCheckBindingKeys, IHealthCheckOptions } from '@venizia/ignis';
export class Application extends BaseApplication {
preConfigure(): ValueOrPromise<void> {
// 1. Override options BEFORE registering component
this.bind<IHealthCheckOptions>({ key: HealthCheckBindingKeys.HEALTH_CHECK_OPTIONS })
.toValue({
restOptions: { path: '/api/health' }, // Custom path
});
// 2. Register component (will use overridden options)
this.component(HealthCheckComponent);
}
}Pattern 2: Merge with Defaults
For partial overrides, merge with defaults in the component:
// In your component's binding() method
override binding(): ValueOrPromise<void> {
const extraOptions = this.application.get<Partial<IMyOptions>>({
key: MyBindingKeys.OPTIONS,
isOptional: true,
}) ?? {};
// Merge with defaults
const options = { ...DEFAULT_OPTIONS, ...extraOptions };
// Use merged options...
}Pattern 3: Deep Merge for Nested Options
For complex nested configurations:
override binding(): ValueOrPromise<void> {
const extraOptions = this.application.get<Partial<ISwaggerOptions>>({
key: SwaggerBindingKeys.SWAGGER_OPTIONS,
isOptional: true,
}) ?? {};
// Deep merge nested objects
const options: ISwaggerOptions = {
...DEFAULT_OPTIONS,
...extraOptions,
restOptions: {
...DEFAULT_OPTIONS.restOptions,
...extraOptions.restOptions,
},
explorer: {
...DEFAULT_OPTIONS.explorer,
...extraOptions.explorer,
},
};
}Best Practices Summary
| Aspect | Recommendation |
|---|---|
| Directory | Use common/ for shared keys, types, constants |
| Keys | Use @app/[component]/[feature] format |
| Types | I prefix for interfaces, T prefix for type aliases |
| Constants | Use static classes with SCHEME_SET for validation |
| Defaults | Define DEFAULT_OPTIONS constant at file top |
| Exports | Use barrel exports (index.ts) at every level |
| Validation | Validate required options in binding() |
| Logging | Log binding activity with structured messages |
| Scope | Always set scope: ComponentName.name |
| Idempotency | configure() is already idempotent via isConfigured guard — no need to add your own |
Quick Reference Template
// common/keys.ts
export class MyComponentBindingKeys {
static readonly OPTIONS = '@app/my-component/options';
}
// common/types.ts
export interface IMyComponentOptions {
restOptions: { path: string };
// ... other options
}
// common/constants.ts (optional)
export class MyConstants {
static readonly VALUE_A = 'a';
static readonly VALUE_B = 'b';
}
// common/rest-paths.ts (optional)
export class MyRestPaths {
static readonly ROOT = '/';
static readonly BY_ID = '/:id';
}
// common/index.ts
export * from './keys';
export * from './types';
export * from './constants';
export * from './rest-paths';
// component.ts
import { BaseApplication, BaseComponent, inject, CoreBindings, Binding, ValueOrPromise } from '@venizia/ignis';
import { MyComponentBindingKeys, IMyComponentOptions } from './common';
import { MyController } from './controller';
const DEFAULT_OPTIONS: IMyComponentOptions = {
restOptions: { path: '/my-feature' },
};
export class MyComponent extends BaseComponent {
constructor(
@inject({ key: CoreBindings.APPLICATION_INSTANCE })
private application: BaseApplication,
) {
super({
scope: MyComponent.name,
initDefault: { enable: true, container: application },
bindings: {
[MyComponentBindingKeys.OPTIONS]: Binding.bind<IMyComponentOptions>({
key: MyComponentBindingKeys.OPTIONS,
}).toValue(DEFAULT_OPTIONS),
},
});
}
override binding(): ValueOrPromise<void> {
const options = this.application.get<IMyComponentOptions>({
key: MyComponentBindingKeys.OPTIONS,
isOptional: true,
}) ?? DEFAULT_OPTIONS;
// Register controllers, services, etc.
this.application.controller(MyController);
}
}
// index.ts
export * from './common';
export * from './component';
export * from './controller';See Also
Related Concepts:
- Components Overview - What components are
- Creating Components - Build your own components
- Application - Registering components
- Dependency Injection - Component bindings
- gRPC Controllers - gRPC controller reference
Built-in Components:
- Authentication Component - JWT authentication
- Health Check Component - Health endpoints
- Swagger Component - API documentation
- Socket.IO Component - WebSocket support
Best Practices:
- Architectural Patterns - Component design patterns
- Code Style Standards - Component coding standards