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 🙏

© 2024 – Pkg Stats / Ryan Hefner

super-route-express

v1.0.16

Published

Non pretentious express framework

Downloads

32

Readme

SuperRoute

Non-pretentious, Mildly-opinionated Express Framework

Build Status Coverage Status Install Size Dependency Count

SuperRoute is a framework for building readable api routes, aimed at improving code re-usability, code readability and route documentation.

Main Features:

Installation


npm install --save super-route-express

import:

import {SuperRoute} from 'super-route-express';

Usage


Create a new route class that extends SuperRoute.

You can create classes for specific route types or groups - e.g. routes that require authentication, access control, or share data with the middleware.

import {SuperRoute} from 'super-route-express';

class BasicRoute extends SuperRoute {}

// all created routes will require authentication
class AuthenticatedRoute extends BasicRoute {
  authenticate: true;
}
// all created routes will use access control function to block non admin users
class AdminOnlyRoute extends AuthenticatedRoute {
  permissions: {
    equalOrGreaterThan: 'admin'
  }
}

Instantiate Routes

A SuperRoute instance (and it's children's instances) is passed a RouteSettings configuration object. Each route has a stack of request handlers that or bound the route as their this argument when calling mount(), it allows access to error handling methods as well as additional instance and class data that might be useful.

See the Api section blow for full options.

const routes: Array<SuperRoute> = [
  new AdminOnlyRoute({
    path: 'users/new',
    verb: 'post',
    name: 'new user',
    description: 'creates a new user',
    bodyParams: [
      new BodyParameter('firstName', 'string', 'user first name', true),
      new BodyParameter('lastName', 'string', 'user last name', true),
      new BodyParameter(
        'mobilePhone',
        'string',
        'user mobile phone',
        true,
        [{
            test: (value: string) => {
              return value.length === 10
            },
            description: 'checks if mobile phone has 10 digits'
        }]
      ),
    ],
    middleware: [
      (req: Request, res: Response, next: NextFunction) => {
        // ... Some middleware logic
      }
    ]
  }),
  new AuthenticatedRoute({
    path: 'users/read/:id',
    verb: 'post',
    name: 'new user',
    description: 'creates a new user',
    authenticate: true,
    routeParams: [
      new RouteParameter('id', 'mongodb id', true, [{
            test: (value: string) => ObjectId.isValid(value),
            description: 'id must be a valid mongodb ObjectId'
        }]
      ),
    ],
    middleware: [
      // stack req processing middleware here for re-usability
      SomeGenericMiddleWare,
      SomeMoreGenericMiddleware({configuration: 'some configuration'}),
      (req: Request, res: Response, next: NextFunction) => {
        // ... Some middleware logic
      }
    ]
  }),
]

Mount routes

const apiRouter = Router();
routes.forEach(route => route.mount(router));

Access Control


SuperRoute offers two levels of access control that can be defined in the extending class.

1. Access Control function

Defined by the $$accessControlFunction property, it is used to limit access to users with defined permissions, using the route's permissions settings.

Setting Route Permissions

Use a RoutePermissions object to configure access control for a route instance or extending class with the following options.

  • equalOrGreaterThan - requester must have a permission level that is equal or greater than the given string as defined by the hierarchy array.

  • specific - requester must have all the given permissions

  • merge - when set to 'and' requester must satisfy both the specific an hierarchical rules.

All properties are optional, but the configuration must contain either equalOrGreaterThan or specific.

Example:

{
  equalOrGreaterThan: 'admin',
  specific: ['specialPermission', 'awesomeDude'],
  merge: 'and'
}

will only grant access to admins that also have the specialPermission and awesomeDude permisions

Per-route instance configuration:

const route = new BasicRoute({
  path: 'some/path',
  verb: 'post',
  name: 'some name',
  permissions: {
    equalOrGreaterThan: 'admin',
    specific: ['specialPermission', 'awesomeDude'],
    merge: 'and'
  }
  // ... //
})

Or configure a child class:

class AdminOnlyRoute extends SuperRoute {
  permissions: { specific: ['admin'] }
}

