Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/beg-in/server
Node.js server-side organizational pattern
https://github.com/beg-in/server
Last synced: 19 days ago
JSON representation
Node.js server-side organizational pattern
- Host: GitHub
- URL: https://github.com/beg-in/server
- Owner: Beg-in
- License: mit
- Created: 2017-05-30T23:26:10.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2018-12-05T23:23:31.000Z (about 6 years ago)
- Last Synced: 2024-12-04T14:46:48.764Z (23 days ago)
- Language: JavaScript
- Size: 504 KB
- Stars: 1
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# begin-server
## Setup
### Requirements
- [Node >= v7.9](https://nodejs.org)
- [Postgresql](http://www.postgresql.org)
- [Redis](https://redis.io)```bash
$ npm install begin-server
```### Suggested Project Structure
Using this structure will allow automatic component discovery in the `src/server` directory.
Components are just a directory within `src/server` that have an `index.js` file.```
(project)
├── index.js
├── package.json
├── properties.js
└── src
├── client
├── server
│ ├── profile
│ │ ├── index.js
│ │ └── model.js
│ └── [additional component]
│ ├── index.js
│ └── model.js
└── shared
└── roles.js
```### Suggested configuration
Note: this uses the `roleAuthorizer` from the `auth` module, see below for details
`(project)/index.js`:
```js
'use strict';let server = require('begin-server');
let route = require('begin-server/route');
let auth = require('begin-server/auth');
let roles = require('./src/shared/roles');route.setAuthorizer(auth.roleAuthorizer(roles));
server.loadComponents();
server.listen();
````(project)/src/shared/roles.js`:
```js
'use strict';let roles = require('begin-server/roles');
module.exports = roles({
manager: [/* permissions here */],
// other roles here
});
```## Configuration Properties
### Details
- Create a file called `properties.js` in the root of your project
- Configuration properties cascade with `production` properties being the base configuartion and other environment types overriding if used
- Activate other environments using the `STAGE` environment variable, `production` by default
- Don't use destructuring on the properties module since it returns a Proxy object
- properties can be overriden with envrionment variables by substituting the path to the configuation property with underscores
- properties are used for many modules within this library, see each module's `Properties` section for options### Example
properties.js
```js
'use strict';module.exports = {
production: {
// the property domain in build is required
build: {
domain: 'example.com',
},// configuration options for modules in this library
server: {
mail: {
support: '[email protected]',
},
},// public properties in the begin-build package are merged in with all other properties
public: {
cdn: 'https://cdn.example.com',
},
},development: {
// these properties override production properties when STAGE=development
public: {
cdn: 'http://localhost:8080',
}
},
};
```retrieve a configuration property:
```js
let properties = require('begin-server/properties');let supportEmail = properties.mail.support();
let cdn = properties.cdn();
```### Specification
**properties**
Kind: `Proxy`
Use: `properties([default: Any [, devDefault: Any]]) => Any`
Returns: `Any` - the envrionment variable if available or configuration object at the current path
Arguments:
- default: `Any` - the default value to use when this property isn't present for this environment
- devDefault: `Any` - the defualut value to use when this property isn't present in development modeProperties:
- name: `String` - the name of the project defined in `package.json`
- domain: `String` - the domain of this server defined in `build`
- cwd: `String` - the components directory of the project
- build: `Object` - configuration properties defined in `build`
- isDevelopment: `Boolean` - true if development environment
- port: `Number` - the port to listen on when using the base module, default `8081`
- `listen.ip`: `String` - the ip address to listen on when using the base module, default `undefined`
- [path]: `properties` - returns a new proxy function to properties with the updated path## Controllers
### Details
Controller scripts are the entry point to a component of the server, they should act as a proxy to their associated Models.
All REST endpoint routes can be defined in a controller's `routes` function.Controller Object:
- Must be a plain object
- In the case of automatic component discovery, must be the `module.exports` of `index.js` in a component directory
- May have special propeties `routes` and `scope`
- May have any number arbitrary functions or properties
- The `scope` property should be an object that contains other controllers to bind to `this` on methods within the controller
- Controllers bound to scope will have a reference to the current request context
- Methods within the controller context will have the Express properties [`req`](https://expressjs.com/en/api.html#req) and [`res`](https://expressjs.com/en/api.html#res) bound to their `this`
- Avoid using lambdas with methods within the controller object to allow context binding### Example (with roleAuthorizer)
```js
'use strict';let ProfileController = require('../profile');
let MyModel = require('./model');module.exports = {
routes(api, { $open, $admin, $root, manager }) {
api.path('my-controller');
api($open).get(':id', ({ params }) => this.read(params.id));
api($admin).post(({ body }) => this.create(body));
api(manager).put(({ body }) => this.update(body));
api($root).delete();
},scope: {
ProfileController, // add another controller to the request context
},async read(id) {
let myModel = await MyModel.read(id);
// ...
return myModel.safe(); // serialized to JSON and respond with a 200 status code
},async create() {
let profile = await this.ProfileController.read(); // bound controller reference
// ...
},async update({ id, someProperty }) {
let oldModel = await this.read(id);
await oldModel.update({ someProperty });
// ...
},async delete() {
// ...
},
};
```### Routes
Details:
- The routes function should not be a lambda since its `this` is bound with special references to methods defined in the controller to initialize the request context
- When using `roleAuthorizer` from the `auth` module all routes require authentication by default, use `$open` as an argument to the `api` function to allow unauthenticated requests
- Async functions and promises will be resolved before a response is sent
- Returned objects will automaically be serialized to JSON unless the `noRes` option is set### Specification
**routes**
Use: `routes(api [, helpers])`
Arguments:
- api: `Function | Object` - route registration api object, see below
- helpers: `Object` - route helpers and authorization helpers defined by `route.setAuthorizer`, see below**api**
(Optional) use as `Function`: `api(authorization)`
Arguments
- authorization: `Function` - authorization callback function; with `roleAuthorizer` provided with a single argument `role: String` and must return `Boolean`; `true` will authorize the route and continue; `false` will throw to the route error handlerReturns
- api: `Object` - a new instance of the `api` object with the same properties belowUse as `Object`: `api[.path | .post | .get | .put | .delete | .other]`
Properties:
- `set(config: Object)` - pass special configuration options to the `route` module
- `path(endpoint: String)` - set the relative path for future uses of this api object
- argument `endpoint` - the new relative path to set for this controller's routes
- argument `MethodCallback` - see below
- `post([endpoint: String,] MethodCallback: Function)` -
- argument `endpoint` - the REST endpoint path for this route
- argument `MethodCallback` - see below
- `get([endpoint: String,] MethodCallback: Function)`
- argument `endpoint` - the REST endpoint path for this route
- argument `MethodCallback` - see below
- `put([endpoint: String,] MethodCallback: Function)`
- argument `endpoint` - the REST endpoint path for this route
- argument `MethodCallback` - see below
- `delete([endpoint: String,] MethodCallback: Function)`
- argument `endpoint` - the REST endpoint path for this route
- argument `MethodCallback` - see below
- `other(method: String, [endpoint: String,] MethodCallback: Function)`
- argument `method` - a different [method from Express](https://expressjs.com/en/api.html#routing-methods)
- argument `endpoint` - the REST endpoint path for this route
- argument `MethodCallback` - see below**MethodCallback**
Note: It is recommended to use a lambda expression here to avoid losing the `this` reference
Use: `callback([req [, res]])`
Arguments:
- (optional) req: `Object` - [request object from Express](https://expressjs.com/en/api.html#req)
- (optional) res: `Object` - [response object from Express](https://expressjs.com/en/api.html#res)**Helpers**
Properties:
- noRes: `Object` - object that can be passed to `api.set()` to disable the JSON response type
- [(with `roleAuthorizer`) - role `$helpers` see below]
- [(with a different authorizer) - properties provided by the authorizer's `helpers`]## Model
### Details
- in Model tables in the database there are two columns, `id` (primary key) and `data` (jsonb type)
- this class automatically extracts the property `_id` from models and uses this in the `id` column
- use the `init` function to create the table if it doesn't exist, this is safe to run multiple times
- The most used static and instance helper methods are the standard `CRUD` operations, i.e. `create`, `read`, `update`, and `delete`
- `read` is the only `CRUD` operation without an instance method### Example
#### Configuring a model
```js
'use strict';let Model = require('begin-server/model');
class MyModel extends Model {
static config() {
let config = super.config();
config.rules = {
};
config.validate = validate(config.rules);
config.protect = [
];
return config;
}static get
}MyModel.init();
module.exports = MyModel;
```#### Constructing a model in a controller and saving it to the database
```js
let MyModel = require('./model');module.exports = {
// ...
async create() {
// input object to constructor, properties not found in `rules` are discarded
let myModel = new MyModel({
prop: 'foo',
prop2: 'bar',
});
try {
await myModel.create();
} catch (e) { // ApiError
// this model has some invalid property
// or the database connection was interrupted
throw e;
}
// sanitize myModel by removing protected properties
return myModel.safe();
},
// ...
};
```#### Working with database queries
```js
let Model = require('begin-server/model');class MyModel extends Model {
...
static getFoo(arg, arg2) { // first create a class method
return this.query(`
select ${Model.JSONB}
from MyModel
where data->>'prop' = $1
and data->>'prop2' = $2;
`, [arg, arg2]);
}
...
});
// now prepared is available as a function.
let promise = MyModel.prepared(['value1', 'value2']);
// value1 will be inserted into the query at position "$1"
```#### ORM helpers from queries
```js
// of() transforms the result objects into MyModel type
let myModels = MyModel.getFoo().of();// of(T) transforms the result objects into type T
let ofType = MyModel.getFoo().of(MyOtherModel);// unique(err) transforms the result to just the first result
// and will reject with err if there is more than one
let uniqueDefault = MyModel.getFoo().unique(); // default error
let uniqueNoError = MyModel.getFoo().unique(null); // do not error, resolve null
let uniqueCustom = MyModel.getFoo().unique(apiError.conflict()); // custom error// unique() Can chain with of()
let uniqueOf = MyModel.getFoo().unique().of();// required(err) will reject with err if there is no result
let manyDefault = MyModel.getFoo().required(); // default error
let many = MyModel.getFoo().required(apiError.noContent()); // custom error// Can chain with of()
let manyOf = MyModel.getFoo().required().of();// required() Can chain with unique()
let requiredUnique = MyModel.getFoo().required().unique();
let requiredUniqueOf = MyModel.getFoo().required().unique().of();
```### Specification
**config**
Use: `config() => ModelConfig`
Kind: static method of Model
Details:
- call `super.config()` to get the base model configurationReturns: `ModelConfig` - the configuration for this Model
**ModelConfig**
Kind: `Object` interface of `config`
Properties:
- table: `String` - the name of this model's table in the database (defaults to class name)
- created: `Function => Any` - a function to define a `created` property on newly constructed models (`undefined` to omit)
- validate: `Function` - a function to validate newly constructed models (defaults to `false` and omitted)
- protect: `Array` - an array defining the properties to exclude when calling `safe()`**_id**
Kind: instance property of Model
Details:
- the primary key of this model**JSONB**
Kind: static constant of Model
Details:
- helper string that will combine the column `id` (primary key) with the column `data` to produce an object with the property `_id`**validate**
Use: `validate(obj: Object)`
Kind: static method of Model
Arguments:
- obj: `Object` - properties to validate or stripReturns: `Object` - a plain object with all properties validated and properties stripped that are not defined in `config.rules`
**init**
Use: `init() => void`
Kind: static method of Model
Details:
- Initialize the database table defined by `config()` of this model**genId**
Use: `genId() => String`
Kind: static method of Model
Returns: `String` - a cryptographically random ID that defines the `_id` property (primary key) of model instances (from `util.randomId`)
**create**
Use: `create() => Promise>`
Kind: instance method of Model
Details:
- Calls `validate` to first validate this instance and strip unknown properties
- Runs a create query on the database
- Will call `genId` to populate the property `_id` (primary key) if not presentReturns: `Promise>` - a promise that resolves to this model
**create**
Use: `create(obj: Object) => Promise>`
Kind: static method of Model
Arguments:
- obj: `Object` - properties to set on a new instance of this modelDetails:
- calls the constructor of this model and runs the `create` instance methodReturns: `Promise>` - a promise that resolves to a new model with properties from `obj`
**read**
Use: `read(id) => Promise>`
Kind: instance method of Model
Details:
- Runs a select query on the database matching on the `_id` property (primary key)Throws:
- `ApiError.fatal` - fatal error when there is no result or multiple conflicting resultsReturns: `Promise>` - a promise that resolves to this model
**update**
Use: `update([obj: Object]) => Promise>`
Kind: instance method of Model
Details:
- Calls `validate` to first validate this instance and strip unknown properties
- Runs an update query on the databaseArguments:
- (optional) obj: `Object` - an object with properties to validate and update on this instanceReturns: `Promise>` - a promise that resolves to the updated model
**update**
Use: `update(obj: Object) => Promise>`
Kind: static method of Model
Arguments:
- obj: `Object` - properties to validate and set on an updated instance of this modelDetails:
- Reads the model from the database using the `_id` property (primary key) and calls the instance method `update`Returns: `Promise>` - a promise that resolves to an updated model with properties from `obj`
**delete**
Use: `delete() => Promise`
Kind: instance method of Model
Details:
- Runs a delete query on the database
- calls the static method `delete` with the `_id` property (primary key)Returns: `Promise` - a promise with no resolve type
**delete**
Use: `delete(id: String) => Promise`
Kind: static method of Model
Arguments:
- id: `String` - the `_id` (primary key) of the instance to delete from the databaseReturns: `Promise` - a promise with no resolve type
**safe**
Use: `safe() => Object`
Kind: instance method of Model
Details:
- call this method on models before sending over the networkReturns: `Object` - a plain object with properties defined in `config.protect` stripped
**initId**
Use: `initId() => Model`
Kind: instance method of Model
Details:
- automatically called by `create` and will validatate that the property `_id` (primary key) does not exist in the tableThrows:
- `ApiError.fatal` - after a number of attempts to generate new ids, assume there is a logical error if this appears as it is statistically improbable this outcome is due to chanceReturns: `Model` - this model
**query**
Use: `query(queryString: String [, parameters: Array]) => Model`
Kind: instance method of Model
Details:
- See the `query` section belowArguments:
- queryString: `String` - the raw query string to run in the database, will be sanitized and prepared by [node-postgres](https://node-postgres.com/)
- parameters: `Array` - parameters to pass into the query via `$1`, `$2`, etc...Returns: `QueryPromise` - see below
#### Query
Detils:
- The query function returns a Promise with special functions called a QueryPromise**QueryPromise**
Extends: `Promise`
Properties:
- of: `Function([err: Error]) => Promise>>` - resolves as a list of this model type
- required: `Function([err: Error]) => RequiredQueryPromise>` - resolves a list of plain objects, thows `ApiError.notFound` or `err` if present with no result
- unique: `Function([err: Error]) => UniqueQueryPromise` - resolves a single plain object, throws `ApiError.conflict` or `err` if present with more than one result
- empty: `Function([err: Error]) => Promise` - throws `ApiError.conflict` or `err` if present when there is a result**RequiredQueryPromise**
Extends: `Promise`
Properties:
- of: `Function([err: Error]) => Promise>>` - resolves a list of this model type, thows `ApiError.notFound` or `err` if present with no result
- unique: `Function([err: Error]) => UniqueQueryPromise` - resolves a single plain object, throws `ApiError.conflict` or `err` if present with more than one result**UniqueQueryPromise**
Extends: `Promise`
Properties:
- of: `Function => Promise>` - resolves a single object of this model type, throws `ApiError.conflict` or `err` if present with more than one result### Putting it all together
```js
let MyModel = require('./model');module.exports = {
routes(api) {
api.path('my-endpoint');
api.get('foo', () => this.getFoo());
},async getFoo() {
try {
let myModel = await MyModel.getFoo('fooProp', 'fooProp2')
.required()
.unique()
.of();
// myModel will be an instance of MyModel since `of()` was used
return myModel.safe(); // sanitize output
} catch (e) { // ApiError
// result was non-unique or not present
}
},
};
```## Role
**$helpers**:
Note: these are used by the `roleAuthorizer`
- for use in the `routes` function in controllers
- exposed by the `helpers` argument
- consumed by the `api` functionProperties:
- $open: `Function => Boolean` - allow unauthenticated requests
- (pass to authorizer without calling)
- $hasRole: `Function => Boolean` - require the user to have a role
- (pass to authorizer without calling)
- $only: `Function => Function => Boolean` - specify specifc roles that are allowed
- Arguments: `$only(role: String [, role2: String [, ...]])` - the roles to allow
- $exclude: `Function => Function => Boolean`
- Arguments: `$exclude(role: String [, role2: String [, ...]])` - the roles to disallow
- $permission: `Function => Function => Boolean`
- Arguments: `$permissions(permission: String [, permission2: String [, ...]])` - the roles to disallow
- `root`: `Function` - only allow authenticated users with the `root` role
- (pass to authorizer without calling)
- `admin`: `Function` - allow authenticated users with the `admin` or `root` roles (pass to authorizer without calling)
- [additional roles]: `Function` - allow authenticated users with this role or a higher ranked role
- (pass to authorizer without calling)## Api Errors
API errors are defined in the `begin-util` package under the `error` module
All api error functions throw a special error type.
These should bubble up to your routes where they will result in a http response with the appropriate error code and will be formatted by the route error handler
### Example
```js
let error = require('begin-util/error');// custom message and status code
error('a message to reject with', 200);// Bad Request (400)
error(); // default message
error('a message to reject with'); // custom message// respond with Internal Server Error (204)
error.fatal();
error.fatal('a message to log');
error(new Error());// All `error` functions accept a single parameter for the message
error.badRequest('a message to reject with');// Additional `error` error functions
error.noContent(); // No Content (204)
error.badRequest(); // Bad Request (400)
error.unauthorized(); // Unauthorized (401)
error.paymentRequired(); // Payment Required (402)
error.forbidden(); // Forbidden (403)
error.notFound(); // Not Found (404)
error.methodNotAllowed(); // Method Not Allowed (405)
error.conflict(); // Conflict (409)
error.unsupportedMediaType(); // Unsupported Media Type (415)
error.serverError(); // Internal Server Error (500)
```### Specification
#### Error
Use: `error([message [, status]])`
Details:
- A helper function to call the constructor of `ApiError`
- All invocations of pre-defined error statuses have default messages that can be used when no `message` argument is provided
- `serverError` (500) status code will only ever send the default message to the client and will log any provided messageArguments:
- (optional) message: `String` - A message to return to the client for this HTTP error that overrides the default for this status type
- (optional) status: `Number` - Integer for the HTTP status codeReturns: `ApiError`
Properties:
- noContent: `ApiErrorFunction` - status code 204
- badRequest: `ApiErrorFunction` - status code 400
- unauthorized: `ApiErrorFunction` - status code 401
- paymentRequired: `ApiErrorFunction` - status code 402
- forbidden: `ApiErrorFunction` - status code 403
- notFound: `ApiErrorFunction` - status code 404
- methodNotAllowed: `ApiErrorFunction` - status code 405
- conflict: `ApiErrorFunction` - status code 409
- gone: `ApiErrorFunction` - status code 410
- unsupportedMediaType: `ApiErrorFunction` - status code 415
- serverError: `ApiErrorFunction` - status code 500
- fatal: `ApiErrorFunction` - status code 500
- ApiError: `ApiError` - the ApiError class
- isError: `Function => boolean` - test if an object is an instance of the ApiError class
- ERROR_CODES: `Object` - A key-value object containing each of the pre-defined status codes**ApiError**
Extends: `Error`
Properties:
- (optional) message: `String` - A message to return to the client for this HTTP error that overrides the default for this status type
- (optional) status: `Number` - Integer for the HTTP status code**ApiErrorFunction**
Use: `error.{status-type}([message]) => ApiError`
Arguments:
- (optional) message: `String` - A message to return to the client for this HTTP error that overrides the default for this status typeReturns: `ApiError`
Properties
- reject: `ApiErrorRejection` - helper function to send a rejected promise with this status type**ApiErrorRejection**
Use: `error.{status-type}.reject([message]) => Promise(rejected)`
Returns: `Promise(rejected)`
Arguments:
- (optional) message: `String` - A message to return to the client for this HTTP error that overrides the default for this status type## Log
Wrapper for [Winston](https://github.com/winstonjs/winston)
### Example
```js
log.debug('a debug message to log');
log.info('an info message to log');
log.warn('an warn message to log');
log.error('an error message to log');
```### Properties
- `log`: `Boolean` - whether to enable the Log module, default `true`
- `log.level`: `Number` - what level of logs pass to output, default `warn` in production, `debug` in development## App
The app module will simply provide a reference to the underlying Express app
This automatically applies the `CORS` module, compression, JSON body parsing, and a security library called Helmet
### Example
```js
let app = require('begin-server/app');
// app is a reference to the Express app object
app.use(/* put some middleware in the app */);
```### Properties
- `app.cors`: `Boolean` - a value of `false` will disable CORS middleware
- `app.helmet`: `Boolean` - a value of `false` will disable Helmet security middleware## Auth
The auth module handles security-based operations using Secure-Password and JWT
### Example
```js
let auth = require('begin-server/auth');module.exports = {
async passwords() {
// Password hashing and verification
const secret = 'super secret';
// these are async functions
let kdf = await auth.hash(secret);
await auth.verifyHash(kdf, secret)
},tokens() {
// JWT Token creation and verification
let token = auth.getToken({ sub: '1234' });
let { sub } = auth.decodeToken(token);
},
};
```### Properties
- `auth.issuer`: `String` - the issuer of JWT tokens defaults to `properties.domain`
- `auth.key`: `String` - a base64 string key to use to sign JWT tokens, defaults to a ES256 key in development mode
- IMPORTANT defaults to `undefined` in production, always specify a production key!
- `auth.public`: `String` - a base64 string key to use to verify JWT tokens, defaults to a public ES256 key in development mode
- IMPORTANT defaults to `undefined` in production, always specify a production public key!
- `auth.algorithm`: `String` - JWT algorithm to use, defaults to `ES512` in production, `ES256` in development
- `auth.version`: `String` - the jwtid to use on JWT tokens, defaults to `1.0`
- `auth.expiresIn`: `String` - a time string that specifies the duration until JWT's expire, defaults to `1 day`### Specification
**hash**
Kind: `Function`
Use: `hash(secret) => Promise`
Arguments:
- secret: `String` - the plaintext password to hashReturns: `String` - a Base64 representation of the hashed password containing parameters and salt
**verifyHash**
Kind: `Function`
Use: `verifyHash(kdf, secret [, improve]) => Promise`
Returns: `Boolean` - true if the hash matches, throws otherwise
Arguments:
- kdf: `String` - the Base64 string output from `hash`
- secret: `String` - the plaintext password to check against `kdf`
- improve: `async Function => Void` - a callback to run when the hash algorithm can be upgraded, callback called with a single argument `improvedKdf` - the new hash that can be savedThrows:
- `ApiError.badRequest` - when the password does not match the hash
- `ApiError.serverError` - when an unknown error occurs, the status code will be logged**access**
Kind: `Function`
Details:
- This function will save an access token to the cacheUse: `access(ctx, config) => Promise`
Arguments:
- ctx: `Object` - the request context containing `req` and `res` parameters
- config: `Object` - a token configuration object with payload parameters to add to the JWTReturns: `String` - a jwt token string
**revoke**
Kind: `Function`
Details:
- This function will remove an access token from the cacheUse: `revoke(access) => Void`
Arguments:
- access: `String` - the token to revoke**audience**
Kind: `Function`
Details:
- A helper function to help manage audience types in tokensUse: `audience(method)`
Returns: `String` - the audience string
Arguments:
- method: `String` - a unique identifier for the scope of this audience**getToken**
Kind: `Function`
Details:
- Generate a jsonwebtoken (JWT) stringUse: `getToken(payload [, options]) => String`
Returns: `String` - a string representation of the JWT token
Arguments:
- payload: `Object` - arbitrary parameters to save to this JWT
- (optional) options: `Object` - parameters defined by the [node-jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) libarary that change the behavior of the JWT**verify**
Kind: `Function`
Details:
- Validate a jsonwebtoken (JWT) stringUse: `verify(token [, options]) => Void`
Arguments:
- token: `String` - the JWT token string to validate
- (optional) options: `Object` - parameters defined by the [node-jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) libarary that change the behavior of the JWTThrows:
- `Error` - on invalid token based on provided options**roleAuthorizer**
Kind: `Function`
Use: `roleAuthorizer(roles) => Authorizer`
Details:
- A function to produce an authorizer using the provided roles and consumed by `route.setAuthorizer`Returns: `Authorizer` - a special function with helpers attached
Arguments:
- roles: `Object` - an object with keys in order of role heirarchy and with values of Arrays with strings of permissions## Cache
### Details
- The cache class uses [ioredis](https://github.com/luin/ioredis) under the hood
- The cache class should extend Model classes
- Use the standard CRUD operations on the Model class and instances will be syncronized in the cache
- By default cache items expire after 24 hours### Example
```js
let Cache = require('begin-server/cache');
let MyModel = require('./model');let CacheMyModel = cache(MyModel);
// The CRUD operations from Model are available and will interact with the cache
CacheMyModel.read('123');// Interact with the cache directly via get and set
Cache.get('mymodel_123');
```### Properties
- `cache.expires`: `Number` - amount of seconds until cache objects expire, default `86400` (1 day)
- `cache.url`: `String` - the host url of the cache server, default `localhost`### Specification
**cache**
Kind: `Function`
Use: `cache(model: Model) => CacheModel`
Returns: `CacheModel`
Arguments:
- model: `Model` - the model class to extend with cache operationsProperties:
- client: `Object` - an instance of ioredis connected to the cache server
- get: `Function => Promise` - function for retrieving keys from the cache, see below
- set: `Function => Void` - function for setting values in the cache, see below**CacheModel**
Kind: `Class`
Extends: `Model`
**get**
Kind: static method of `CacheModel`
Use: `get(key: String) => Object`
Returns: `Object` - the object at `key` in the cache
Arguments:
- key: `String` - the key for the object in the cache**set**
Kind: static method of `CacheModel`
Use: `set(key: String, val: Object) => Void`
Arguments:
- key: `String` - the key for the object to place in the cache
- val: `String` - the object to place in the cache**del**
Kind: static method of `CacheModel`
Use: `del(key: String) => Void`
Arguments:
- key: `String` - the key for the object to delete from the cache**cacheName**
Kind: static method of `CacheModel`
Use: `cacheName(id: String) => String`
Returns: `String` - a key to use for this class with the provided id
Arguments:
- id: `String` - the id to use**cacheName**
Kind: instance method of `CacheModel`
Use: `cacheName() => String`
Returns: `String` - a key to use for this class with this instance's `_id` parameter
**create**
Kind: instance method of `CacheModel`
Details:
- this will call the create method of the super class and will save this instance to the cache using `cacheName`Use: `create() => Promise>`
Returns: `CacheModel` - this instance of CacheModel
**read**
Kind: static method of `CacheModel`
Details:
- this will retrieve an instance from the cache using `cacheName` or if unsuccessfull will call the super `read` methodUse: `read(id: String) => Promise>`
Returns: `CacheModel` - the instance of CacheModel found in cache or returned from the super `read` method
Arguments:
- id: `String` - the id of the instance to retrieve**update**
Kind: instance method of `CacheModel`
Details:
- this will update an instance in the cache and will call the `update` method on the `super` classUse: `update([obj: Object]) => Promise>`
Returns: `CacheModel` - an updated instance of `CacheModel`
Arguments:
- (optional) obj: `Object` - an object with properties to update on this instance**delete**
Kind: static method of `CacheModel`
Details:
- this will delete an instance from the cache and will call the `delete` method on the `super` classUse: `delete(id: String) => Promise`
Arguments:
- id: `String` - the id of the instance to delete from the cache and the Model## CORS
### Details
- this is an Express middleware that allows for inteligent CORS operations
- Already used by app by default
- CORS headers are always sent in development mode
- Only allows CORS headers for the configured domain and subdomains### Example
```js
let app = require('begin-server/app');
let cors = require('begin-server/cors');// Already part of app, only shown here for demonstration purposes
app.use(cors);
```## Database
### Details
- A reference to a Pool from [node-postgres](https://node-postgres.com/)
- Already used by Model classes### Example
```js
let db = require('begin-server/db');
```### Properties
- `pg.host`: `String` - url of the database server to use, default `properties.name` in production, `localhost` in development
- `pg.password`: `String` - database password to use, default `undefined`
- `pg.user`: `String` - database user to use, default `properties.name` in production, `undefined` in development
- `pg.port`: `Number` - database server port to use, default `undefined`
- `pg.database`: `String` - database name to use, default `properties.name`### Details
- Uses [nodemailer](https://nodemailer.com/about/) preconfigured for use with Amazon SES (Simple Email Service)
- Uses the Template module to prepare Pug templates as HTML for emails### Example
```js
let mail = require('begin-server/mail');const template = require.resolve('./template.pug');
mail({
to: '[email protected]',
subject: 'Some Email Subject',
template,
options: {
from: '[email protected]', // The default from address is configured in properties
},
someLocalProp: 'localvalue', // additional fields are sent to the Template module as locals
}) // any additional arguments are passed to the template module
```### Properties
- `mail.name`: `String` - name to use as the From address in emails, default `properties.name`
- `mail.address`: `String` - email address to send from, default `info@{properties.domain}`
- `mail.support`: `String` - email address to use for customer support, default `mail.address` above### Specification
**mail**
Kind: `Function`
Use: `mail(config: MailConfig [, ...args]) => Promise`
Returns: `Promise` - resolved when the mail has been sent
Arguments:
- config: `MailConfig`
- args: `Any` - addional arguments are passed to the Template module**MailConfig**
Kind: `Object`
Properties:
- to: `String` - the address to send mail to
- subject: `String` - the email subject
- template: `String` - the full path to a Pug template to render using the Template module
- options: `Object` - Nodemailer configuration options including properties `from` (the sending address) and `html` (the raw html to use instead of rendering `template`)
- (optional) [addional properties]: `Any` - other properties are passed as locals to the template## Profile
### Details
- the Profile module is a fully featured component with a controller and a model
- The controller requires the use of `RoleAuthorizer` from the Auth module
- The controller uses the Cache, Auth, and Mail modules to handle login and profile creation
- Features and rest endpoints in the component are not documented here see `profile/index.js` and `profile/model.js` for more details### Example
Controller setup:
```js
let BaseController = require('begin-server/profile');
let Profile = require('./model');// the base controller will be a function that takes a single argument - the profile model class to use
let base = BaseController(Profile);module.exports = Object.assign({}, base, {
routes(api, helpers) {
let { $open } = helpers;
// set up the routes from BaseController
base.routes.call(this, api, helpers);
// add a new route
api.put('details', ({ body }) => this.updateDetails(body));
},// add a new function
async updateDetails({ firstName, lastName }) {
let profile = await this.read();
await profile.update({ firstName, lastName });
},
});
```Model setup:
```js
'use strict';let validate = require('begin-util/validate');
let BaseProfile = require('begin-server/profile/model');module.exports = class Profile extends BaseProfile {
// extend some of the configuration options
static config() {
// always initialize the config from the base class
let config = super.config();// rules is just an object
config.rules = Object.assign(config.rules, {
foo: validate.any,
});// always call validate if rules are changed
config.validate = validate(config.rules);// protect is just an array that can be concatenated with new properties
config.protect = config.protect.concat([
'foo',
]);
return config;
}// Add some prop that is allowed to be sent to the profile owner
static get SAFE_FOR_OWNER() {
return super.SAFE_FOR_OWNER.concat([
'foo',
]);
}
};// initilize the database table
module.exports.init();
```## Route
### Details
- Uses Express under the hood to register REST endpoints
- Automatically resolves promises (async functions) and converts Objects to JSON unless configured otherwise
- the Route module is automatically initialized by the base module when automatic component discovery is used
- The only function that is probably relevant to basic use is the `setAuthorizer` function which should be passed `roleAuthorizer` in most cases### Example
```js
let route = require('begin-server/route');
let log = require('begin-server/log');
let { roleAuthorizer } = require('begin-server/auth');
let roles = require('./src/shared/roles');
let controller = require('./src/server/some-component');route.setAuthorizer(roleAuthorizer(roles));
// manually register a controller and its associated routes
route.register(controller);// this is the default error handler, only here for demonstration purposes
route.setErrorHandler(log.error);```
## Template
### Details
- compile Pug templates and cache them when used### Example
```js
let template = require('begin-server/template');const file = require.resolve('./template.pug');
let compiled = template(file);
```## Util
### Details
- utility function module### Example
```js
let { randomId } = require('begin-server/util');let id = randomId();
```