ravel

Core Application Components

Ravel

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.

new Ravel()

Extends AsyncEventEmitter

Example
const Ravel = require('ravel');
const app = new Ravel();
Static Members
Error
DatabaseProvider
AuthenticationProvider
httpCodes
Module
Routes
Resource
config
Instance Members
cwd
$log
$err
initialized
listening
callback
server
keys
registerProvider(ProviderClass, args)
init()
rotateKeygripKey(newKey)
listen()
start()
close()
registerParameter(key, required, defaultValue)
set(key, value)
get(key)
module(name)
load(classes)
scan(basePaths)
routes(basePath)
resource(basePath)

Module

Modules are plain old node.js modules exporting a single class which encapsulates application logic and middleware. Modules support dependency injection of core Ravel services and other Modules alongside npm dependencies (no relative require's!). Modules are instantiated safely in dependency-order, and cyclical dependencies are detected automatically.

Module
Parameters
args (Array<any>) Either a single string name for this module, or no arguments.
Example
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

Routes

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:

  • Parameters: '/foo/:id' (:id is captured as ctx.params.id, and matches any chars but / by default)
  • Multiple parameters: '/:foo/:bar' (ctx.params.foo and ctx.params.bar are both available)
  • Mixed parameters: '/foo/bar-:id' (:id, without bar-, is captured as ctx.params.id)
  • Regex parameters: '/foo/:id(\\d+)'
  • Compound parameters: '/foo/:id-:name' (ctx.params.id and ctx.params.name are both available)
  • Optional parameters: '/foo/:id{-:name}?' (matches /foo/12 and /foo/12-john)
  • "Catch all" routes, which match their own pattern, as well as any path components beneath them. In 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:

  • Route components without parameters are visited first
  • Route components with mixed parameters are visited before ones with non-mixed parameters
  • Route components with required parameters are visited before ones with optional parameters
  • "Catch all" routes are visited last, only if nothing else has matched a path
  • Declaring two functionally identical routes is not permitted (i.e. /foo/:bar and /foo/:car)
Routes
Parameters
basePath (string) The base path for all routes in this class. Should be unique within an application.
Example
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;
Static Members
HEAD
GET
POST
PUT
PATCH
DELETE
mapping
before
transaction
authenticated
cache

Resource

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. Resources 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" @mappings 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.

Resources 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.

Resource
Parameters
basePath (string) The base path for all endpoints in this class. Should be unique within an application.
Example
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;
Static Members
mapping()
before
transaction
authenticated
cache

Dependency Injection

inject

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 Modules 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
Parameters
rest (...string) The names of Module s or npm modules to inject.
Example
// @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
  }
}

autoinject

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
Parameters
rest (...string) The names of Module s or npm modules to inject.
Example
// @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 Services

Services

Core Ravel services available for injection by name into any @Modules, @Resources or @Routes.

new Services()
Example
@Module
@inject('$log')
class MyModule {
  constructor ($log) {
    this.$log = $log;
  }

  aMethod () {
    this.$log.info('In aMethod!');
    //...
  }
}
Static Members
$app
$err
$log
$kvstore
$params
$db
$ws

HTTPCodes

Hash of HTTP Response Codes, available as require('ravel').httpCodes. Useful for creating custom error types.