Configure Access Control function

  • Assign an AccessControlFunction to the $$accessControlFunction in the class definition. When mounted, it will be called with the route's permissions and should return a RequestHandler.
  • Use the static method SuperRoute.checkPermissions to validate the user's permissions. note that checkPermissionsreceives a hierarchy Array in which the item with the largest index is the highest in the hierarchy.
  • Also note that if the route's permissions are not defined by the class or it's instances, $$accessControlFunction will not be called.
class AuthenticatedRoute extends SuperRoute {
  $$accessControlFunction: AccessControlFunction = (permissions: RoutePermissions) => {
    return async (req: Request, res: Response, next: NextFunction) => {
      // define heirarchy
      const hierarchy = [
        'end_user',
        'editor',
        'admin'
      ]
      let auth;
      try {
        // check user permissions against route permissions ans heirarchy 
        auth = SuperRoute.checkPermissions(req.user.permissions, permissions, hierarchy);
      } catch (err) {
        return next(err);
      }
      if (!auth) {
        // Do some more logic.....
        this.handleError(req, res, next, `user does not have sufficient permissions to access ${this.path}`, 403)
      } else {
        // Do some more logic.....
        return next();
      }
    }
  }
}

2. Authentication function

Defined by the $$authenticationFunction property, it is used to verify that the requester is logged in. it can be used to perform e.g. JWT verification or another login strategy, or as a form of input validation to verify that the request reaching the middle contains an authenticated user's data. When mounted, the function is called with the route's permissions definitions. Example:

class AuthenticatedRoute extends BasicRoute {
  authenticate: true;
  $$authenticationFunction = (req: Request, res: Response, next: NextFunction) => {
    if (req.hasOwnProperty(user)) {
      next();
    } else {
      this.handle(arguments, 'Not Authenticated', 400)
    }
  }
}

Input Validation


SuperRoute offers both route parameters and body validation. Route parameters and properties of the request's body can be defined in the route settings, serving both the purpose of input validation and route documentation generation.

Input that doesn't meet the defined spec will return a detailed error and 400 status code.

Body Validation

const route = new BasicRoute({
  // ...route settings... //
  bodyParams: [
    new BodyParameter('id', 'string', 'user id', true)
  ]
})

Pass an array of BodyParameter to the route's settings with the following arguments:

| Name | Type | Default value | Description | | :------ | :------ | :------ | :------ | | name | string | - | property name | | type | ParameterType | 'any' | the expected type of the parameter. if defined, will throw an error if the parameter's type doesn't match | | description | string | '' | text that will be displayed in the rendered help output | | required | boolean | true | if true, will throw an error when the property is missing | | additionalTests | RequestParameterTestFunction[] | [] | an array of additional test functions and their description |

BodyParameter (as well as RouteParameter) can be defined with additional validations (in addition to the type checking)

new BodyParameter('mobilePhone', 'string', 'user mobile phone', true, [
  {
    test: (value: string) => {
      return value.length === 10
    },
    description: 'mobile phone must have 10 digits'
  }
])

All validation errors are combined and returned.

Route Parameters Validation

const route = new BasicRoute({
  // ...route settings... //
  bodyParams: [
    new RouteParameter('id', 'user id', true)
  ]
})

Pass an array of RouteParameter to the route's settings with the following arguments:

| Name | Type | Default value | Description | | :------ | :------ | :------ | :------ | | name | string | - | property name | | description | string | '' | text that will be displayed in the rendered help output | | required | boolean | true | if true, will throw an error when the property is missing | | additionalTests | RequestParameterTestFunction[] | [] | an array of additional test functions and their description |

Route Parameters can also have additional validations same as body parameters

Error Handling


Handling with this.handle()

Middleware functions are all bound to the route as their this argument, so it is possible to call this.handle() from any of the middleware functions, with following parameters:

| Name | Type | Default value | Description | | :------ | :------ | :------ | :------ | | middlewareArgs | IArguments | - | req, res, next from the express middleware function | | errorOrMessage | string | Error | Error object, or an error message string | - | error message | | statusCode | number | 500 | html response status code | | respondWith? | string | - | optional custom error message to send as response | | log | boolean | false | log the error to the console if true | | redirect | string | false | redirect url passed to the error handler | | options? | object | - | options object to pass to a custom error handler |

