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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@vortex-js/core

v1.0.1

Published

A simple and powerful role-based access control (RBAC) middleware for Express.js, designed to be easy to use and integrate with your existing applications. It provides a flexible way to manage user permissions and roles, making it ideal for building secur

Downloads

46

Readme

Vortex-JS/Core

npm version License: MIT

A comprehensive TypeScript framework for building Express.js APIs with built-in role-based access control (RBAC) and automatic Postman documentation generation.

Features

  • Role-Based Access Control: Define granular access permissions for your API endpoints
  • Hierarchical Route Organization: Logically group your routes with shared prefixes and middleware
  • Automatic Postman Documentation: Generate Postman collections and environments from your routes
  • TypeScript Support: Fully typed API for better developer experience
  • Express.js Integration: Built on top of Express.js for easy adoption

Table of Contents

Installation

npm install @vortex-js/core
# or
yarn add @vortex-js/core

Quick Start

Here's a simple example to get you started:

import express from "express";
import { AppWrapper, Routes, Route } from "@vortex-js/core";

// Create your Express app
const app = express();

// Define your routes
const apiRoutes = new Routes({
  prefix: "/api",
  routes: [
    new Route({
      method: "GET",
      path: "/users",
      middlewares: [
        (req, res) => {
          res.json({ users: ["John", "Jane"] });
        },
      ],
      name: "Get Users",
      description: "Retrieve a list of all users",
      rai: "users:read",
      roles: ["admin", "user"],
    }),
    new Route({
      method: "POST",
      path: "/users",
      middlewares: [
        (req, res) => {
          res.status(201).json({ message: "User created" });
        },
      ],
      name: "Create User",
      description: "Create a new user",
      rai: "users:create",
      roles: ["admin"],
    }),
  ],
});

// Create app wrapper
const appWrapper = new AppWrapper({
  app,
  routes: apiRoutes,
  roles: ["admin", "user", "guest"],
  postman: {
    name: "My API",
    description: "API documentation",
    baseUrl: "http://localhost:3000",
  },
});

// Get configured Express app
const configuredApp = appWrapper.getExpressApp();

// Generate Postman documentation
appWrapper.generatePostmanCollection("./collection.json");
appWrapper.generatePostmanEnvironment("./environment.json");

// Start the server
configuredApp.listen(3000, () => {
  console.log("Server running on port 3000");
});

Core Concepts

AppWrapper

The AppWrapper is the main entry point of the framework. It wraps your Express application and provides:

  • Role-based access control setup
  • Route registration
  • Postman documentation generation
const appWrapper = new AppWrapper({
  app: expressApp, // Your Express application
  routes: routesInstance, // Routes configuration
  roles: ["admin", "user"], // Available roles
  postman: {
    // Optional Postman configuration
    name: "My API",
    description: "API Documentation",
    baseUrl: "http://localhost:3000",
    version: "1.0.0",
  },
});

// Get the configured Express app
const configuredApp = appWrapper.getExpressApp();

Routes

The Routes class represents a group of routes with shared configuration:

  • Common URL prefix
  • Shared middleware
  • Organized documentation
const userRoutes = new Routes({
  prefix: "/users", // URL prefix for all contained routes
  routes: [route1, route2, route3], // Array of Route or Routes instances
  middlewares: [authMiddleware], // Optional shared middleware
  params: [
    {
      // Optional URL parameters
      path: "userId",
      method: (req, res, next, id) => {
        // Parameter handling logic
        next();
      },
    },
  ],
  postman: {
    // Optional Postman folder configuration
    folderName: "User Management",
  },
  error: errorRoute, // Optional error handling
});

Route

The Route class represents an individual API endpoint:

const getUserRoute = new Route({
  method: "GET", // HTTP method (GET, POST, PUT, DELETE)
  path: "/:id", // URL path (will be combined with Routes prefix)
  middlewares: [
    // Array of Express middleware functions
    (req, res) => {
      res.json({ user: { id: req.params.id, name: "John" } });
    },
  ],
  name: "Get User", // Name for documentation
  description: "Get user by ID", // Description for documentation
  rai: "users:read", // Resource Access Identifier
  roles: ["admin", "user"], // Roles that can access this route
  postman: {
    // Optional Postman configuration
    body: {
      // Example request body
      name: "John Doe",
      email: "[email protected]",
    },
    params: [
      {
        // Example URL parameters
        key: "id",
        value: "123",
        description: "User ID",
      },
    ],
  },
});

ErrorRoute

The ErrorRoute class provides custom error handling for a route group:

