Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/avoidwork/tenso
Tenso is an HTTP REST API framework
https://github.com/avoidwork/tenso
api api-gateway cqrs gateway microservice microservices rest
Last synced: 19 days ago
JSON representation
Tenso is an HTTP REST API framework
- Host: GitHub
- URL: https://github.com/avoidwork/tenso
- Owner: avoidwork
- License: bsd-3-clause
- Created: 2014-08-02T01:05:00.000Z (almost 10 years ago)
- Default Branch: master
- Last Pushed: 2023-12-28T14:37:06.000Z (6 months ago)
- Last Synced: 2024-05-14T14:22:14.138Z (about 1 month ago)
- Topics: api, api-gateway, cqrs, gateway, microservice, microservices, rest
- Language: JavaScript
- Homepage:
- Size: 3.79 MB
- Stars: 171
- Watchers: 8
- Forks: 11
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Lists
- awesome-stars - tenso
- awesome-stars - avoidwork/tenso - Tenso is an HTTP REST API framework (JavaScript)
- awesome-stars - avoidwork/tenso - Tenso is an HTTP REST API framework (JavaScript)
- awesome-list-microservice - tenso
README
Tenso
=====[![build status](https://secure.travis-ci.org/avoidwork/tenso.svg)](http://travis-ci.org/avoidwork/tenso)
Tenso is an HTTP REST API framework, that will handle the serialization & creation of hypermedia links; all you have to do is give it `Arrays` or `Objects`.
## Benchmark
1. Clone repository from [GitHub](https://github.com/avoidwork/tenso).
1. Install dependencies with `npm` or `yarn`.
1. Execute `benchmark` script with `npm` or `yarn`.## Example
Creating an API with Tenso can be this simple:```javascript
const path = require('path'),
app = require("tenso")({routes: require(path.join(__dirname, "routes.js"))});module.exports = app;
```### Creating Routes
Routes are loaded as a module, with each HTTP method as an export, affording a very customizable API server.You can use `res` to `res.send(body[, status, headers])`, `res.redirect(url)`, or `res.error(status[, Error])`.
The following example will create GET routes that will return an `Array` at `/`, an `Error` at `/reports/tps`, & a version 4 UUID at `/uuid`.
As of 10.3.0 you can specify `always` as a method to run middleware before authorization middleware, which will skip `always` middleware registered after it (via instance methods).
```javascript
const uuid = require("tiny-uuid4");module.exports.get = {
"/": ["reports", "uuid"],
"/reports": ["tps"],
"/reports/tps": (req, res) => res.error(785, Error("TPS Cover Sheet not attached")),
"/uuid": (req, res) => res.send(uuid(), 200, {"cache-control": "no-cache"})
};
```#### Protected Routes
Protected routes are routes that require authorization for access, and will redirect to authentication end points if needed.#### Unprotected Routes
Unprotected routes are routes that do not require authorization for access, and will exit the authorization pipeline early to avoid rate limiting, csrf tokens, & other security measures. These routes are the DMZ of your API! _You_ **must** secure these end points with alternative methods if accepting input!#### Reserved Route
The `/assets/*` route is reserved for the HTML browsable interface assets; please do not try to reuse this for data.### Request Helpers
Tenso decorates `req` with "helpers" such as `req.allow`, `req.csrf`, `req.ip`, `req.parsed`, & `req.private`. `PATCH`, `PUT`, & `POST` payloads are available as `req.body`. Sessions are available as `req.session` when using `local` authentication.Tenso decorates `res` with "helpers" such as `res.send()`, `res.status()`, & `res.json()`.
## Responses
Responses will have a standard shape, and will be utf-8 by default. The result will be in `data`. Hypermedia (pagination & links) will be in `links:[ {"uri": "...", "rel": "..."}, ...]`, & also in the `Link` HTTP header.Page size can be specified via the `page_size` parameter, e.g. `?page_size=25`.
Sort order can be specified via then `order-by` which accepts `[field ]asc|desc` & can be combined like an SQL 'ORDER BY', e.g. `?order_by=desc` or `?order_by=lastName%20asc&order_by=firstName%20asc&order_by=age%20desc`
```json
{
"data": "`null` or ?",
"error": "`null` or an `Error` stack trace / message",
"links": [],
"status": 200
}
```Final modifications can be made to a response body after `hypermedia()` by overriding `tenso.final(req, res, body)`.
## REST / Hypermedia
Hypermedia is a prerequisite of REST, and is best described by the [Richardson Maturity Model](http://martinfowler.com/articles/richardsonMaturityModel.html). Tenso will automagically paginate Arrays of results, or parse Entity representations for keys that imply
relationships, and create the appropriate Objects in the `link` Array, as well as the `Link` HTTP header. Object keys that match this pattern: `/_(guid|uuid|id|uri|url)$/` will be considered
hypermedia links.For example, if the key `user_id` was found, it would be mapped to `/users/:id` with a link `rel` of `related`.
Tenso will bend the rules of REST when using authentication strategies provided by passport.js, or CSRF if is enabled, because they rely on a session. Session storage is in memory, or Redis. You have the option of a stateless or stateful API.
Hypermedia processing of the response body can be disabled as of `10.2.0`, by setting `req.hypermedia = false` via middleware.
## Browsable API / Renderers / Serializers
Tenso 1.4.0 added a few common format renderers, such as CSV, HTML, YAML, & XML. The HTML interface is a browsable API! You can use it to verify requests & responses, or simply poke around your API to see how it behaves.Custom renderers can be registered with `server.renderer('mimetype', fn);` or directly on `server.renderers`. The parameters for a renderer are `(req, res, arg)`. Custom serializes can be registered with `server.serializer('mimetype', fn);` or directly on `server.serializers`. The parameters for a serializer are `(arg, err, status = 200, stack = false)`; if `arg` is `null` then `err` must be an `Error` & `stack` determines if the response body is the `Error.message` or `Error.stack` property.
## ParsersCustom renderers can be registered with `server.parser('mimetype', fn);` or directly on `server.parsers`. The parameters for a renderer are `(arg)`.
## Configuration
This is a sample configuration for Tenso, without authentication or SSL. This would be ideal for development, but not production! Enabling SSL is as easy as providing file paths for the two keys.```
{
"auth": { /* Optional, see Authentication section */
"delay": 0 /* Random delay on authorization validation */
},
"cacheSize": 1000, /* Optional, size of Etag & route LRU caches */
"cacheTTL": 0, /* Optional, TTL of items in Etag & route LRU caches */
"headers": {}, /* Optional, custom headers */
"hostname": "localhost", /* Optional, default is 'localhost' */
"index": ["index.htm", "index.html"], /* Files served when accessing a static assets folder */
"json": 0, /* Optional, default indent for 'pretty' JSON */
"logging": {
"level": "info", /* Optional */
"enabled": true, /* Optional */
"stack": false, /* Optional */
"stackWire": false /* Optional */
},
"corsExpose": "x-custom-header, x-custom-header-abc",
"origins": ["*"], /* Optional, allowed origins of CORS requests */
"port": 8000, /* Optional */
"routes": require("./routes.js"), /* Required! */
"regex": {
"hypermedia": "[a-zA-Z]+_(guid|uuid|id|url|uri)$", /* Optional, changes hypermedia detection / generation */
"id": "^(_id|id)$" /* Optional, changes hypermedia detection / generation */
},
"session": { /* Optional */
"secret": null,
"store": "memory", /* "memory" or "redis" */
"redis": {} /* See connect-redis for options */
},
"ssl": { /* Optional */
"cert": null,
"key": null
},
"renderHeaders": true, /* false will disable headers in HTML interface */
"title": "My API", /* Page title for browsable API */
"uid": 33 /* Optional, system account uid to drop to after starting with elevated privileges to run on a low port */
}
```## Authentication
The `protect` Array is the endpoints that will require authentication. The `redirect` String is the end point users will be redirected to upon successfully authenticating, the default is `/`.Sessions are used for non `Basic` or `Bearer Token` authentication, and will have `/login`, `/logout`, & custom routes. Redis is supported for session storage.
Multiple authentication strategies can be enabled at once.
Authentication attempts have a random delay to deal with "timing attacks"; always rate limit in production environment!
### Basic Auth
```
{
"auth": {
"basic": {
"enabled": true,
"list": ["username:password", ...],
},
"protect": ["/"]
}
}
```### JWT
JWT (JSON Web Token) authentication is stateless and does not have an entry point. The `auth(token, callback)` function must verify `token.sub`, and must execute `callback(err, user)`.This authentication strategy relies on out-of-band information for the `secret`, and other optional token attributes.
```
{
"auth": {
"jwt": {
"enabled": true,
"auth": function (token, cb) { ... }, /* Authentication handler, to 'find' or 'create' a User */
"algorithms": [], /* Optional signing algorithms, defaults to ["HS256", "HS384", "HS512"] */
"audience": "", /* Optional, used to verify `aud` */
"issuer: "", /* Optional, used to verify `iss` */
"ignoreExpiration": false, /* Optional, set to `true` to ignore expired tokens */
"scheme": "Bearer", /* Optional, set to specify the `Authorization` scheme */
"secretOrKey": ""
}
"protect": ["/private"]
}
}
```### Local
Local authentication will create `/login`. `auth(username, password)` must execute `callback(err, user)`.```
{
"auth": {
"local": {
"enabled": true,
"auth": function ( ... ) { ... }, /* Authentication handler, to 'find' or 'create' a User */
}
"protect": ["/private"]
}
}
```### OAuth2
OAuth2 authentication will create `/auth`, `/auth/oauth2`, & `/auth/oauth2/callback` routes. `auth(accessToken, refreshToken, profile, callback)` must execute `callback(err, user)`.
```
{
"auth": {
"oauth2": {
"enabled": true,
"auth": function ( ... ) { ... }, /* Authentication handler, to 'find' or 'create' a User */
"auth_url": "", /* Authorization URL */
"token_url": "", /* Token URL */
"client_id": "", /* Get this from authorization server */
"client_secret": "" /* Get this from authorization server */
},
"protect": ["/private"]
}
}
```### Oauth2 Bearer Token
```
{
"auth": {
"bearer": {
"enabled": true,
"tokens": ["abc", ...]
},
"protect": ["/"]
}
}
```### SAML
SAML authentication will create `/auth`, `/auth/saml`, & `/auth/saml/callback` routes. `auth(profile, callback)` must execute `callback(err, user)`.Tenso uses [passport-saml](https://github.com/bergie/passport-saml), for configuration options please visit it's homepage.
```
{
"auth": {
"saml": {
"enabled": true,
...
},
"protect": ["/private"]
}
}
```## Sessions
Sessions can use a memory (default) or redis store. Memory will limit your sessions to a single server instance, while redis will allow you to share sessions across a cluster of processes, or machines. To use redis, set the `store` property to "redis".If the session `secret` is not provided, a version 4 `UUID` will be used.
```
{
"session" : {
cookie: {
httpOnly: true,
path: "/",
sameSite: true,
secure: false
},
name: "tenso.sid",
proxy: true,
redis: {
host: "127.0.0.1",
port: 6379
},
rolling: true,
resave: true,
saveUninitialized: true,
secret: "tensoABC",
store: "memory"
}
}
```## Security
Tenso uses [lusca](https://github.com/krakenjs/lusca#api) for security as a middleware. Please see it's documentation for how to configure it; each method & argument is a key:value pair for `security`.```
{
"security": { ... }
}
```## Rate Limiting
Rate limiting is controlled by configuration, and is disabled by default. Rate limiting is based on `token`, `session`, or `ip`, depending upon authentication method.Rate limiting can be overridden by providing an `override` function that takes `req` & `rate`, and must return (a modified) `rate`.
```
{
"rate": {
"enabled": true,
"limit": 450, /* Maximum requests allowed before `reset` */
"reset": 900, /* TTL in seconds */
"status": 429, /* Optional HTTP status */
"message": "Too many requests", /* Optional error message */
"override": function ( req, rate ) { ... } /* Override the default rate limiting */
}
}
```## Limiting upload size
A 'max byte' limit can be enforced on all routes that handle `PATCH`, `POST`, & `PUT` requests. The default limit is 20 KB (20480 B).```
{
"maxBytes": 5242880
}
```## Logging
Standard log levels are supported, and are emitted to `stdout` & `stderr`. Stack traces can be enabled.```
{
"logging": {
"level": "warn",
"enabled": true,
"stack": true
}
}
```## Template
The browsable template can be overridden with a custom HTML document.```
{
"template": ""
}
```## Static assets folder
The browsable template can load assets from this folder.```
{
"static": "/assets"
}
```## Static assets cache
The browsable template assets have a default `public` cache of `300` seconds (5 minutes). These assets will always be considered `public`, but you can customize how long they are cacheable.```
{
"staticCache": 300
}
```## Custom static routes
Custom static routes can be defined like such:```
"/other": (req, res) => req.server.static(req, res);
```## EventSource streams
Create & cache an `EventSource` stream to send messages to a Client. See [tiny-eventsource](https://github.com/avoidwork/tiny-eventsource) for configuration options:```
const streams = new Map();...
"/stream": (req, res) => {
const id = req.user.userId;if (streams.has(id) === false) {
streams.set(id, req.server.eventsource({ms: 3e4), "initialized");
}streams.get(id).init(req, res);
}...
// Send data to Clients
streams.get(id).send({...});
```## Testing Code Coverage
Run the `coverage` script with `npm` or `yarn`.```console
-----------------|---------|----------|---------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------
All files | 79.05 | 69.1 | 66.18 | 78.81 |
tenso | 80 | 63.16 | 50 | 80 |
index.js | 80 | 63.16 | 50 | 80 | 21-24,33-34,53-54
tenso/lib | 79 | 69.35 | 66.42 | 78.75 |
base.js | 22.5 | 100 | 17.86 | 22.5 | 6-30,44-58,68-76,86-94
middleware.js | 89.86 | 77.36 | 83.33 | 91.18 | 13-14,43-44,87,132
parsers.js | 58.33 | 0 | 50 | 58.33 | 12-19
regex.js | 100 | 100 | 100 | 100 |
renderers.js | 97.3 | 68.75 | 93.33 | 100 | 10-19,35,41-48,58
serializers.js | 100 | 55.56 | 100 | 100 | 6-16
shared.js | 40 | 33.33 | 50 | 40 | 8-11
tenso.js | 73.39 | 53.62 | 65 | 72.9 | 167-168,181-191,199-206,248-249,262,296-298,306-312,322-341
utility.js | 83.33 | 74.37 | 81.13 | 82.97 | 34,46-49,53,72,118-120,137-138,147-148,152-153,187,203,207,218-219,228,236,238,249-250,271,301,315-337,339-361,382,388-392,438-439,498,543,567,629-630
-----------------|---------|----------|---------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------
```## License
Copyright (c) 2021 Jason MulliganLicensed under the BSD-3-Clause license.