It will construct a RouteError object and pass it to next().

It's also possible to just pass a vanilla Error object to next, but handling with RouteError

Generate Route Documentation


Route information, including body / route parameters and their validation rules can be exported.

Export to markdown

call toMarkdown() method on any route to generate documentation. Optionally, on an array of routes:

fs.writeFileSync('routes.md', RoutesArray.map(route => route.toMarkdown()).join('<br><br>'))

Here's an example of a route that uses all features available for documentation.

You can see the resulting markdown output here.

View Documentation in terminal

It's also possible to enable making and OPTIONS http request to a route and receive a text output of the route's documentation, which may be helpful during development.

Expample:

import {SuperRoute} from "./SuperRoute";

function shouldShowHelp() {
  return process.env.test === 'true'
}

class RouteWithHelp extends SuperRoute {
  showHelp = (self: RouteWithHelp) => {
    return process.env.test === 'true';
  }
}

// Or 

const route = new BasicRoute({
  //... route settings ...//
  showHelp: true // or false
})

curl --request OPTIONS localhost:8080/path/to/route

Route Versioning

SuperRoute can also VersionedMiddleware objects to handle route versioning. See version-router-express for further configuration details.

Example:

configure route with versioned routes

app.use(VersionedRouter.ExtractVersionFromHeader('App-version'));

// .... server logic .... //

new BasicRoute({
  path: 'versioned_route',
  verb: 'get',
  name: 'versioned route',
  versionedMiddleware: [
    {
      version: '1.0.0',
      default: false,
      middleware: [
        (req: Request, res: Response, next: NextFunction) => {
          console.log('route 2 function 1')
          next()
        },
        (req: Request, res: Response, next: NextFunction) => {
          console.log('route 2 function 2')
          res.send({route: '1'})
        },
      ]
    },
    {
      version: '>=1.2.0 <2.0.0',
      default: false,
      middleware: [
        (req: Request, res: Response, next: NextFunction) => {
          console.log('route 2 function 1')
          next()
        },
        (req: Request, res: Response, next: NextFunction) => {
          console.log('route 2 function 2')
          res.send({route: '2'})
        },
      ]
    },
    {
      version: '2.0.0',
      default: true,
      middleware: [
        (req: Request, res: Response, next: NextFunction) => {
          console.log('route 3 function 1')
          next()
        },
        (req: Request, res: Response, next: NextFunction) => {
          console.log('route 3 function 2')
          res.send({route: '3'})
        },
      ]
    }
  ]
})

make requests with custom header

curl -H 'App-version: 1.0.0' -X GET localhost:8081/versioned_rout
## ==> '{route: '1'}'
curl -H 'App-version: 1.1.0' -X GET localhost:8081/versioned_rout
## will resolve to default
## ==> '{route: '3'}' 
curl -H 'App-version: 1.3.0' -X GET localhost:8081/versioned_rout
## ==> '{route: '2'}'

API Reference


Interface: RouteSettings

Configuration Object for SuperRoute instance

const route = new SuperRoute({
  path: 'some/path',
  verb: 'post',
  name:'some name'
  // ....
})

| Name | Type | Description | Required | | :------ | :------ | :------ | :------ | | path | ExpressHttpVerb | Http Verb | true | | verb | string | Route path | true | | name | string | Route Name | true | | comments | string | Additional comments for documentation | fasle | | description | string | Route description | fasle| | middleware | RequestHandler[] | Route Middleware |fasle | | versionedMiddleware | VersionedMiddleware[] | An Array of VersionedMiddleware instances - see https://www.npmjs.com/package/version-router-express |fasle | | bodyParams | BodyParameter[] | An Array of BodyParameter instances, defining required and/or optional parameters for request body as well as input validation tests | fasle| | routeParams | RouteParameter[] | An Array of RouteParameter instances, defining the types and validation rules for route parameters | fasle| | authenticate | boolean | When true, will mount the authentication function as middleware before other routes | fasle| | permissions | RoutePermissions | A RoutePermissions object for use with the package's standard access control function | fasle| | responseContentType | string | response content type - used to set headers | false | | errorHandlerOptions | ErrorHandlerOptions | Options that will be passed to the global error handler | | | redirectOnError | string | Optional redirect when error is handled by the route | false | | showHelp | boolean or (self: SuperRoute) => boolean | When set to true, will return an ascii output of the requested route's information when called with the OPTIONS http method. Example: curl --request OPTIONS https://localhost:8080/path/to/my/route | fasle| | responseFormat | string | response body type - used for automated testing | false | | responseReturnType | SuccessResponse |response format used for documentation | false |

