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

@ooneex/routing

v1.3.13

Published

Decorator-driven HTTP routing with path parameters, validation constraints, permission guards, and named route generation

Readme

@ooneex/routing

Decorator-driven HTTP routing with path parameters, validation constraints, permission guards, and named route generation.

Bun TypeScript MIT License

Features

Method-Specific Decorators - Define routes using Route.get, Route.post, Route.put, Route.delete, Route.patch, Route.options, Route.head, and Route.socket decorators

Parameter Validation - Built-in validation for route params, query strings, and payloads using ArkType schemas

Type-Safe Route Paths - Compile-time validation of route paths with typed parameter extraction

WebSocket Support - Define WebSocket routes alongside HTTP routes with Route.socket

Route Versioning - Version routes with a numeric version field in route configuration

Role-Based Access Control - Restrict routes by user roles and environment

IP/Host Restrictions - Limit access by IP address or hostname

Route Generation - Generate URLs for named routes with parameter interpolation

Route Documentation Utilities - Convert route configurations to TypeScript type strings or JSON documentation

Container Integration - Automatic controller registration with the DI container

Installation

bun add @ooneex/routing

Usage

Basic HTTP Route

import { Route } from '@ooneex/routing';
import type { IController, ContextType } from '@ooneex/controller';
import type { IResponse } from '@ooneex/http-response';

@Route.http({
  name: 'api.users.list',
  path: '/api/users',
  method: 'GET',
  description: 'List all users'
})
class UserListController implements IController {
  public async index(context: ContextType): Promise<IResponse> {
    return context.response.json({
      users: [{ id: 1, name: 'John' }]
    });
  }
}

Route with Parameters

import { Route } from '@ooneex/routing';
import { type } from 'arktype';

@Route.http({
  name: 'api.users.show',
  path: '/api/users/:id',
  method: 'GET',
  description: 'Get user by ID',
  params: {
    id: type('string.uuid')
  }
})
class UserShowController implements IController {
  public async index(context: ContextType): Promise<IResponse> {
    const { id } = context.params;
    const user = await this.userRepository.findById(id);
    
    return context.response.json({ user });
  }
}

Route with Query Validation

import { Route } from '@ooneex/routing';
import { type } from 'arktype';

@Route.http({
  name: 'api.products.search',
  path: '/api/products',
  method: 'GET',
  description: 'Search products',
  queries: type({
    q: 'string',
    page: 'number = 1',
    limit: 'number = 10',
    'category?': 'string'
  })
})
class ProductSearchController implements IController {
  public async index(context: ContextType): Promise<IResponse> {
    const { q, page, limit, category } = context.queries;
    
    const products = await this.search(q, { page, limit, category });
    
    return context.response.json({ products });
  }
}

Route with Payload Validation

import { Route } from '@ooneex/routing';
import { type } from 'arktype';

@Route.http({
  name: 'api.users.create',
  path: '/api/users',
  method: 'POST',
  description: 'Create a new user',
  payload: type({
    email: 'string.email',
    name: 'string >= 2',
    password: 'string >= 8'
  })
})
class UserCreateController implements IController {
  public async index(context: ContextType): Promise<IResponse> {
    const { email, name, password } = context.payload;
    
    const user = await this.userService.create({ email, name, password });
    
    return context.response.json({ user }, 201);
  }
}

WebSocket Route

import { Route } from '@ooneex/routing';
import type { IController, ContextType } from '@ooneex/socket';

@Route.socket({
  name: 'api.chat.connect',
  path: '/ws/chat/:roomId',
  description: 'Connect to chat room',
  params: {
    roomId: type('string')
  }
})
class ChatController implements IController {
  public async index(context: ContextType): Promise<IResponse> {
    const { roomId } = context.params;
    
    await context.channel.subscribe();
    
    return context.response.json({
      connected: true,
      room: roomId
    });
  }
}

Route with Role-Based Access

import { Route } from '@ooneex/routing';
import { ERole } from '@ooneex/role';

@Route.http({
  name: 'admin.users.delete',
  path: '/admin/users/:id',
  method: 'DELETE',
  description: 'Delete a user (admin only)',
  roles: [ERole.ADMIN, ERole.SUPER_ADMIN]
})
class UserDeleteController implements IController {
  public async index(context: ContextType): Promise<IResponse> {
    await this.userService.delete(context.params.id);
    
    return context.response.json({ deleted: true });
  }
}

Generating URLs

import { router } from '@ooneex/routing';

// Generate URL for a named route
const userUrl = router.generate('api.users.show', { id: '123' });
console.log(userUrl); // "/api/users/123"

// Generate URL with multiple parameters
const orderUrl = router.generate('api.users.orders.show', {
  userId: '123',
  orderId: '456'
});
console.log(orderUrl); // "/api/users/123/orders/456"

API Reference

Decorators

@Route.http(config: RouteConfigType)

Decorator for defining HTTP routes.

Parameters:

  • config.name - Route name in namespace.resource.action format
  • config.path - URL path with optional parameters (e.g., /users/:id)
  • config.method - HTTP method (GET, POST, PUT, PATCH, DELETE, etc.)
  • config.description - Human-readable description
  • config.params - Parameter validation schema (optional)
  • config.queries - Query string validation schema (optional)
  • config.payload - Request body validation schema (optional)
  • config.response - Response validation schema (optional)
  • config.roles - Required roles for access (optional)
  • config.permission - Custom permission class (optional)
  • config.env - Allowed environments (optional)
  • config.ip - Allowed IP addresses (optional)
  • config.host - Allowed hostnames (optional)
  • config.cache - Enable caching (optional)
  • config.generate - Code generation options (optional)

