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

yaar

v3.0.2

Published

Yet Another API Router

Downloads

12

Readme

yaar

An API router to be used by an express application.

Basic usage

In this section, we will walk through basic usage for the library.

Instantiate an api router;

const { APIRouter, HTTPRPCInterface } = require('yaar');
const router = new APIRouter();

yaar does not listen for or accept connections itself---it is merely a routing layer. It must be used within an application that accepts express routers, like so:

const express = require('express');
const app = express();

app.use(router.getExpressRouter());

After making a router, create a version and add an interface to it:

router.version(1).addInterface(new HTTPRPCInterface());

Now you can create some methods!

router.register({
	method: 'some-method'
}, () => {
	return 'some response';
});

The APIRouter class is a CrispHooks emitter, and emits several events during the course of a request:

	router.on('request-begin', function(ctx) {
		console.log('Request placed to ' + ctx.method);
	});
	router.on('request-end', function(ctx) {
		console.log('Request to ' + ctx.method + ' completed');
	});
	router.on('request-error', function(ctx, error) {
		console.log('Request to ' + ctx.method + ' failed unexpectedly:');
		console.log(error);
	});

Versions

yaar allows for routes to be namespaced by version. To create a version, run:

router.version(1);
const versionTwo = router.version(2);

This returns a VersionRouter---a child router on which version-specific methods may be defined.

Methods

Methods are functions defined on routers, accessed via interfaces (as explained below). They are registered on the router, with the method name defined in an options object. Method names may be semantically sectioned by ., which can be utilized or ignored by specific interfaces. See the documentation on APICallRegistrar#register for an in-depth explanation of all accepted options.

router.register({
	method: 'foo.bar'
}, () => {
	return 'yay!';
});

// => { response: 'yay!' }

As shown above, a return value in a method will be used as a response.

Errors are sent in a similar fashion.

router.register({
	method: 'foo'
}, () => {
	throw new Error('no!');
});

// => { error: { code: 'internal_error', message: 'no!' } }

XError instances are explicitly supported, for convenience:

router.register({
	method: 'not_modified'
}, () => {
	throw new XError(XError.NOT_MODIFIED, 'I\'m afraid I can\'t do that.');
});

// => { error: { code: 'not_modified', message: 'I\'m afraid I can\'t do that.' } }

Methods may also be defined on specific VersionRouter instances, rather than the main router. Such methods will only be available under the relevant version namespace. VersionRouter#register functions the same as APIRouter#register.

Parameters

As stated above, parameters are exposed as ctx.params. If a method is registered with a schema, these params will be normalized to it. For example:

router.register({
	method: 'everything.ever',
	schema: createSchema({ foo: Number })
}, (ctx) => {
	return ctx.params;
});

If the above method were called with parameters { foo: '64' }, the response would be { result: { foo: 64 } }.

Versions

Methods may be defined to adhere to specific versions. The supported syntax is as follows:

router.register({
	method: 'everything.ever',
	versions: [ '-1', 3, '4-5', '7', '9-' ]
}, () => {
	return true;
});

This versions option accepts an array with any combination of the following valid items:

  • a number, referencing a specific version
  • a string, referencing a specific version
  • a range terminated on both ends, referencing a range of versions
  • a range terminated on only one end, referencing a range of versions

Promises

These methods support promises as well. Returning a Promise from a method will result in a success response if the promise resolves, and an error response if the promise rejects.

router.register({
	method: 'resolve'
}, () => {
	return new Promise((resolve) => {
		setImmediate(() => resolve({ foo: 'bar' }));
	});
});

// => { result: { foo: 'bar' } }
router.register({
	method: 'limit_exceeded'
}, () => {
	return new Promise((resolve, reject) => {
		setImmediate(() => reject(new XError(XError.LIMIT_EXCEEDED, 'STOP DOING THAT!')));
	});
});

// => { error: { code: 'limit_exceeded', message: 'STOP DOING THAT!' } }

Middleware

APIMiddleware

When you register a method on a router, you are really just defining a single middleware function. Multiple such middleware methods may be defined as follows:

router.register(
	{ method: 'foo' },
	(ctx) => { console.log('This route was just hit.'); },
	(ctx) => { ctx.someArray = []; },
	someOtherMiddleware,
	(ctx) => { ctx.someArray.push('foo'); },
	(ctx) => { ctx.someArray.push('bar'); },
	yetAnotherMiddleware,
	...
);

The ctx argument is made up of the following:

  • ctx.req: The express request.
  • ctx.res: The express response.
  • ctx.method: The name of the method.
  • ctx.params: Parameters to the called method.
  • ctx.version: The version of the router on which the method was called.
  • ctx.result: The result of the method, if one has been set.
  • ctx.error: The error of the method, if one has been set.
  • ctx.extraErrors: List of errors that have occurred in PostMiddleware functions.

ctx is shared across middleware functions in the same pipeline, and may thus be used for state:

router.register({
	method: 'foo'
}, (ctx) => {
	ctx.blah = 'blah';
}, (ctx) => {
	return ctx.blah;
});

PreMiddleware

PreMiddleware runs before relevant methods.

router.registerPreMiddleware({}, (ctx) => {
	ctx.someProp = 'foo';
});

PreMiddleware may also be defined per-version, by adding them to a versionRouter instead of the main router.

PostMiddleware

PostMiddleware runs before relevant methods. Errors in PostMiddleware functions are ignored.

router.registerPostMiddleware({}, (ctx) => {
	console.log(`The method ${ctx.method} was just hit.`);
});

PostMiddleware may also be defined per-version, by adding them to a versionRouter instead of the main router.

Interfaces

Interfaces transform requests/responses to a common format for API calls. They are added to VersionRouters, like so:

router.version(1).addInterface(new SomeAPIInterface());
versionTwo.addInterface(new SomeAPIInterface());

HTTPRPC

HTTPRPC is an interface for RPC over HTTP. This exposes the methods as routes accessible over HTTP. All methods are exposed as POST requests, with all method parameters passed in the POST body. All methods respond with a status code of 200, whether the responses contain errors or not. Methods will respond with a 500 status code if the requested endpoint is not found.

Consider the following method:

router.register({
	method: 'foo'
}, () => {
	return 'yay!';
});

This would be accessible via HTTP requests to /v1/rpc/foo, for example.

The HTTPRPC interface translates methods segmented by . into / in the HTTP endpoints. The following method would be accessible at /v1/rpc/foo/bar:

router.register({
	method: 'foo.bar'
}, () => {
	return 'yay!';
});

See the HTTPRPCInterface file for more information.

JSONRPC

JSONRPC is an interface for following JSON RPC standard over HTTP. Request posted to /${verion}/jsonrpc is handled by this interface. It accept request body like this:

{
	method: 'method.name',
	id: 'request.id',
	params: {
		foo: 'bar'
	}
}

It will always return with HTTP status 200 with following response:

{
	id: 'request.id',
	result: {
		key: 'value'
	},
	error: {
		code: 'error code',
		message: 'some error message'
	}
}

Note field result and error will not always exist. If an error is returned, result field should have an value of null. Likewise, error will be null if result is returned.

Some extra options on the register object for JSONRPC:

  • manualResponse: If set, and no route middlewares throw an error, no response will automatically be sent. The final middleware will be expected to directly manipulate the res object to construct the response.
  • streamingResponse: Use this option to stream a large number of objects back to the client. If set, the final route middleware is expected to return a readable object stream containing the response data, or a promise resolving with such a stream. Data from this stream will be converted into newline-separated JSON and written to the response. Once the stream terminates, a final special object will be written, containing a boolean flag success that indicates whether or not an error was thrown by the stream, and the error if applicable.
  • keepAlive: If set, and manualResponse is not set, the router will periodically insert a space into the response during downtime, in order to keep the connection open. Defaults to true.
  • keepAliveInterval: Specifies the length of time in ms to wait before sending a keep-alive space. Defaults to 10 seconds.

API Info

Yaar be implementin' yer API routes fer gettin' thar method infarrmation. Just call APIRouter#registerAPIInfoRoute() and thar be treasure called the "api-info" method.