npm package: @hitchy/plugin-odem-rest
Description: HTTP REST API for Hitchy's document-oriented database
Author: Thomas Urban
Latest version: 0.9.3

# plugin-odem-rest [![pipeline status](https://gitlab.com/hitchy/plugin-odem-rest/badges/master/pipeline.svg)](https://gitlab.com/hitchy/plugin-odem-rest/-/commits/master)

HTTP REST API for [Hitchy's](https://core.hitchy.org/) [document-oriented database](https://odem.hitchy.org/)

[Hitchy](http://core.hitchy.org/) is a server-side framework for developing web applications with [Node.js](https://nodejs.org/). [Odem](https://www.npmjs.com/package/@hitchy/plugin-odem) is a plugin for Hitchy implementing a document-oriented database using data backends like regular file systems, temporary in-memory databases and third-party key-value stores.
 
This plugin is defining blueprint routes for accessing data managed in document-oriented database using REST API.

## License

[MIT](LICENSE)

## Installation

In your Hitchy-based application run

```bash
npm i @hitchy/plugin-odem-rest @hitchy/plugin-odem
```

The command is installing this plugin and the additionally required [@hitchy/plugin-odem](https://www.npmjs.com/package/@hitchy/plugin-odem). 

:::warning Compatibility  
Starting with version 0.4.0 the latter plugin must be installed explicitly.  
:::

:::warning Compatibility  
Starting with version 0.6.0 authorization is tightened as soon as [@hitchy/plugin-auth](https://auth.hitchy.org) is discovered in current project. You need an [additional configuration](#authorization) to keep the REST API as open as before.  
:::

## Usage

This package depends on [@hitchy/plugin-odem](https://odem.hitchy.org/) and its preparation of model definitions discovered by [Hitchy's core](https://core.hitchy.org/). See the linked manuals for additional information.

In addition, [@hitchy/plugin-auth](https://auth.hitchy.org) is supported resulting in a tightened access control. When using that plugin in a project, a file **config/auth.js** with following content is necessary to test-drive this plugin's REST API:

```javascript
exports.auth = {
    authorizations: {
        "@hitchy.odem": "*"
    }
};
```

> **Do not use this in a production setup!**
> 
> This example is granting permissions to create, adjust and remove items of your document-oriented database without any authentication. In addition, all _protected_ models and properties get exposed to everyone.
> 
> See the [section on authorization](#authorization) for additional information!

Now, for a quick start example create a file **api/models/local-employee.js** in your Hitchy-based application with the following content:

```javascript
module.exports = {
	props: {
		lastName: {
			type: "string",
			required: true,
		},
		firstName: {
			type: "string",
			required: true,
		},
		birthday: {
			type: "date",
		},
		salary: {
			type: "number",
		},
		availableForOutsourcing: {
			type: "boolean",
		},
	},
	computed: {
		fullName() {
			return `${this.lastName}, ${this.firstName}`;
		}
	},
};
```

When starting your Hitchy-based application it will discover a model named **LocalEmployee** and expose it via REST API using base URL `/api/local-employee` just because of this package and its dependencies mentioned before.

#### Models of Hitchy plugins

Due to the way Hitchy is discovering plugins and compiling [components](https://core.hitchy.org/internals/components.html) defined there, this plugin is always covering models defined in installed plugins as well. Thus, any plugin is capable of defining additional models to be supported. In addition, some plugin may extend models defined by another plugin.


### How it works

This plugin is defining a set of [blueprint routes](https://core.hitchy.org/internals/routing-basics.html#focusing-on-routes) implementing a REST API for every model defined in file system as described before.

Those routes comply with this pattern:

* `<prefix>/<model>` is addressing a model or its collection of items
* `<prefix>/<model>/<uuid>` is addressing a single item of a model

The prefix is `/api` by default. It is adjustable by putting content like this into a file named **config/model.js**:

```javascript
exports.model = {
    urlPrefix: "/my/custom/prefix"
};
```

The model's segment in URL `<model>` is derived as the kebab-case version of model's name which is given in PascalCase. Thus, a model definition in a file named **api/models/my-fancy-model.js** is assumed to describe a model named **MyFancyModel** by default, resulting in model's URL segment to be **my-fancy-model** again. So the URL path for the collection of items is `/api/my-fancy-model`.

In Hitchy's document-oriented database all model instances or items are uniquely addressable via UUIDs. By appending an item's UUID to the given URL path of a collection you get the URL path of that item, e.g. `/api/my-fancy-model/01234567-1234-1234-1234-56789abcdef0`.


### The REST API

The provided routes implement these actions:

| Method | URL | Action                                                                                                                                   |
|---|---|------------------------------------------------------------------------------------------------------------------------------------------|
| GET | `/api/model` | Lists items of selected model.                                                                                                           |
| GET | `/api/model/<uuid>` | Fetches properties of selected item.                                                                                                     |
| PUT | `/api/model/<uuid>` | Replaces all properties of selected item with those given in request body. Selected item is created when missing.                        |
| PATCH | `/api/model/<uuid>` | Adjusts selected item by replacing values of properties given in request body (leaving those missing in request body untouched).         |
| POST | `/api/model` | Creates new item initialized with properties provided in request body.                                                                   |
| DELETE | `/api/model/<uuid>`  | Removes selected item from model's collection.                                                                                           |
| HEAD | `/api/model` | Tests if selected model exists.                                                                                                          |
| HEAD | `/api/model/<uuid>` | Tests if selected item exists.                                                                                                           |
| QUERY | `/api/model` | Fetches items of model matching JSON-encoded query in request body.<br><br>_Not working unless Node.js is supporting [HTTP QUERY method](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/?include_text=1)._ |

In addition, following URLs are available for accessing schema information:

| Method | URL | Action |
|---|---|---|
| GET | `/api/.schema` | Lists schemata of all published models. |
| GET | `/api/model/.schema` | Fetches schema of selected model. |

The API is accepting and returning data in JSON format. Any returned data is always an object. When fetching items this object contains single property `items` containing all fetched items as array.

Response status code indicates basic result of either requests.

| Status | Reason                                                                                                                                                                                                                   |
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 200 | A request was successful. In case of HEAD-request the tested model or item exists.                                                                                                                                       |
| 201 | A POST request was successful in creating another item. This is not used when creating new item using PUT request, though.                                                                                               |
| 400 | A given UUID is malformed.                                                                                                                                                                                               |
| 404 | A requested model or item was not found.                                                                                                                                                                                 |
| 405 | A given method is not allowed on selected model or item. This is basically a more specific information related to performing some invalid request like trying to PATCH or DELETE a whole model instead of a single item. |


### Convenience routes

By default, the module is exposing another set of routes for every model that enables requesting either supported action using GET-requests. This is assumed to be very useful in development e.g. to conveniently add or remove items using regular browser.

The URL path is extended to insert an action's name after the model's name and before some optionally given UUID.

| Convenience Route | Related REST Action |
|---|---|
| `GET /api/model/create` | `POST /api/model` |
| `GET /api/model/write/<uuid>` | `PATCH /api/model/<uuid>` |
| `GET /api/model/replace/<uuid>` | `PUT /api/model/<uuid>` |
| `GET /api/model/has/<uuid>` | `HEAD /api/model/<uuid>` |
| `GET /api/model/remove/<uuid>` | `DELETE /api/model/<uuid>` |

There are no extra routes following this pattern for actions that are exposed via GET-methods already.

All request data is provided in query parameters instead of request body for GET requests do not have a body.

#### Disabling feature

Disable this feature in the configuration file **config/model.js**:

```javascript
exports.model = {
    convenience: false,
};
``` 


### Extended fetching of items

Whenever fetching a list of items using GET request on a model's URL there are additional options for controlling the retrieved list.


#### Filtering

Using query parameter `q` the list of fetched items can be limited to those items matching criteria given in that simple query. The abbreviated name `q` just refers to a _search query_.

##### Simple comparisons

The search query may comply with the pattern `name:operation:value` to compare every item's property with a given value using one of these operations:
  
| Name | Test Operation           |
|------|--------------------------|
| eq   | is equal                 |
| neq  | is not equal             |
| lt   | is less than             |
| lte  | is less than or equal    |
| gt   | is greater than          |
| gte  | is greater than or equal |

For example, a GET-request for `/api/localEmployee?q=lastName:eq:Doe` will deliver all items of model **LocalEmployee** with property **lastName** equal given value **Doe**. The value may contain further colons.

##### Unary tests

Alternatively the search query may comply with the pattern `name:operation` for testing the named property using one of these supported operations:
  
| Name    | Test Operation                              |
|---------|---------------------------------------------|
| null    | property is set / has any value             |
| notnull | property is unset / does not have any value |

For example, a GET-request for `/api/localEmployee?q=lastName:null` will deliver all items of model **LocalEmployee** with unset property **lastName**.

##### Ternary tests

A third type of test operations are ternary tests. This refers to operations consisting of three parameters: the property's name and two values instead of one to compare that property's values with. Related queries comply with the pattern `name:operation:value:value`, hence using colon in first given value is not supported.
  
| Name    | Test Operation                                                 |
|---------|----------------------------------------------------------------|
| between | property's value is between the two given values (inclusively) |

For example, a GET-request for `/api/localEmployee?q=salary:between:2000:4000` will deliver all items of model **LocalEmployee** with value of property **salary** in range from 2000 to 4000.

##### Complex tests <Bade type=info text=v0.5.2+></Badge>

Hitchy's document-oriented database supports more complex queries that can not be encoded as such simple queries as described above. Thus, a different way of querying has been added. 

When using parameter `query` instead of `q`, its value is assumed to be a JSON-encoded query complying with query syntax supported by [Model.find() method of Hitchy's document-oriented database](https://odem.hitchy.org/api/model.html#model-find).

```http request
GET /api/user?query={"in":{"name":["john","jane","jason"]}}
```

_This example omits proper URL encoding of value to `query` parameter for illustration purposes. You should always encode queries._

In addition, support for upcoming [HTTP QUERY method](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/?include_text=1) has been prepared. However, this one does not work unless Node.js is accepting HTTP requests with method `QUERY`. 

```http request
QUERY /api/user
Content-Type: application/json

{"in":{"name":["john","jane","jason"]}}
```


#### Sorting

Using query parameter `sortBy=lastName` a fetched list of items is sorted by values of named property (which is `lastName` in this example) in ascending order. By providing another query parameter `descending=1` the sorting is done in descending order.


#### Slicing

Query parameter `limit=n` is requesting to fetch at most **n** items. Parameter `offset=n` is requesting to skip **n** items before starting retrieval. Slicing is applied after sorting items.

When slicing this way only a subset of basically available items is fetched by intention. If you need to know the total number of available items when requesting a slice you can either set custom field `x-count` in request header or query parameter `count` to `1` or any other truthy value. This will have a slight negative impact on request performance, but causes delivery of the total number of matching items in a separate property `count` of response body as well as in response header named `x-count`.

#### Authorization

As soon as [@hitchy/plugin-auth](https://auth.hitchy.org) is included with your project, **@hitchy/plugin-odem-rest** is using it to check a requesting user's authorization by declaring named [resources](https://auth.hitchy.org/introduction.html#resources) available for setting up authorization rules in [database](https://auth.hitchy.org/api/model/authorization-rule.html) or in [configuration](https://auth.hitchy.org/api/config.html#config-auth-authorizations).

##### Resource namespace

All declared resources share common prefix `@hitchy.odem`.

Despite the generic name, the authorization control described here does not apply to **@hitchy/plugin-odem** in general. Custom server-side code needs to stay capable of interacting with models and their private or protected properties. Instead, authorization control affects the REST API implemented by this plugin, only. Other plugins may implement APIs for accessing the same models by different means. Those plugins may share the resource naming pattern described here so that all APIs featuring remote interaction with the data can be controlled in the same way. Thus, resource names omit to refer to the REST API explicitly. 

##### Per-model resources

Resources are declared to control authorizations for basically interacting with either model:

  * When finding or listing items, accessing the resource `@hitchy.odem.model.<ModelName>.list` must be granted.
  * When checking an item, accessing the resource `@hitchy.odem.model.<ModelName>.check` must be granted.
  * When reading an item, accessing the resource `@hitchy.odem.model.<ModelName>.read` must be granted.
  * When patching or replacing an item, accessing the resource `@hitchy.odem.model.<ModelName>.write` must be granted.
  * When creating a new item, accessing the resource `@hitchy.odem.model.<ModelName>.create` must be granted.
  * When removing an item, accessing the resource `@hitchy.odem.model.<ModelName>.remove` must be granted.
  * Accessing a model's schema requires the resource `@hitchy.odem.model.<ModelName>.schema` to be granted.
  * Accessing the collection of all models' schemata requires the resource `@hitchy.odem.schema` to be granted. This implicitly causes models with their [promote option](https://odem.hitchy.org/guides/defining-models.html#options) being `protected` to be exposed in responses to that request unless resource `@hitchy.odem.model.<ModelName>.promote` is not revoked from the user.

> In these examples, replace `<ModelName>` with a model's name in PascalCase.

##### Per-property resources

In addition, resources are declared to control access on a model's protected properties. By default, protected properties are hidden from a user's REST request.

> Prior to supporting **@hitchy/plugin-auth** for authoriation control in v0.6.0, protected properties have been available to any authenticated user. The same behavior still applies if **@hitchy/plugin-auth** is not available in a project.

  * When finding or listing items, accessing the resource `@hitchy.odem.model.<ModelName>.property.<propertyName>.list` must be granted for the protected property to be supported in filter queries and to include it in resulting set of items.
  * When reading an item, accessing the resource `@hitchy.odem.model.<ModelName>.property.<propertyName>.read` must be granted to include the property in the resulting record.
  * When creating an item, accessing the resource `@hitchy.odem.model.<ModelName>.property.<propertyName>.create` must be granted to include the property in the resulting record.
  * When patching or replacing an item, accessing the resource `@hitchy.odem.model.<ModelName>.property.<propertyName>.write` must be granted to consider an update for the property and to include it in the resulting record.

> In these examples, replace `<ModelName>` with a model's name in PascalCase and `<propertyName>` with its name as defined in the model.

Per-property authorization control is not considered for public and private properties. This is meant to limit performance penalties on processing requests. 

  * Public properties are always available via REST API as soon as the according [per-model authorization](#per-model-resources) is given.
  * Private properties are never available.

##### Examples

The following example for a file **config/auth.js** is effectively disabling all authorization control in context of **@hitchy/plugin-odem-rest** and thus should be used for evaluation purposes, only:

```javascript
exports.auth = {
    authorizations: {
        "@hitchy.odem": "*"
    }
};
```

A slightly more sophisticated example could look like this:

```javascript
exports.auth = {
    authorizations: {
        "@hitchy.odem": "*",
        "@hitchy.odem.model.User": [ "-*", "@admin" ],
        "@hitchy.odem.model.Role": [ "-*", "@admin" ],
        "@hitchy.odem.model.User.property.password": [ "-*" ],
    }
};
```

This example is granting access to all features of **@hitchy/plugin-odem-rest** unless they address either model `User` or `Role`. Access on those models is limited to users with role `admin`. However, even those users must not access protected property `password`, though you might want to declare it as private anyway in which case last rule in given example does not have any effect.