const customErrorHandler = new ErrorRoute({
  middleware: (err, req, res, next) => {
    if (err.name === 'ValidationError') {
      return res.status(400).json({ error: err.message });
    }
    next(err);
  }
});

const apiRoutes = new Routes({
  prefix: '/api',
  routes: [...],
  error: customErrorHandler
});

Role Access Identifiers (RAIs)

Role Access Identifiers (RAIs) are unique strings that identify resources in your API. Each route has an RAI and a list of roles that can access it. The framework automatically:

  1. Extracts RAIs from your routes
  2. Checks if the current user's roles allow access to the requested RAI
  3. Returns an appropriate error if access is denied
// Define a route with RAI and roles
const route = new Route({
  // ...other properties
  rai: "users:update", // The resource being accessed
  roles: ["admin", "owner"], // Roles that can access this resource
});

// User authentication should set the user's roles
app.use((req, res, next) => {
  req.user = {
    roles: ["user", "owner"], // This user has 'user' and 'owner' roles
  };
  next();
});

// The framework middleware will check if the user can access the route
// In this example, the user has the 'owner' role, so access is granted

Postman Documentation

The framework automatically generates Postman collections and environments from your routes:

// Generate Postman collection
appWrapper.generatePostmanCollection("./postman/collection.json");

// Generate Postman environment
appWrapper.generatePostmanEnvironment("./postman/environment.json");

The generated files include:

  • All routes with their methods, paths, and descriptions
  • Request body examples
  • URL parameters
  • Environment variables

API Reference

AppWrapper

class AppWrapper {
  constructor(config: AppWrapperConfig);
  getExpressApp(): Express;
  generatePostmanCollection(filePath: string): void;
  generatePostmanEnvironment(filePath: string): void;
}

interface AppWrapperConfig {
  app: Express;
  routes: Routes;
  postman?: PostmanConfig;
  roles?: string[];
}

interface PostmanConfig {
  name: string;
  description?: string;
  version?: string;
  baseUrl?: string;
}

Routes

class Routes {
  constructor(r: IRoutes);
  buildRouter(p_router?: Router, p_prefix?: { path: string }): Router;
  generateFolder(pathPrefix?: string): PostmanRouteItem[] | PostmanRouteItem;
}

interface IRoutes {
  prefix?: string;
  routes: Array<Route | Routes>;
  error?: ErrorRoute;
  params?: Param[];
  middlewares?: Handler[];
  postman?: {
    folderName: string;
  };
  module?: string;
}

interface Param {
  path: string;
  method: RequestParamHandler;
}

Route

class Route {
  constructor(r: IRoute);
  buildRoute(router: Router, route: Routes, prefix: { path: string }): void;
  generateRoute(pathPrefix?: string): PostmanRouteItem;
}

interface IRoute {
  method: "GET" | "POST" | "PUT" | "DELETE";
  path: string;
  middlewares: Handler[];
  name?: string;
  description?: string;
  rai: string;
  roles: string[];
  postman?: {
    body?: Record<string, unknown>;
    params?: Array<{ key: string; value: string; description: string }>;
  };
}

ErrorRoute

class ErrorRoute {
  constructor(r: IErrorRoute);
}

interface IErrorRoute {
  middleware: PathParams;
}

RAI System

function InitializeCreatingRAIs(RoutesInstance: Routes): {
  rais: IRAI[];
  roles: string[];
};

interface IRAI {
  method: string;
  path: string;
  _id: string;
  name: string;
  description: string;
  rai: string;
  children: string[];
  roles: string[];
  isStopped: boolean;
}

interface IRole {
  _id: string;
  name: string;
}

PostmanGenerator

class PostmanGenerator {
  constructor(
    name: string,
    description?: string,
    options?: {
      baseUrl?: string;
      version?: string;
    }
  );

  addEnvironmentVariable(key: string, value: string, type?: string): void;
  addEnvironmentVariables(
    variables: Array<{
      key: string;
      value: string;
      type?: string;
    }>
  ): void;

  generateCollection(items: PostmanRouteItem[]): PostmanCollection;
  generateEnvironment(items: PostmanRouteItem[]): PostmanEnvironment;

  saveCollectionToFile(filePath: string, options?: SaveOptions): void;
  saveEnvironmentToFile(filePath: string, options?: SaveOptions): void;
  saveToFiles(
    collectionPath: string,
    environmentPath: string,
    options?: SaveOptions
  ): void;
}

Examples

Basic API with Authentication

This example shows how to set up a basic API with JWT authentication:

import express from "express";
import jwt from "jsonwebtoken";
import { AppWrapper, Routes, Route } from "@vortex-js/core";

const app = express();
app.use(express.json());