Class: SuperRoute


Base Class for an express route super route Middleware order:

  1. Authentication function
  2. Access control
  3. Route Parameters validation
  4. Route Parameters validation
  5. Route Specific middleware defined in the middleware or versioned middleware arrays

Implements

  • RouteSettings

Methods

handle

handle(middlewareArgs: IArguments, errorOrMessage: string | Error | RouteError, statusCode?: number, respondWith?: string, log?: boolean, redirect?: string | false, options?: { [key: string]: any; }): void

handles errors in the route's logic.

errors can be channeled to a custom error handler specific to the route by defining $$errorHandler by default, a RouteError object containing route and request data will be created and passed to next(err)

Parameters:

| Name | Type | Default value | Description | | :------ | :------ | :------ | :------ | | middlewareArgs | IArguments | - | req, res, next from the express middleware function | | errorOrMessage | string | Error | RouteError | - | error message | | statusCode | number | 500 | html response status code | | respondWith? | string | - | optional custom error message to send as response | | log | boolean | false | log the error to the console if true | | redirect | string | false | - | redirect url | | options? | object | - | options object to pass to a custom error handler |

Returns: void

checkPermissions

StaticcheckPermissions(userPermissions: string | string[], permissions: RoutePermissions, hierarchy: string[]): boolean

checks if the user has the permissions defined in the permissions object and according to the defined hierarchy For use with an access control function.

Parameters:

| Name | Type | | :------ | :------ | | userPermissions | string | string[] | | permissions | RoutePermissions | | hierarchy | string[] |

Returns: boolean


mount

mount(router: Router): void

mounts the route on a router instance or express app

Parameters:

| Name | Type | | :------ | :------ | | router | Router |

Returns: void

Defined in: src/SuperRoute.ts:159


toMarkdown

toMarkdown(): string

Generates markdown documentation for hte route

Returns: string

Defined in: src/SuperRoute.ts:418


DefaultErrorHandler

StaticDefaultErrorHandler(err: RouteError RouteErrorI, req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: NextFunction): void

a default error handler to mount as the last middleware of the app

Parameters:

| Name | Type | | :------ | :------ | | err | RouteError RouteErrorI | | req | Request | | res | Response| | next | NextFunction |

Returns: void

Class: BodyParameter

Defines a parameter expected to be present in the request's body

constructor

new BodyParameter(name: string, type?: ParameterType, description?: string, required?: boolean, additionalTests?: RequestParameterTestFunction[]): BodyParameter

Constructs a BodyParameter instance

Parameters:

| Name | Type | Default value | Description | | :------ | :------ | :------ | :------ | | name | string | - | property name | | type | ParameterType | 'any' | the expected type of the parameter. if defined, will throw an error if the parameter's type doesn't match | | description | string | '' | text that will be displayed in the rendered help output | | required | boolean | true | if true, will throw an error when the property is missing | | additionalTests | RequestParameterTestFunction[] | [] | an array of additional test functions and their description | | nullable | boolean | false | when true, allows a required parameter to be null, ignoring it's type definition |

Class: RouteParameter

Defines a parameter expected to be present in the request's route

constructor

