npm package: @n7e/http-routing
Description: Provides HTTP routing middleware.
Author: Martin Pettersson
Latest version: 0.5.0

# HTTP Routing

Provides HTTP routing middleware.

For further insights, read on.

- [Installation](#installation)
- [Action Router Middleware](#action-router-middleware)
- [Action Router Middleware Builder](#action-router-middleware-builder)
    - [Routable Action](#routable-action)
    - [Routing Predicate](#routing-predicate)
    - [Routable Action Invocation](#routable-action-invocation)
- [Path Pattern](#path-pattern)
- [Decorators](#decorators)
    - [Request Method Decorators](#request-method-decorators)
    - [Decorator Metadata](#decorator-metadata)
    - [Routable Decorator Action](#routable-decorator-action)


## Installation

To install this library use your favorite package manager. No additional steps
are required to start using the library.

```shell
npm install @n7e/http-routing
```

*This library is implemented in TypeScript but can be used with JavaScript
without any additional steps.*


## Action Router Middleware

The provided action router middleware delegates incoming requests to a matching
[routable action](#routable-action). To create an action router middleware use
the provided [action router middleware builder](#action-router-middleware-builder).

Requests routed to a matching action will have the action added as an attribute
accessible at: `request.attributes.routableAction`.


## Action Router Middleware Builder

To create an action router middleware builder instance, simply import it and
create a new instance:

```typescript
import { ActionRouterMiddlewareBuilder } from "@n7e/http-routing";

const actionRouterMiddlewareBuilder = new ActionRouterMiddlewareBuilder();
```

If we already have or want to create a [routable action](#routable-action) we
can add it to the builder as is:

```typescript
actionRouterMiddlewareBuilder.addRoutableAction(routableAction);
```

Otherwise, the builder provides a method to create a
[routable action](#routable-action) from provided parameters:

```typescript
import { RequestMethod } from "@n7e/http";

actionRouterMiddlewareBuilder.add(
    RequestMethod.GET,
    "some/path",
    (request, parameters, signal) => { /* Produce an appropriate response. */ }
);
```

Once configured the builder will produce a middleware that will match incoming
requests to the registered actions if possible. If no matching action is found
the middleware will delegate to the provided request handler.

```typescript
const actionRouterMiddleware = actionRouterMiddlewareBuilder.build();
```

How requests are matched and actions invoked is decided by the builders
configured [routing predicate](#routing-predicate) and
[routable action invokation](#routable-action-invocation) respectively.


### Routable Action

Routable actions are the destination of incoming requests and are responsible
for producing appropriate HTTP responses. To match routable actions against
incoming requests routable actions has the following properties (besides the
action itself of course):

<dl>
    <dt>requestMethod</dt>
    <dd>HTTP request method to match against.</dd>
    <dt>pathExpression</dt>
    <dd>A regular expression to match against the HTTP URI path component.</dd>
    <dt>metadata</dt>
    <dd>
        Arbitrary metadata. This can be used to match requests or any other
        purpose the implementor feel is appropriate.
    </dd>
</dl>


### Routing Predicate

The logic behind how requests are matched against
[routable actions](#routable-action) can be configured by providing a predicate
function to the builder:

```typescript
actionRouterMiddlewareBuilder.useRoutingPredicate();
```

The predicate function receives the incoming request and a routable action and
should determine whether the request should be routed to the given routable
action.

The default behaviour is to check if the request method matches the routable
action request method and to test that the routable action path expression
matches the request URI path.

Here is an example of a routing predicate that matches request methods defined
in method override request headers.

```typescript
actionRouterMiddlewareBuilder.useRoutingPredicate(
    (request, routableAction) => (
        [
            request.method,
            request.headerFieldValueFor("X-Http-Method-Override")
        ].includes(routableAction.requestMethod) &&
        routableAction.pathExpression.test(request.uri.path)
    )
);
```


### Routable Action Invocation

How [routable actions](#routable-action) are invoked once an incoming requests
has been routed can be configured by providing a routable action invokation
function to the builder:

```typescript
actionRouterMiddlewareBuilder.useActionInvocation(invokationFunction);
```

The function receives the incoming request, [routable action](#routable-action)
and an abort signal and should invoke the [routable action](#routable-action)'s
action to produce an appropriate response.

The default behaviour is to extract parameters from the incoming request URI
path using the action router's path expression and invoke the action with the
request, parameters and abort signal.

*See [path pattern](#path-pattern) for how to extract parameters from route
expressions.*


## Path Pattern

When creating routable actions using the
[action router middleware builder](#action-router-middleware-builder) or using
[decorators](#decorators) you can use a special syntax to define which URI path
components a routable action should match against.

It looks really similar to a URI path component but with an additional  leading
`:` for parameters, a trailing `?` for optional parameters and an optional
regular expression enclosed in parentheses.

Here are some examples:

| Description                                    | Path Pattern                 |
|------------------------------------------------|------------------------------|
| Match path verbatim                            | `some/path`                  |
| Extract parameter                              | `some/:parameter`            |
| Extract optional parameter                     | `some/:parameter?`           |
| Extract parameter matching expression          | `some/:parameter(test-.+?)`  |
| Extract optional parameter matching expression | `some/:parameter(test-.+?)?` |

*Regular expressions used in path patterns are quite limited, most notably
anchors and capture groups are not supported.*

Path patterns can be converted to regular expressions using a provided function:

```typescript
import { pathExpressionFor } from "@n7e/http-routing";

const regularExpression = pathExpressionFor("/some/:parameter");
```

This will produce a regular expression that matches URI path components
appropriately and creates named groups for matched parameters.

Parameters can be extracted from path expressions using a provided function:

```typescript
import { extractRouteParametersFrom } from "@n7e/http-routing";

const parameters = extractRouteParametersFrom("some/path", pathExpression);
```

Optional parameters that are not present will be omitted from the parameters
object. Here's a concrete example:

```typescript
import { extractRouteParametersFrom, pathExpressionFor } from "@n7e/http-routing";

console.log(extractRouteParametersFrom("some/path", pathExpressionFor("/some/:parameter")));

// {parameter: "path"}
```


## Decorators

If you prefer an object oriented approach there are some decorators available to
easily create routable controllers. The decorators adds metadata to the
controller class that can be retrieved with a provided function:

```typescript
import { getDecoratorMetadataFor } from "@n7e/http-routing";

const metadata = getDecoratorMetadataFor(SomeController);
```

The metadata provided by the decorators has the following properties:

<dl>
    <dt>routePrefix</dt>
    <dd>Controller wide base path.</dd>
    <dt>routeMetadata</dt>
    <dd>Controller wide metadata.</dd>
    <dt>routableActions</dt>
    <dd><a href="#routable-decorator-action">Routable decorator actions</a> mapped to controller methods.</dd>
</dl>

Here's a simple example of a controller with a single routable decorator action:

```typescript
import { Request, RequestMethod, Response } from "@n7e/http";
import { request } from "@n7e/http-routing";

class SomeController {
    @request(RequestMethod.GET, "some/path")
    public someMethod(request: Request): Promise<Response> {
        // ...
    }
}
```

You can use the `@route()` decorator to provide a controller wide path prefix
like so:

```typescript
import { Request, RequestMethod, Response } from "@n7e/http";
import { request, route } from "@n7e/http-routing";

@route("some/route")
class SomeController {
    @request(RequestMethod.GET, "some/path")
    public someMethod(request: Request): Promise<Response> {
        // ...
    }
}
```

*Ultimately it's up to an implementation to add the registered decorator routes
to a routing mechanism. But the idea is that the controller route prefix should
prefix all route paths in the controller.*


### Request Method Decorators

For convenience there are dedicated decorators provided for common request
methods. The provided decorators are:

- **CONNECT**
- **DELETE**
- **GET**
- **HEAD**
- **OPTIONS**
- **PATCH**
- **POST**
- **PUT**
- **TRACE**

The functionality of these decorators are identical to that of `@request()` but
the request method is omitted:

```typescript
import { GET } from "@n7e/http-routing";
import type { Request, Response } from "@n7e/http";

class SomeController {
    @GET("some/path")
    public someMethod(request: Request): Promise<Response> {
        // ...
    }
}
```


### Decorator Metadata

The optional last argument to all decorators allows us to provide arbitrary
metadata to the routable decorator action.

```typescript
import { Request, RequestMethod, Response } from "@n7e/http";
import { request, route } from "@n7e/http-routing";

@route("some/route", {key: "value"})
class SomeController {
    @request(RequestMethod.GET, "some/path", {key: "value"})
    public someMethod(request: Request): Promise<Response> {
        // ...
    }
}
```

*The significance of this metadata is up to the implementations of the routing
mechanism.*


### Routable Decorator Action

Routable decorator actions are almost identical to
[routable actions](#routable-action) with two key differences:

<dl>
    <dt>pathPattern</dt>
    <dd>
        Instead of a regular expression routable decorator actions uses the
        <a href="#path-pattern">path pattern</a> syntax to match requests
        against.
    </dd>
    <dt>action</dt>
    <dd>
        To enable creative freedom in how to structure the application routable
        decorator actions only have one restriction, and that's to return a
        promise resolving with an HTTP response. Whatever parameters (if any)
        the action accepts or requires is up to the implementor.
    </dd>
</dl>