// Authentication middleware
const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(" ")[1];

  if (!token) {
    return res.status(401).json({ error: "Authentication required" });
  }

  try {
    const decoded = jwt.verify(token, "your-secret-key");
    req.user = {
      id: decoded.id,
      roles: decoded.roles,
    };
    next();
  } catch (err) {
    return res.status(401).json({ error: "Invalid token" });
  }
};

// Login route (outside RBAC system)
app.post("/login", (req, res) => {
  const { username, password } = req.body;

  // Example authentication (replace with your own)
  if (username === "admin" && password === "password") {
    const token = jwt.sign({ id: 1, roles: ["admin"] }, "your-secret-key", {
      expiresIn: "1h",
    });

    return res.json({ token });
  }

  if (username === "user" && password === "password") {
    const token = jwt.sign({ id: 2, roles: ["user"] }, "your-secret-key", {
      expiresIn: "1h",
    });

    return res.json({ token });
  }

  return res.status(401).json({ error: "Invalid credentials" });
});

// RBAC routes
const apiRoutes = new Routes({
  prefix: "/api",
  routes: [
    // Public route
    new Route({
      method: "GET",
      path: "/public",
      middlewares: [
        (req, res) => {
          res.json({ message: "This is public" });
        },
      ],
      name: "Public Endpoint",
      description: "Accessible to everyone",
      rai: "public:read",
      roles: ["admin", "user", "guest"],
    }),

    // Protected routes
    new Routes({
      prefix: "/users",
      middlewares: [authenticate], // Apply authentication to all routes in this group
      routes: [
        new Route({
          method: "GET",
          path: "",
          middlewares: [
            (req, res) => {
              res.json({
                users: [
                  { id: 1, name: "Admin" },
                  { id: 2, name: "User" },
                ],
              });
            },
          ],
          name: "Get All Users",
          description: "List all users",
          rai: "users:list",
          roles: ["admin"], // Only admin can list users
        }),

        new Route({
          method: "GET",
          path: "/profile",
          middlewares: [
            (req, res) => {
              res.json({ profile: { id: req.user.id, roles: req.user.roles } });
            },
          ],
          name: "Get Profile",
          description: "Get current user profile",
          rai: "users:profile",
          roles: ["admin", "user"], // Both admin and user can access their profile
        }),
      ],
    }),
  ],
});

// Create app wrapper
const appWrapper = new AppWrapper({
  app,
  routes: apiRoutes,
  roles: ["admin", "user", "guest"],
  postman: {
    name: "Authentication Example API",
    description: "API with authentication and RBAC",
    baseUrl: "http://localhost:3000",
  },
});

// Get configured Express app
const configuredApp = appWrapper.getExpressApp();

// Generate Postman documentation
appWrapper.generatePostmanCollection("./collection.json");
appWrapper.generatePostmanEnvironment("./environment.json");

// Start the server
configuredApp.listen(3000, () => {
  console.log("Server running on port 3000");
});

Nested Routes

This example demonstrates how to organize routes in a hierarchical structure:

import express from "express";
import { AppWrapper, Routes, Route } from "express-rbac-framework";

const app = express();
app.use(express.json());

// Define some middleware
const logRequest = (req, res, next) => {
  console.log(`${req.method} ${req.originalUrl}`);
  next();
};

const checkApiKey = (req, res, next) => {
  const apiKey = req.headers["x-api-key"];
  if (!apiKey || apiKey !== "valid-key") {
    return res.status(401).json({ error: "Invalid API key" });
  }
  next();
};

// Define routes with nested structure
const apiRoutes = new Routes({
  prefix: "/api",
  middlewares: [logRequest, checkApiKey],
  postman: { folderName: "API" },
  routes: [
    // Users routes
    new Routes({
      prefix: "/users",
      postman: { folderName: "User Management" },
      routes: [
        new Route({
          method: "GET",
          path: "",
          middlewares: [(req, res) => res.json({ users: [] })],
          name: "List Users",
          description: "Get all users",
          rai: "users:list",
          roles: ["admin"],
        }),

        new Route({
          method: "POST",
          path: "",
          middlewares: [(req, res) => res.status(201).json({ id: 1 })],
          name: "Create User",
          description: "Create a new user",
          rai: "users:create",
          roles: ["admin"],
          postman: {
            body: {
              name: "John Doe",
              email: "[email protected]",
            },
          },
        }),

        // User details routes
        new Routes({
          prefix: "/:userId",
          params: [
            {
              path: "userId",
              method: (req, res, next, id) => {
                if (isNaN(parseInt(id))) {
                  return res.status(400).json({ error: "Invalid user ID" });
                }
                next();
              },
            },
          ],
          routes: [
            new Route({
              method: "GET",
              path: "",
              middlewares: [
                (req, res) => res.json({ id: req.params.userId, name: "John" }),
              ],
              name: "Get User",
              description: "Get user by ID",
              rai: "users:read",
              roles: ["admin", "user"],
            }),

            new Route({
              method: "PUT",
              path: "",
              middlewares: [(req, res) => res.json({ updated: true })],
              name: "Update User",
              description: "Update a user",
              rai: "users:update",
              roles: ["admin"],
              postman: {
                body: {
                  name: "Updated Name",
                  email: "[email protected]",
                },
              },
            }),

            new Route({
              method: "DELETE",
              path: "",
              middlewares: [(req, res) => res.json({ deleted: true })],
              name: "Delete User",
              description: "Delete a user",
              rai: "users:delete",
              roles: ["admin"],
            }),
          ],
        }),
      ],
    }),

    // Products routes
    new Routes({
      prefix: "/products",
      postman: { folderName: "Product Management" },
      routes: [
        // Product routes here
      ],
    }),
  ],
});