Example:

@Route.http({
  name: 'api.products.update',
  path: '/api/products/:id',
  method: 'PUT',
  description: 'Update a product',
  params: { id: type('string.uuid') },
  payload: type({ name: 'string', price: 'number' }),
  roles: [ERole.ADMIN]
})

@Route.socket(config: RouteConfigType)

Decorator for defining WebSocket routes.

Parameters: Same as @Route.http, but method is ignored and isSocket is set to true.


Classes

Router

Main router class for managing routes.

Methods:

addRoute(route: RouteConfigType): this

Manually add a route to the router.

Parameters:

  • route - The route configuration

Returns: The router instance for chaining

Throws: RouterException if route name or path/method already exists

findRouteByPath(path: string): RouteConfigType[] | null

Find all routes registered for a specific path.

Parameters:

  • path - The URL path

Returns: Array of route configurations or null

findRouteByName(name: RouteNameType): RouteConfigType | null

Find a route by its unique name.

Parameters:

  • name - The route name

Returns: Route configuration or null

getRoutes(): Map<string, RouteConfigType[]>

Get all registered routes.

Returns: Map of path to route configurations

getHttpRoutes(): Map<string, RouteConfigType[]>

Get only HTTP routes (excludes WebSocket routes).

Returns: Map of path to HTTP route configurations

getSocketRoutes(): Map<string, RouteConfigType>

Get only WebSocket routes.

Returns: Map of path to WebSocket route configuration

generate<P>(name: RouteNameType, params?: P): string

Generate a URL for a named route.

Parameters:

  • name - The route name
  • params - Parameter values to interpolate

Returns: The generated URL path

Throws: RouterException if route not found or missing required parameters

Example:

const url = router.generate('api.users.show', { id: '123' });

Types

RouteNameType

Route name following the namespace.resource.action pattern.

type RouteNameType = `${RouteNamespace}.${string}.${RouteAction}`;
// Example: "api.users.list", "admin.products.create"

Valid Namespaces:

  • api, client, admin, public, auth, webhook, internal, external, system, metrics, docs

Valid Actions:

  • list, show, create, update, delete, search, export, import, and 200+ more

RouteConfigType

type RouteConfigType = {
  name: RouteNameType;
  path: `/${string}`;
  method: HttpMethodType;
  params?: Record<string, AssertType | IAssert>;
  queries?: AssertType | IAssert;
  payload?: AssertType | IAssert;
  response?: AssertType | IAssert;
  controller: ControllerClassType;
  description: string;
  env?: Environment[];
  ip?: string[];
  host?: string[];
  roles?: ERole[];
  permission?: PermissionClassType;
  cache?: boolean;
  isSocket: boolean;
  generate?: {
    doc?: boolean;
    fetcher?: boolean;
    queryHook?: boolean;
  };
};

ExtractParameters<T>

Utility type to extract parameter names from a route path.

type Params = ExtractParameters<'/users/:id/orders/:orderId'>;
// Result: "id" | "orderId"

RouteParameters<T>

Utility type to create a typed record from route parameters.

type Params = RouteParameters<'/users/:id'>;
// Result: { id: string }

Advanced Usage

Environment-Specific Routes

import { Route } from '@ooneex/routing';
import { Environment } from '@ooneex/app-env';

@Route.http({
  name: 'api.debug.info',
  path: '/api/debug',
  method: 'GET',
  description: 'Debug information (dev only)',
  env: [Environment.LOCAL, Environment.DEVELOPMENT]
})
class DebugController implements IController {
  public async index(context: ContextType): Promise<IResponse> {
    return context.response.json({
      environment: context.app.env.env,
      timestamp: new Date().toISOString()
    });
  }
}

Custom Permission Classes

import { Route } from '@ooneex/routing';
import { Permission } from '@ooneex/permission';
import type { IUser } from '@ooneex/user';

class CanEditOwnProfile extends Permission {
  public can(user: IUser | null, context: ContextType): boolean {
    if (!user) return false;
    return user.id === context.params.id;
  }
}

@Route.http({
  name: 'api.users.update',
  path: '/api/users/:id',
  method: 'PUT',
  description: 'Update user profile',
  permission: CanEditOwnProfile
})
class UserUpdateController implements IController {
  // Only the user themselves can update their profile
}

IP Address Restrictions

@Route.http({
  name: 'internal.metrics.show',
  path: '/internal/metrics',
  method: 'GET',
  description: 'Internal metrics endpoint',
  ip: ['127.0.0.1', '10.0.0.0/8', '192.168.1.0/24']
})
class MetricsController implements IController {
  // Only accessible from specified IP ranges
}

Code Generation Options

@Route.http({
  name: 'api.users.list',
  path: '/api/users',
  method: 'GET',
  description: 'List all users',
  response: type({ users: 'User[]' }),
  generate: {
    doc: true,        // Generate API documentation
    fetcher: true,    // Generate fetch client function
    queryHook: true   // Generate React Query hook
  }
})

Error Handling

import { router, RouterException } from '@ooneex/routing';

try {
  const url = router.generate('api.nonexistent.route', { id: '123' });
} catch (error) {
  if (error instanceof RouterException) {
    console.error('Route error:', error.message);
  }
}

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Development Setup

  1. Clone the repository
  2. Install dependencies: bun install
  3. Run tests: bun run test
  4. Build the project: bun run build

Guidelines

  • Write tests for new features
  • Follow the existing code style
  • Update documentation for API changes
  • Ensure all tests pass before submitting PR

Made with ❤️ by the Ooneex team