new RouteParameter(name: string, description?: string, required?: boolean, additionalTests?: [RequestParameterTestFunction](#Interface: RequestParameterTestFunction)[]): RouteParameter

Constructs a BodyParameter instance

Parameters:

| Name | Type | Default value | Description | | :------ | :------ | :------ | :------ | | name | string | - | property name | | description | string | '' | text that will be displayed in the rendered help output | | required | boolean | true | if true, will throw an error when the property is missing | | additionalTests | RequestParameterTestFunction[] | [] | an array of additional test functions and their description |

Interface: RequestParameterTestFunction

A test function and test description for testing request parameters

new BodyParameter('age', 'number', 'user age', true, [
  {
    test: (value) => value > 18,
    description: 'User must be over 18'
  }
])

| Name | Type | Description | | :------ | :------ | :------ | | test | test(value: any): boolean | a test function that receives the parameter value as an argument and returns a boolean | | description | string | an optional description of the test that will be displayed in the error output in case the function returns false |

Interface: AccessControlFunction

Access control function for SuperRoute settings When mounted, it will be called with the route's permissions and should return a RequestHandler

AccessControlFunction(permissions: RoutePermissions): RequestHandler

Parameters:

| Name | Type | | :------ | :------ | | permissions | RoutePermissions |

Interface: RoutePermissions

Access Control configuration for a route instance or extending class

  • equalOrGreaterThan - requester must have a permission level that is equal or greater than the given string as defined by the hierarchy array.

  • specific - requester must have all the given permissions

  • merge - when set to 'and' requester must satisfy both the specific an hierarchical rules.

Example:

{
  equalOrGreaterThan: 'admin';
  specific: ['specialPermission', 'awesomeDude'];
  merge: 'and'
}

will only grant access to admins that also have the specialPermission and awesomeDude permisions

Interface: ErrorHandlerOptions

Indexable

▪ [key: string]: any

Properties

log

Optional log: boolean


redirectOnError

Optional redirectOnError: string


Class: RouteError

A SuperRoute error with additional data

  • Error

    RouteError

constructor

+ new RouteError(message: string, statusCode: number, redirect?: null | string | false, log?: boolean): RouteError

Constructs a RouteError instance

Parameters:

| Name | Type | Default value | Description | | :------ | :------ | :------ | :------ | | message | string | - | Error message | | statusCode | number | - | Http status code | | redirect | null string false | false | tells the error handler if it should redirect the request | | log | boolean | false | tells the error handler to log the error - use it to override the default logging behaviour for specific errors when needed. |

Returns: RouteError

Properties

logError

logError: boolean


message

Error message

message: string

Inherited from: Error.message


redirect

Tells the error handler if it should redirect the request

redirect: string | false


requestPath

The path of the request that invoked the route that where the error was thrown

Optional requestPath: string


response

Optional response to replace the error message when sent to the user • Optional response: string


route

The path of the route where the error was thrown

Optional route: string


statusCode

Response status code

statusCode: number


Methods

handle

Handles a RouteError with the data of the given route. Example:

new RouteError('SomeError', 400, null, true).handle(this, req, res, next);

handle(route: SuperRoute, req: Request, res: Response, next: NextFunction): void

Parameters:

| Name | Type | | :------ | :------ | | route | any | | req | Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>> | | res | Response<any, Record<string, any>> | | next | NextFunction |

Returns: void

Defined in: src/RouteError.ts:66


respondWith

Attach a custom response to the RouteError object

respondWith(response: string): RouteError

Parameters:

| Name | Type | | :------ | :------ | | response | string |

Returns: RouteError

Defined in: src/RouteError.ts:61


FromError

Generate a RouteError instance from a vanilla Error instance.

StaticFromError(err: Error, statusCode: number, redirect?: null | string | false, log?: boolean): RouteError

Parameters:

| Name | Type | Default value | | :------ | :------ | :------ | | err | Error | - | | statusCode | number | - | | redirect | null string false | false | | log | boolean | false |

Returns: RouteError

Type aliases

ExpressHttpVerb

Available http verbs for super-route settings object

Ƭ ExpressHttpVerb: "get" | "post" | "put" | "head" | "delete" | "options" | "trace" | "copy" | "lock" | "mkcol" | "move" | "purge" | "propfind" | "proppatch" | "unlock" | "report" | "mkactivity" | "checkout" | "merge" | "m-search" | "notify" | "subscribe" | "unsubscribe" | "patch" | "search" | "connect"


ParameterType

Ƭ ParameterType: "string" | "number" | "boolean" | "object" | "array" | "parsableDateString" | "null" | "any"

Optional types for a body parameter

Defined in: src/RequestParameters.ts:93


SuccessResponse

response options

Ƭ SuccessResponse: "message" | "Array" | "object" | "file"