const appWrapper = new AppWrapper({
  app,
  routes: apiRoutes,
  roles: ["admin", "user", "guest"],
  postman: {
    name: "Nested Routes Example",
    description: "API with hierarchical route structure",
    baseUrl: "http://localhost:3000",
  },
});

const configuredApp = appWrapper.getExpressApp();
appWrapper.generatePostmanCollection("./collection.json");

configuredApp.listen(3000, () => {
  console.log("Server running on port 3000");
});

Custom Error Handling

This example shows how to implement custom error handling:

import express from "express";
import { AppWrapper, Routes, Route, ErrorRoute } from "@vortex-js/core";

const app = express();
app.use(express.json());

// Custom validation middleware
const validateUser = (req, res, next) => {
  const { name, email } = req.body;

  if (!name || !email) {
    const error = new Error("Name and email are required");
    error.name = "ValidationError";
    return next(error);
  }

  if (typeof name !== "string" || name.length < 3) {
    const error = new Error("Name must be at least 3 characters long");
    error.name = "ValidationError";
    return next(error);
  }

  if (!email.includes("@")) {
    const error = new Error("Invalid email format");
    error.name = "ValidationError";
    return next(error);
  }

  next();
};

// Custom error handler
const apiErrorHandler = new ErrorRoute({
  middleware: (err, req, res, next) => {
    console.error("API Error:", err);

    if (err.name === "ValidationError") {
      return res.status(400).json({
        error: "Validation Error",
        message: err.message,
      });
    }

    if (err.name === "NotFoundRouteError") {
      return res.status(404).json({
        error: "Not Found",
        message: "The requested resource does not exist",
      });
    }

    if (err.name === "ApiRouteNotFoundError") {
      return res.status(403).json({
        error: "Access Denied",
        message: "You do not have permission to access this resource",
      });
    }

    // Default error handler
    res.status(500).json({
      error: "Server Error",
      message: "An unexpected error occurred",
    });
  },
});

// Define routes
const apiRoutes = new Routes({
  prefix: "/api",
  error: apiErrorHandler, // Apply custom error handling
  routes: [
    new Routes({
      prefix: "/users",
      routes: [
        new Route({
          method: "POST",
          path: "",
          middlewares: [
            validateUser, // Apply validation
            (req, res) => {
              res.status(201).json({
                id: 1,
                name: req.body.name,
                email: req.body.email,
              });
            },
          ],
          name: "Create User",
          description: "Create a new user with validation",
          rai: "users:create",
          roles: ["admin"],
          postman: {
            body: {
              name: "John Doe",
              email: "[email protected]",
            },
          },
        }),
      ],
    }),
  ],
});

const appWrapper = new AppWrapper({
  app,
  routes: apiRoutes,
  roles: ["admin", "user", "guest"],
});

const configuredApp = appWrapper.getExpressApp();

// Global fallback error handler
configuredApp.use((err, req, res, next) => {
  console.error("Unhandled Error:", err);
  res.status(500).send("Something went wrong!");
});

configuredApp.listen(3000, () => {
  console.log("Server running on port 3000");
});

Advanced RBAC

This example demonstrates a more complex role-based access control scenario:

import express from "express";
import jwt from "jsonwebtoken";
import { AppWrapper, Routes, Route } from "express-rbac-framework";

const app = express();
app.use(express.json());