new HTTPCodes()
Static Members
OK
CREATED
NO_CONTENT
PARTIAL_CONTENT
MULTIPLE_CHOICES
MOVED_PERMANENTLY
FOUND
SEE_OTHER
NOT_MODIFIED
USE_PROXY
SWITCH_PROXY
TEMPORARY_REDIRECT
PERMANENT_REDIRECT
BAD_REQUEST
UNAUTHORIZED
PAYMENT_REQUIRED
FORBIDDEN
NOT_FOUND
METHOD_NOT_ALLOWED
NOT_ACCEPTABLE
PROXY_AUTHENTICATION_REQUIRED
REQUEST_TIMEOUT
CONFLICT
GONE
LENGTH_REQUIRED
PRECONDITION_FAILED
REQUEST_ENTITY_TOO_LARGE
REQUEST_URI_TOO_LONG
UNSUPPORTED_MEDIA_TYPE
REQUESTED_RANGE_NOT_SATISFIABLE
EXPECTATION_FAILED
IM_A_TEAPOT
AUTHENTICATION_TIMEOUT
METHOD_FAILURE
UNPROCESSABLE_ENTITY
LOCKED
FAILED_DEPENDENCY
UPGRADE_REQUIRED
PRECONDITION_REQUIRED
TOO_MANY_REQUESTS
REQUEST_HEADER_FIELDS_TOO_LARGE
LOGIN_TIMEOUT
NO_RESPONSE
RETRY_WITH
BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS
REQUEST_HEADER_TOO_LARGE
CERT_ERROR
NO_CERT
HTTP_TO_HTTPS
TOKEN_EXPIRED_INVALID
CLIENT_CLOSED_REQUEST
INTERNAL_SERVER_ERROR
NOT_IMPLEMENTED
BAD_GATEWAY
SERVICE_UNAVAILABLE
GATEWAY_TIMEOUT
HTTP_VERSION_NOT_SUPPORTED
VARIANT_ALSO_NEGOTIATES
INSUFFICIENT_STORAGE
LOOP_DETECTED
BANDWIDTH_LIMIT_EXCEEDED
NOT_EXTENDED
NETWORK_AUTHENTICATION_REQUIRED
ORIGIN_ERROR
WEB_SERVER_IS_DOWN
CONNECTION_TIMED_OUT
PROXY_DECLINED_REQUEST
A_TIMEOUT_OCCURRED
NETWORK_READ_TIMEOUT_ERROR
NETWORK_CONNECT_TIMEOUT_ERROR

Routing and Middleware

mapping

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.

mapping(verb: symbol, path: string, config: object)
Parameters
verb (symbol) An HTTP verb such as Routes.HEAD , Routes.GET , Routes.POST , Routes.PUT , Routes.PATCH , or Routes.DELETE .
path (string) The path for this endpoint, relative to the base path of the Routes class.
config (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.
Example
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 {
}

before

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 AsyncFunctions available on this.

before(rest: ...string)
Parameters
rest (...string) The property names of valid middleware available on this .
Example
// 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
  }
}

cache

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 AsyncFunctions available on this.

See cache for more information.

cache(args: ...Any)
Parameters
args (...Any) Options for the caching middleware, or undefined.
Example
// 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).
  }
}

Databases and Transaction-Per-Request

transaction

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).

transaction(args: ...string)
Parameters
args (...string) A list of database provider names to open connections for.
Example
// 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
  }
}

DatabaseProvider

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.

new DatabaseProvider(ravelInstance: Ravel, name: string)
Parameters
ravelInstance (Ravel) An instance of a Ravel application.
name (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.
Instance Members
name
prelisten(ravelInstance)
end(ravelInstance)
getTransactionConnection()
exitTransaction(connection, shouldCommit)

Authentication

authenticated

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.

authenticated(args: (Class | Method | object))
Parameters
args ((Class | Method | object)) A configuration object (returning a decorator), or the Method or Class to directly apply this decorator to.
Example
// 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
  }
}

authconfig

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.

authconfig(target: Class)
Parameters
target (Class) The class to declare as the @authconfig class.

AuthenticationProvider

The abstract superclass of AuthenticationProviders - 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.

new AuthenticationProvider(ravelInstance: Ravel)
Parameters
ravelInstance (Ravel) An instance of a Ravel application.
Instance Members
name
init(app, passport, verify)
handlesClient(client)
credentialToProfile(credential, client)

Logging

Logger

An abstraction for a logging service which wraps around intel.

new Logger(source: any)
Parameters
source (any)
Instance Members
trace()
verbose()
debug()
info()
warn()
error()
critical()

Built-in Error Types

Built-in error types are automatically translated into standard HTTP response status codes when thrown.

$err

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.

new $err()
Static Members
General
Access
Authentication
DuplicateEntry
IllegalValue
NotAllowed
NotFound
NotImplemented
RangeOutOfBounds

WebSockets

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:

  • Support a basic publish/subscribe model
  • Wrap around ws, rather than a more complex (and dependency-heavy) library with transport fallbacks.
  • Are automatically safe for horizontal scaling, using redis to synchronize between different replicas of your application.
  • Are essentially one-way. Browser clients receive messages, but make HTTP requests to "send" them. This allows you to 1. set the rules for who can publish what and when, and 2. leverage existing Ravel functionality like @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 Ravel Applications

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();
  });
});

Testing Modules

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);
});

Testing Resources and Routes

Much like testing Modules, Resources 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);
});

Testing in Isolation

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();
  });