This class provides Ravel, a lightweight but powerful framework for the rapid creation of enterprise Node.js applications which scale horizontally with ease and support the latest in web technology.
Extends AsyncEventEmitter
const Ravel = require('ravel');
const app = new Ravel();
The base class of Ravel Error
s, which associate
HTTP status codes with your custom errors.
const Ravel = require('ravel');
class NotFoundError extends Ravel.Error {
constructor (msg) {
super(msg, Ravel.httpCodes.NOT_FOUND);
}
}
The base class for Ravel DatabaseProvider
s. See
DatabaseProvider
for more information.
const DatabaseProvider = require('ravel').DatabaseProvider;
class MySQLProvider extends DatabaseProvider {
// ...
}
The base class for Ravel AuthenticationProvider
s. See
AuthenticationProvider
for more information.
const AuthenticationProvider = require('ravel').AuthenticationProvider;
class GoogleOAuth2Provider extends AuthenticationProvider {
// ...
}
A dictionary of useful http status codes. See HTTPCodes for more information.
const Ravel = require('ravel');
console.log(Ravel.httpCodes.NOT_FOUND);
The base decorator for Ravel Module
s. See
Module
for more information.
const Module = require('ravel').Module;
class MyModule {
// ...
}
module.exports = MyModule;
The base class for Ravel Routes
. See
Routes
for more information.
const Routes = require('ravel').Routes;
@Routes('/')
class MyRoutes {
// ...
}
module.exports = MyRoutes;
The current working directory of the app using the ravel
library.
Return the app instance of Logger.
See Logger
for more information.
app.$log.error('Something terrible happened!');
Value is true
iff init() or start() has been called on this app instance.
Value is true
iff listen()
or start()
has been called on this app instance.
The underlying HTTP server for this Ravel instance.
Only available after init()
(i.e. Use @postinit
).
Useful for testing with supertest
.
Type: http.Server
The underlying Keygrip instance for cookie signing.
Type: Keygrip
Register a provider, such as a DatabaseProvider or AuthenticationProvider.
(Function)
The provider class.
(...any)
Arguments to be provided to the provider's constructor
(prepended automatically with a reference to app).
Initializes the application, when the client is finished supplying parameters and registering modules, resources routes and rooms.
await app.init();
Starts the application. Must be called after initialize()
.
await app.listen();
Intializes and starts the application.
await app.start();
Stops the application. A no op if the server isn't running.
await app.close();
Register an application parameter.
Recursively register Module
s, Resource
s and Routes
with Ravel.
Useful for testing.
(...Function)
Class prototypes to load.
const inject = require('ravel').inject;
const Module = require('ravel').Module;
@Module('test')
class MyModule {
aMethod () {
//...
}
}
app.load(MyModule);
await app.init();
Recursively register Module
s, Resource
s and Routes
with Ravel,
automatically naming them (if necessary) based on their relative path.
All files within this directory (recursively) should export a single
class which is decorated with @Module
, @Resource
or @Routes
.
(...string)
The directories to recursively scan for .js files.
These files should export a single class which is decorated
with
@Module
,
@Resource
or
@Routes
.
// recursively load all `Module`s, `Resource`s and `Routes` in a directory
app.scan('./modules');
// a Module 'modules/test.js' in ./modules can be injected as `@inject('test')`
// a Module 'modules/stuff/test.js' in ./modules can be injected as `@inject('stuff.test')`
Module
s are plain old node.js modules exporting a single class which encapsulates
application logic and middleware. Module
s support dependency injection of core Ravel
services and other Module
s alongside npm dependencies (no relative require
's!).
Module
s are instantiated safely in dependency-order, and cyclical
dependencies are detected automatically.
(Array<any>)
Either a single string name for this module, or no arguments.
const inject = require('ravel').inject;
const Module = require('ravel').Module;
@Module
@inject('path', 'fs', 'custom-module', '$log')
class MyModule {
constructor (path, fs, custom, $log) {
this.path = path;
this.fs = fs;
this.custom = custom;
this.$log = $log;
}
aMethod () {
this.$log.info('In aMethod!');
//...
}
}
module.exports = MyModule
Provides Ravel with a simple mechanism for registering routes, which should generally only be used
for serving templated pages or static content (not for building RESTful APIs, for which Ravel.Resource
is more applicable). Clients decorate a class with this decorator to create a Routes
module.
Route paths are the concatenation of the basePath provided to @Routes(basePath)
, with the
subpath passed to @mapping(method, subpath)
. Route paths may incorporate complex patterns, such as:
'/foo/:id'
(:id
is captured as ctx.params.id
, and matches any chars but /
by default)'/:foo/:bar'
(ctx.params.foo
and ctx.params.bar
are both available)'/foo/bar-:id'
(:id
, without bar-
, is captured as ctx.params.id
)'/foo/:id(\\d+)'
'/foo/:id-:name'
(ctx.params.id
and ctx.params.name
are both available)'/foo/:id{-:name}?'
(matches /foo/12
and /foo/12-john
)catchAll
mode, /foo/:bar
matches /foo/bar
and /foo/bar/something/else
. This is a flag configured outside of
the route pattern. See @mapping
for configuration details.The routing tree is constructed to ensure predictability at runtime, according to the following rules:
/foo/:bar
and /foo/:car
)(string)
The base path for all routes in this class. Should be unique within an application.
const inject = require('ravel').inject;
const Routes = require('ravel').Routes;
const mapping = Routes.mapping;
const before = Routes.before;
// you can inject your own Modules and npm dependencies into Routes
@Routes('/') // base path for all routes in this class
@inject('koa-bodyparser', 'fs', 'custom-module')
class MyRoutes {
constructor (bodyParser, fs, custom) {
this.bodyParser = bodyParser(); // make bodyParser middleware available
this.fs = fs;
this.custom = custom;
}
// will map to /app
@mapping(Routes.GET, 'app')
@before('bodyParser') // use bodyParser middleware before handler
async appHandler (ctx) {
// ctx is a koa context object.
// await on Promises, and set ctx.body to create a body for response
// "OK" status code will be chosen automatically unless configured via ctx.status
// Extend and throw a Ravel.Error to send an error status code
}
}
module.exports = MyRoutes;
Used with the @mapping decorator to indicate the HEAD
HTTP verb.
const Routes = require('ravel').Routes;
const mapping = Routes.mapping;
@Routes('/')
class MyRoutes {
@mapping(Routes.HEAD, '/something')
async handler (ctx) {
//...
}
}
Used with the @mapping decorator to indicate the GET
HTTP verb.
const Routes = require('ravel').Routes;
const mapping = Routes.mapping;
@Routes('/')
class MyRoutes {
@mapping(Routes.GET, '/something')
async handler (ctx) {
//...
}
}
Used with the @mapping decorator to indicate the POST
HTTP verb.
const Routes = require('ravel').Routes;
const mapping = Routes.mapping;
@Routes('/')
class MyRoutes {
@mapping(Routes.POST, '/something')
async handler (ctx) {
//...
}
}
Used with the @mapping decorator to indicate the PUT
HTTP verb.
const Routes = require('ravel').Routes;
const mapping = Routes.mapping;
@Routes('/')
class MyRoutes {
@mapping(Routes.PUT, '/something')
async handler (ctx) {
//...
}
}
Used with the @mapping decorator to indicate the PATCH
HTTP verb.
const Routes = require('ravel').Routes;
const mapping = Routes.mapping;
@Routes('/')
class MyRoutes {
@mapping(Routes.PATCH, '/something')
async handler (ctx) {
//...
}
}
Used with the @mapping decorator to indicate the DELETE
HTTP verb.
const Routes = require('ravel').Routes;
const mapping = Routes.mapping;
@Routes('/')
class MyRoutes {
@mapping(Routes.DELETE, '/something')
async handler (ctx) {
//...
}
}
The @before
decorator for Routes
and Resource
classes.
See before
for more information.
The @transaction
decorator for Routes
and Resource
classes.
See transaction
for more information.
The @authenticated
decorator for Routes
and Resource
classes.
See authenticated
for more information.
What might be referred to as a controller in other frameworks, a Resource
module defines HTTP
methods on an endpoint, supporting the session-per-request transaction pattern via Ravel
middleware. Resource
s also support dependency injection, allowing for the easy creation of RESTful
interfaces to your Module
-based application logic. Resources are really just a thin
wrapper around Routes
, using specially-named handler functions (get
, getAll
, head
, headAll
,
post
, put
, putAll
, patch
, patchAll
, delete
, deleteAll
) instead of @mapping
. This
convention-over-configuration approach makes it easier to write proper REST APIs with less code,
and is recommended over "carefully chosen" @mapping
s in a Routes
class.
Omitting any or all of the specially-named handler functions is fine, and will result in a
501 NOT IMPLEMENTED
status when that particular method/endpoint is requested.
Resource
s inherit all the properties, methods and decorators of Routes
. See Routes
for more information. Note that @mapping
does not apply to Resources
.
For details concerning path construction, including how to employ parameters and regular expressions,
see Routes
.
(string)
The base path for all endpoints in this class. Should be unique within an application.
const inject = require('ravel').inject;
const Resource = require('ravel').Resource;
const before = Resource.before;
// you can inject your own Modules and npm dependencies into Resources
@Resource('/person')
@inject(koa-bodyparser', 'fs', 'custom-module')
class PersonResource {
constructor (bodyParser, fs, custom) {
this.bodyParser = bodyParser(); // make bodyParser middleware available
this.fs = fs;
this.custom = custom;
}
// will map to GET /person
@before('bodyParser') // use bodyParser middleware before handler
async getAll (ctx) {
// ctx is a koa context object.
// await on Promises, and set ctx.body to create a body for response
// "OK" status code will be chosen automatically unless configured via ctx.status
// Extend and throw a Ravel.Error to send an error status code
}
// will map to GET /person/:id
async get (ctx) {
// can use ctx.params.id in here automatically
}
// will map to HEAD /person
async headAll (ctx) {}
// will map to HEAD /person/:id
async head (ctx) {}
// will map to POST /person
async post (ctx) {}
// will map to PUT /person
async putAll (ctx) {}
// will map to PUT /person/:id
async put (ctx) {}
// will map to PATCH /person
async patchAll (ctx) {}
// will map to PATCH /person/:id
async patch (ctx) {}
// will map to DELETE /person
async deleteAll (ctx) {}
// will map to DELETE /person/:id
async delete (ctx) {}
}
module.exports = PersonResource;
The @mapping
decorator for Routes
classes does not work on Resources
.
Will throw an exception.
The @before
decorator for Routes
and Resource
classes.
See before
for more information.
The @transaction
decorator for Routes
and Resource
classes.
See transaction
for more information.
The @authenticated
decorator for Routes
and Resource
classes.
See authenticated
for more information.
Dependency injection decorator for Ravel Modules
,
Resources
and Routes
.
Since eliminating relative require()
s
has always been one of the core goals of the Ravel
framework, this decorator takes strings instead of
actual references to require()
d modules. This string
is either the name of the module to require, or the
registered name of a client Ravel Module
. Ravel Module
s
override npm modules.
This decorator simply annotates the class with modules which
should be injected when Ravel initializes. See util/injector
for the actual DI implementation.
// @inject works the same way on Modules, Resources and Routes
const inject = require('ravel').inject;
const Module = require('ravel').Module;
@@Module('mymodule')
@inject('path', 'fs', 'custom-module')
class MyModule {
constructor (path, fs, custom) {
super();
this.path = path;
this.fs = fs;
this.custom = custom;
}
aMethod() {
// can access this.fs, this.path or this.custom
}
}
Dependency injection decorator for Ravel Modules
,
Resources
and Routes
.
Extremely similar to @inject, but will automatmically
assign the injected components to values on the current scope,
based on name, after construction (this means that these values
will not be available in the construtor). Injected modules can be
locally renamed by supplying an alias.
This decorator simply annotates the class with modules which
should be injected when Ravel initializes. See util/injector
for the actual DI implementation.
// @autoinject works the same way on Modules, Resources and Routes
const autoinject = require('ravel').autoinject;
const postinject = require('ravel').postinject;
const Module = require('ravel').Module;
@@Module('mymodule')
@autoinject('path', {'fs': 'myfs'}, 'custom-module')
class MyModule {
constructor () {
// this.myfs, this.path and this['custom-module']
// are not available in the constructor, and will
// will be populated after construction time.
// If you set any properties on this with identical
// names, they will be overriden.
}
aMethod() {
// can access this.myfs, this.path or this['custom-module']
}
@postinject
init () {
// Since autoinjected modules are not present in the constructor,
// @postinject-decorated methods provide an alternative way to
// consume injectables for initialization immediately
// after instantiation.
}
}
Core Ravel services available for injection by name into any @Module
s, @Resource
s or @Routes
.
@Module
@inject('$log')
class MyModule {
constructor ($log) {
this.$log = $log;
}
aMethod () {
this.$log.info('In aMethod!');
//...
}
}
A reference to the ravel instance with which this Module is registered.
Ravel's pre-packaged error types. When thrown, they will trigger a corresponding HTTP response code.
Type: Ravel.Error
A logger for this Module
, Resource
or Routes
.
Will log messages prefixed with the name or basepath
of the Module
or Resource
/Routes
.
See Logger
for more information.
this.$log.trace('A trace message');
this.$log.verbose('A verbose message');
this.$log.debug('A debug message');
this.$log.info('A info message');
this.$log.warn('A warn message');
this.$log.error('A error message');
this.$log.critical('A critical message');
// string interpolation is supported
this.$log.info('Created record with id=%s', '42');
// Errors are supported
this.$log.error('Something bad happened!', new Error('Ahh!'));
A reference to the internal Ravel key-value store connection (redis). See node-redis for more information.
Since this is Ravel's own internal, long-lived connection, it is important that
it not be blocked or suspended by calls to exit
, subscribe
, psubscribe
,
unsubscribe
or punsubscribe
.
To retrieve an unrestricted connection to Redis, use kvstore.clone()
.
A service which exposes an Object with a get() method,
which allows easy access to app.get().
See Ravel.get
for more information.
this.$params.get('some ravel parameter');
Most Ravel database connections are retrieved using the transaction-per-request
pattern (see transaction
).
If, however, your application needs to initiate a connection to a database which
is not triggered by an HTTP request (at startup, for example) then this method is
the intended approach. This method is meant to yield connections in much the same
way as @transaction
. See the examples below and
TransactionFactory
for more details.
const Ravel = require('ravel');
const inject = Ravel.inject;
const Module = Ravel.Module;
const prelisten = Module.prelisten;
@Module
@inject('$db')
class MyModule {
constructor($db) { this.$db = $db; }
// in this example, database initialization is
// performed at application start-time via
// the // @prelisten decorator
@prelisten
doInitDb () {
// open connections to specific, named database providers.
// like // @transaction, you can also supply no names (just
// the async function) to open connections to ALL registered
// DatabaseProviders
this.$db.scoped('mysql', 'rethinkdb', async function (ctx) {
// can use ctx.transaction.mysql (an open connection)
// can use ctx.transaction.rethinkdb
});
}
}
The Ravel websocket service. Handles topic subscriptions and publishing. See Websockets for more information.
this.$ws.subscibe('my.topic', ctx); // ctx is a context object in a Routes or Resource handler
this.$ws.unsubscribe('my.topic', ctx); // ctx is a context object in a Routes or Resource handler
this.$ws.publish('my.topic', messageBuffer); // messages are binary buffers
Hash of HTTP Response Codes, available as require('ravel').httpCodes
.
Useful for creating custom error types.
HTTP MULTIPLE CHOICES.
number
:
The HTTP status code 300.
HTTP MOVED PERMANENTLY.
number
:
The HTTP status code 301.
HTTP TEMPORARY REDIRECT.
number
:
The HTTP status code 307.
HTTP PERMANENT REDIRECT.
number
:
The HTTP status code 308.
HTTP PAYMENT REQUIRED.
number
:
The HTTP status code 402.
HTTP METHOD NOT ALLOWED.
number
:
The HTTP status code 405.
HTTP PROXY AUTHENTICATION REQUESTED.
number
:
The HTTP status code 407.
HTTP PRECONDITION FAILED.
number
:
The HTTP status code 412.
HTTP REQUEST ENTITY TOO LARGE.
number
:
The HTTP status code 413.
HTTP REQUEST URI TOO LONG.
number
:
The HTTP status code 414.
HTTP UNSUPPORTED MEDIA TYPE.
number
:
The HTTP status code 415.
HTTP REQUESTED RANGE NOT SATISFIABLE.
number
:
The HTTP status code 416.
HTTP EXPECTATION FAILED.
number
:
The HTTP status code 417.
HTTP AUTHENTICATION TIMEOUT.
number
:
The HTTP status code 419.
HTTP UNPROCESSABLE ENTITY.
number
:
The HTTP status code 422.
HTTP FAILEDDEPENDENCY.
number
:
The HTTP status code 424.
HTTP UPGRADE REQUIRED.
number
:
The HTTP status code 426.
HTTP PRECONDITION REQUIRED.
number
:
The HTTP status code 428.
HTTP TOO MANY REQUESTS.
number
:
The HTTP status code 429.
HTTP REQUEST HEADER FIELDS TOO LARGE.
number
:
The HTTP status code 431.
HTTP BLOCKED BY WINDOWS PARENTAL CONTROLS.
number
:
The HTTP status code 450.
HTTP UNAVAILABLE FOR LEGAL REASONS.
number
:
The HTTP status code 451.
HTTP REQUEST HEADER TOO LARGE.
number
:
The HTTP status code 494.
HTTP TOKEN EXPIRED INVALID.
number
:
The HTTP status code 498.
HTTP CLIENT CLOSED REQUEST.
number
:
The HTTP status code 499.
HTTP INTERNAL SERVER ERROR.
number
:
The HTTP status code 500.
HTTP SERVICE UNAVAILABLE.
number
:
The HTTP status code 503.
HTTP HTTP VERSION NOT SUPPORTED.
number
:
The HTTP status code 505.
HTTP VARIANT ALSO NEGOTIATES.
number
:
The HTTP status code 506.
HTTP INSUFFICIENT STORAGE.
number
:
The HTTP status code 507.
HTTP BANDWIDTH LIMIT EXCEEDED.
number
:
The HTTP status code 509.
HTTP NETWORK NETWORK AUTHENTICATION REQUIRED.
number
:
The HTTP status code 511.
HTTP WEB SERVER IS DOWN.
number
:
The HTTP status code 521.
HTTP CONNECTION TIMED OUT.
number
:
The HTTP status code 522.
HTTP PROXY DECLINED REQUEST.
number
:
The HTTP status code 523.
HTTP A TIMEOUT OCCURRED.
number
:
The HTTP status code 524.
The @mapping
decorator for Routes
classes. Indicates that
the decorated method should be mapped as a route handler using koa
.
Can also be applied at the class-level to indicate routes which do nothing except return a particular status code.
(symbol)
An HTTP verb such as
Routes.HEAD
,
Routes.GET
,
Routes.POST
,
Routes.PUT
,
Routes.PATCH
, or
Routes.DELETE
.
(string)
The path for this endpoint, relative to the base path of the Routes class.
(object
= {}
)
Options for this mapping.
Name | Description |
---|---|
config.status (number | undefined)
|
A status to always return, if this is applied at the class-level. Not supported at the method level. |
config.suppressLog (boolean | undefined)
|
Don't log a message describing this endpoint iff true. |
config.catchAll (boolean | undefined)
|
Whether or not this is a catch-all mapping, matching routes with additional path components after the initial pattern match. This parameter is only supported at the method-level. |
const Routes = require('ravel').Routes;
const mapping = Routes.mapping;
@Routes('/')
class MyRoutes {
// will map to /projects
@mapping(Routes.GET, 'projects')
async handler (ctx) {
// ctx is a koa context object
}
}
const Ravel = require('ravel');
const Routes = Ravel.Routes;
const mapping = Routes.mapping;
// class-level version will create a route 'DELETE /' which responds with 501 in this case
@mapping(Routes.DELETE, 'projects', { status: Ravel.httpCodes.NOT_IMPLEMENTED })
@Routes('/')
class MyRoutes {
}
The @before
decorator for Routes
and Resource
classes. Indicates that
certain middleware should be placed on the given route before the method
which is decorated.
Can also be applied at the class-level to place middleware before all
@mapping
handlers.
References any middleware AsyncFunction
s available on this
.
// Note: decorator works the same way on Routes or Resource classes
const inject = require('ravel').inject;
const Routes = require('ravel').Routes;
const mapping = Routes.mapping;
const before = Routes.before;
@Routes('/')
@inject('koa-bodyparser')
class MyRoutes {
constructor (bodyParser) {
this.bodyParser = bodyParser();
}
@mapping(Routes.GET, '/projects/:id')
@before('bodyParser') // method-level version only applies to this route
async handler (ctx) {
// in here, bodyParser will already have run,
// and ctx.request.body will be populated
}
}
// Note: decorator works the same way on Routes or Resource classes
const inject = require('ravel').inject;
const Routes = require('ravel').Resource;
const before = Resource.before;
@Resource('/')
@inject('koa-bodyparser')
@before('bodyParser') // class-level version applies to all routes in class.
class MyResource {
constructor (bodyParser) {
this.bodyParser = bodyParser();
}
async get(ctx) {
// in here, bodyParser will already have run,
// and ctx.request.body will be populated
}
}
The @cache
decorator for Routes
and Resource
classes. Indicates that
response caching middleware should be placed on the given route before the method
which is decorated.
Can also be applied at the class-level to place caching middleware before all
@mapping
handlers.
References any middleware AsyncFunction
s available on this
.
See cache
for more information.
(...Any)
Options for the caching middleware, or undefined.
// Note: decorator works the same way on Routes or Resource classes
const Routes = require('ravel').Routes;
const mapping = Routes.mapping;
const cache = Routes.cache;
@Routes('/')
class MyRoutes {
@cache // method-level version only applies to this route
@mapping(Routes.GET, '/projects/:id')
async handler (ctx) {
// The response will automatically be cached when this handler is run
// for the first time, and then will be served instead of running the
// handler for as long as the cached response is available.
}
}
// Note: decorator works the same way on Routes or Resource classes
const Resource = require('ravel').Resource;
const cache = Resource.cache;
// class-level version applies to all routes in class, overriding any
// method-level instances of the decorator.
@cache({expire:60, maxLength: 100}) // expire is measured in seconds. maxLength in bytes.
@Resource('/')
@inject(koa-bodyparser')
class MyResource {
constructor (bodyParser) {
this.bodyParser = bodyParser();
}
async get(ctx) {
// The response will automatically be cached when this handler is run
// for the first time, and then will be served instead of running the
// handler for as long as the cached response is available (60 seconds).
}
}
The @transaction
decorator for opening a transaction on a Routes
or
Resource
handler method. Facilitates transaction-per-request.
Can also be applied at the class-level to open connections for all handlers
in that Route
or Resource
class.
Connections are available within the handler as an object ctx.transaction
, which
contains connections as values and DatabaseProvider
names as keys. Connections
will be closed automatically when the endpoint responds (do not close them yourself),
and will automatically roll-back changes if a DatabaseProvider
supports it (generally
a SQL-only feature).
(...string)
A list of database provider names to open connections for.
// Note: decorator works the same way on Routes or Resource classes
const Routes = require('ravel').Routes;
const mapping = Routes.mapping;
@Routes('/')
class MyRoutes {
@mapping(Routes.GET, 'app')
@transaction // open all connections within this handler
async appHandler (ctx) {
// ctx.transaction is an object containing
// keys for DatabaseProviders and values
// for their open connections
}
@mapping(Routes.GET, 'something')
@transaction('mysql', 'rethinkdb') // open one or more specific connections within this handler
async somethingHandler (ctx) {
// can use ctx.transaction.mysql
// can use ctx.transaction.rethinkdb
}
}
// Note: decorator works the same way on Routes or Resource classes
const Resource = require('ravel').Resource;
@transaction('mysql') // all handlers will have ctx.transaction.mysql
@Resource('/')
class MyResource {
async post (ctx) {
// can use ctx.transaction.mysql
}
}
Defines an abstract DatabaseProvider - a mechanism by which connections can be obtained and transactions can be entered and exited for a particular database provider such as MySQL (see ravel-mysql-provider for an example implementation). Override this class to create your own DatabaseProvider.
(Ravel)
An instance of a Ravel application.
(string)
A unique name for this
DatbaseProvider
. A default can
be provided by overriding this constructor and using ES2015
default argument value syntax. Must be unique within
your application.
string
:
The unique name of this DatabaseProvider.
Try to pick something unique within the Ravel ecosystem.
Anything that should happen just before a Ravel application starts listening
(app.listen()
) should happen here. If you override this hook, be sure to call super()
.
This function should return nothing. It is a good place to start connection pools.
(Ravel)
An instance of a Ravel application.
End a transaction and close the connection. Rollback the transaction iff finalErr !== null.
(object)
A connection object which was used throughout the transaction.
(boolean)
If true, commit, otherwise rollback.
Promise
:
Resolved, or rejected if there was an error while closing the connection.
The @authenticated
decorator adds authentication middleware before an endpoint
within a Routes
or Resource
class, requiring that a user have valid credentials
before they can access the decorated endpoint(s).
Assumes that you've require
'd an AuthenticationProvider
, and that you
have set up an @authconfig
Module
.
If an object is passed as an argument to the decorator, it is used for configuration purposes, and supports the following keys:
boolean config.redirect
true iff should automatically redirect to app.get('login route')
if user is not signed in.boolean config.register
register user automatically if they are not registered.
(rather than failing and throwing an $err.Authentication
).See authconfig
and
AuthenticationProvider
for more information.
((Class | Method | object))
A configuration object (returning a decorator), or the Method
or Class to directly apply this decorator to.
// Note: decorator works the same way on Routes or Resource classes
const Routes = require('ravel').Routes;
const mapping = Routes.mapping;
const authenticated = Routes.authenticated;
@Routes('/')
class MyRoutes {
@authenticated({redirect: true}) // works at the method-level, with or without arguments
@mapping(Routes.GET, 'app')
async handler (ctx) {
// will redirect to this.$params.get('login route') if not signed in
}
}
// Note: decorator works the same way on Routes or Resource classes
const Resource = require('ravel').Resource;
const authenticated = Resource.authenticated;
@authenticated // works at the class-level as well (with or without arguments)
@Resource('/')
class MyResource {
async handler (ctx) {
// will respond with a 401 if not signed in
}
}
A decorator for a Module
, indicating that it will offer
specific functions which encapsulate the configuration
of passport.js. For more information on how to implement
an @authconfig
module, please see the README for
an existing Ravel AuthenticationProvider.
(Class)
The class to declare as the
@authconfig
class.
The abstract superclass of AuthenticationProvider
s - modules
which are capable of initializing Passport.js with
a particular strategy, and seamlessly verifying requests
issued by mobile clients via header tokens instead of sessions.
Extend this class to implement a custom AuthenticationProvider
.
(Ravel)
An instance of a Ravel application.
The name of the AuthenticationProvider. Override, and try to pick something unique within the Ravel ecosystem.
Initialize passport.js with a strategy.
Transform a credential for an auth'd user into a user profile, iff the credential is valid for this application.
Promise
:
Resolved with an object containing the user profile and a cache expiry time
({expiry: 10, profile:{...}}) iff the credential is valid for this application, rejects otherwise.
An abstraction for a logging service which wraps around intel.
(any)
Log at the 'trace' level.
Log at the 'verbose' level.
Log at the 'debug' level.
Log at the 'info' level.
Log at the 'warn' level.
Log at the 'error' level.
Log at the 'critical' level.
Built-in error types are automatically translated into standard HTTP response status codes when thrown.
Built-in error types for Ravel. When thrown within (or up to) a Routes
or
Resource
handler, the response to that request will automatically
use the associated error status code (rather than a developer needing
to catch their own errors and set status codes manually).
You can create your own Error
types associated with status codes by
extending Ravel.Error
.
Base error type. Associated with HTTP 500 Internal Server Error.
RavelError
:
The Error class.
Access Error - Throw when a user attempts to utilize functionality they are not authenticated to access. Associated with HTTP 403 FORBIDDEN.
AccessError
:
The Error class.
Authentication Error - Throw when a user is not authenticated to perform some action. Associated with HTTP 401 UNAUTHORIZED.
AuthenticationError
:
The Error class.
Duplicate Entry Error - Throw when an attempt is made to insert a record into the database which violates a uniqueness constraint. Associated with HTTP 409 CONFLICT.
DuplicateEntryError
:
The Error class.
Illegal Value Error - Throw when the user supplies an illegal value. Associated with HTTP 400 BAD REQUEST.
IllegalValueError
:
The Error class.
Not Allowed Error - Used to mark API functionality which is not permitted to be accessed under the current application configuration. Associated with HTTP 405 METHOD NOT ALLOWED.
NotAllowedError
:
The Error class.
Not Found Error - Throw when something is expected to exist, but is not found. Associated with HTTP 404 NOT FOUND.
NotFoundError
:
The Error class.
Not Implemented Error - Throw to mark API functionality which has not yet been implemented. Associated with HTTP 501 NOT IMPLEMENTED.
NotImplementedError
:
The Error class.
Range Out Of Bounds Error - Throw when a range of data is requested from a set which is outside of the bounds of that set. Associated with HTTP 416 REQUESTED RANGE NOT SATISFIABLE.
RangeOutOfBoundsError
:
The Error class.
WebSockets in Ravel have been implemented in an extremely minimalistic fashion, both in keeping with Ravel's philosophy of minimizing dependencies, and to promote safer usage of WebSocket technology within applications.
WebSockets in Ravel:
redis
to synchronize between different replicas of your application.@authenticated
when defining your own publish endpoints. This also avoids the introduction of a custom message protocol such as STOMP and keeps things as straightforward as possible.To utilize WebSockets, inject $ws
into any Module
, Resource
or Routes
class, and define your own endpoints (and/or logic) which allow clients to subscribe to topics, for example:
/**
* A RESTful resource to manage subscriptions
*
* You could protect any of these endpoints with @authenticated,
* or define your own custom application logic!
*/
@Ravel.Resource('/ws/mytopic/subscription')
@Ravel.autoinject('$ws')
class WSSubscriptions {
// user would send an empty POST to /ws/mytopic/subscription
// to subscribe to my.topic
async post (ctx) {
ctx.body = await this.$ws.subscribe('my.topic', ctx);
}
// user would send an empty DELETE to /ws/mytopic/subscription
// to unsubscribe from my.topic
async deleteAll (ctx) {
ctx.body = await this.$ws.unsubscribe('my.topic', ctx);
}
}
/**
* A RESTful resource to handle message publication
*/
@Ravel.Resource('/ws/mytopic')
@Ravel.inject('koa-bodyparser')
@Ravel.autoinject('$ws')
class WSMessages {
constructor(bodyparser) {
this['body-parser'] = bodyparser();
}
// user would send a POST to /ws/mytopic
// to send a message
@Ravel.Resource.before('bodyparser')
async post (ctx) {
ctx.body = await this.$ws.publish('my.topic', ctx.request.body);
}
}
Then, on the client, the ws library can be used normally to connect to the Ravel server and listen for the on('message')
event.
WebSockets can be disabled via app.set('enable websockets', false)
.
Testing a Ravel application is a relatively simple exercise with any popular testing framework, such as jest (which Ravel uses for its own internal test suite).
Much as with running the application, you will need a .babelrc
file to transpile decorators:
.babelrc
{
"retainLines": true,
"env": {
"test": {
"plugins": ["transform-decorators-legacy"]
}
}
}
Having done so, bootstrap your application the same way you run it:
describe('Some test', () => {
let app;
beforeEach(async () => {
const Ravel = new require('ravel');
app = new Ravel();
app.set('keygrip keys', ['abc']); // required parameter
app.set('log level', app.$log.NONE); // if you want to silence logging
// load your app components
app.scan('../src/modules', '../src/resources', '../src/routes');
await app.init();
});
});
Let's assume you have a Module
with an injection name 'mymodule'
(either inferred from its filename or named manually via @Module
). This Module
can be accessed directly for testing after app.init()
via app.module('mymodule')
:
it('should do something', () => {
const m = app.module('mymodule');
expect(typeof m.method).toBe('function');
expect(m.method()).toBe(true);
});
Much like testing Module
s, Resource
s and Routes
can be accessed directly for unit testing via app.resource(basePath)
and app.routes(basePath)
, where basePath
is the string passed to the @Routes
or @Resource
decorator.
A more savvy approach to testing your endpoints, however, is to leverage supertest to make and validate real requests against your API:
const request = require('supertest');
it('should respond in some way', async () => {
// pass app.callback or app.server to supertest to give it access to your endpoints
const res = await request(app.callback).get('/my/endpoint');
expect(res.status).toBe(200);
});
If you wish to test an individual Module
, Resource
or Routes
class without bootstrapping your entire application, you can leverage app.load()
to load or mock components:
beforeEach(async () => {
const Ravel = new require('ravel');
app = new Ravel();
app.set('keygrip keys', ['abc']); // required parameter
app.set('log level', app.$log.NONE); // if you want to silence logging
// You want to test IsolatedModule, which depends on AnotherModule
const IsolatedModule = require('../src/path/to/module');
// You can either require AnotherModule, or use a mock:
@Ravel.Module('anothermodule')
class AnotherModule {
// mocked methods
}
// load your app components
app.load(IsolatedModule, AnotherModule);
await app.init();
});