// Authentication middleware
const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(" ")[1];

  if (!token) {
    req.user = { roles: ["guest"] }; // Default guest role
    return next();
  }

  try {
    const decoded = jwt.verify(token, "your-secret-key");
    req.user = {
      id: decoded.id,
      roles: decoded.roles,
      organization: decoded.organization,
    };
    next();
  } catch (err) {
    req.user = { roles: ["guest"] }; // Default to guest on error
    next();
  }
};

// Mock database
const users = [
  { id: 1, name: "Admin", organization: "org1", isAdmin: true },
  { id: 2, name: "Manager", organization: "org1", isManager: true },
  { id: 3, name: "User 1", organization: "org1" },
  { id: 4, name: "User 2", organization: "org2" },
];

// Example of owner check middleware
const checkOwnership = (req, res, next) => {
  const userId = parseInt(req.params.userId);
  const user = users.find((u) => u.id === userId);

  if (!user) {
    return res.status(404).json({ error: "User not found" });
  }

  // User is either admin, from same organization, or the user themselves
  const isAdmin = req.user.roles.includes("admin");
  const isSameOrg = user.organization === req.user.organization;
  const isSelf = req.user.id === userId;

  if (isAdmin || isSameOrg || isSelf) {
    req.targetUser = user;
    return next();
  }

  return res.status(403).json({ error: "Access denied" });
};

// Define routes
const apiRoutes = new Routes({
  prefix: "/api",
  middlewares: [authenticate],
  routes: [
    new Routes({
      prefix: "/users",
      routes: [
        // List all users - admin only
        new Route({
          method: "GET",
          path: "",
          middlewares: [
            (req, res) => {
              // Admins see all users
              if (req.user.roles.includes("admin")) {
                return res.json({ users });
              }

              // Managers see users in their organization
              if (req.user.roles.includes("manager")) {
                const orgUsers = users.filter(
                  (u) => u.organization === req.user.organization
                );
                return res.json({ users: orgUsers });
              }

              // Regular users see limited info
              const basicUsers = users.map((u) => ({
                id: u.id,
                name: u.name,
              }));
              return res.json({ users: basicUsers });
            },
          ],
          name: "List Users",
          description: "Get all users with role-based filtering",
          rai: "users:list",
          roles: ["admin", "manager", "user"],
        }),

        // Get specific user details - with ownership check
        new Route({
          method: "GET",
          path: "/:userId",
          middlewares: [
            checkOwnership,
            (req, res) => {
              // Admins see everything
              if (req.user.roles.includes("admin")) {
                return res.json({ user: req.targetUser });
              }

              // Others see limited info
              const { id, name, organization } = req.targetUser;
              return res.json({ user: { id, name, organization } });
            },
          ],
          name: "Get User",
          description: "Get user by ID with role-based data filtering",
          rai: "users:read",
          roles: ["admin", "manager", "user"],
        }),

        // Update user - admin or same organization manager
        new Route({
          method: "PUT",
          path: "/:userId",
          middlewares: [
            checkOwnership,
            (req, res) => {
              // Only admins can change organization
              if (req.body.organization && !req.user.roles.includes("admin")) {
                return res.status(403).json({
                  error: "Only admins can change organization",
                });
              }

              // Update user
              const userIndex = users.findIndex(
                (u) => u.id === parseInt(req.params.userId)
              );
              users[userIndex] = { ...users[userIndex], ...req.body };

              return res.json({
                message: "User updated",
                user: users[userIndex],
              });
            },
          ],
          name: "Update User",
          description: "Update user with role-based permissions",
          rai: "users:update",
          roles: ["admin", "manager"],
          postman: {
            body: {
              name: "Updated Name",
              email: "[email protected]",
            },
          },
        }),

        // Delete user - admin only
        new Route({
          method: "DELETE",
          path: "/:userId",
          middlewares: [
            (req, res) => {
              const userId = parseInt(req.params.userId);
              const userIndex = users.findIndex((u) => u.id === userId);

              if (userIndex === -1) {
                return res.status(404).json({ error: "User not found" });
              }

              users.splice(userIndex, 1);
              return res.json({ message: "User deleted" });
            },
          ],
          name: "Delete User",
          description: "Delete a user (admin only)",
          rai: "users:delete",
          roles: ["admin"],
        }),
      ],
    }),
  ],
});

// Create app wrapper with detailed roles
const appWrapper = new AppWrapper({
  app,
  routes: apiRoutes,
  roles: ["admin", "manager", "user", "guest"],
  postman: {
    name: "Advanced RBAC Example",
    description: "API with complex role-based access control",
    baseUrl: "http://localhost:3000",
  },
});

const configuredApp = appWrapper.getExpressApp();
appWrapper.generatePostmanCollection("./collection.json");

configuredApp.listen(3000, () => {
  console.log("Server running on port 3000");
});

License

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