rafter
TypeScript icon, indicating that this package has built-in type declarations

0.8.75 • Public • Published

Rafter Framework

Rafter is a lightweight, slightly opinionated Javascript framework for rapid development of web applications.

Rafter:

  • is built on top of Expressjs.
  • eliminates the tedious wiring of routes, middleware and services.
  • allows decoupling of services by utilizing dependency injection via an autoloading service container.
  • is flexible and testable.

Install

npm install --save rafter

OR

yarn add rafter

Getting started

The following configuration files are autoloaded during the Rafter service starting:

  • config.ts: a general application or module config.
  • middleware.js: registers services as middleware.
  • routes.ts: links controller services to route definitions.
  • pre-start-hooks.js: loads defined services before Rafter has started the server.

The Rafter autoloader will look for all of these files recursively throughout your project. This allows you to modularize your project rather than defining all your config in one place.

Config

The config file (config.ts) is a place to define all your application style config.

export default {
  db: {
    connectionUrl: 'mongodb://localhost:27000/rafter' || process.env.NODE_DB_CONNECTION,
  },
  server: {
    port: 3000,
  },
  example: {
    message: `Hello Mars`,
  },
};

Middleware

The middleware file (middleware.js) exports an array of service name references which will be loaded/registered in the order in which they were defined. eg.

export default (): IMiddlewares => new Set<IMiddlewareConfig>([`corsMiddleware`, `authenticationMiddleware`]);

Note; the middleware must be registered in the .services.ts config.

Routes

The routes file (routes.ts) exports an array of objects which define the http method, route, controller and action. eg.

export default (): IRoutes =>
  new Set<IRouteConfig>([
    {
      endpoint: `/`,
      controller: `exampleController`,
      action: `index`,
      method: `get`,
    },
  ]);

This would call exampleController.index(req, res) when the route GET / is hit.

Pre start hooks

The routes file (pre-start-hooks.js) exports an array of service references that will be executed before Rafter has started, in the order in which they were defined. This is useful for instantiating DB connections, logging etc.

export default (): IPreStartHooks => new Set<IPreStartHookConfig>([`connectDbService`]);

An example of the connectDbService would be:

export default (dbDao, logger) => {
  return async () => {
    logger.info(`Connecting to the database`);
    return dbDao.connect();
  };
};

Rafter instantiation

Along with the aforementioned configs, all that is required to run Rafter is the following:

import rafter from 'rafter';

const run = async () => {
  const rafterServer = rafter();
  await rafterServer.start();
};

run();

Once start() is called, Rafter will:

  1. Scan through all your directories looking for config files.
  2. Autoload all your services into the service container.
  3. Run all the pre-start-hooks.
  4. Apply all the middleware.
  5. Register all the routes.
  6. Start the server.

To see an example project, visit the skeleton rafter app repository.

Going deeper

Rafter is slightly opinionated; which means we have outlined specific ways of doing some things. Not as much as say, Sails or Ruby on Rails, but just enough to provide a simple and fast foundation for your project.

The foundations of the Rafter framework are:

  • Dependency injection
  • Autoloading services
  • Configuration

Dependency injection

With the advent of RequireJs, dependency injection (DI) had largely been thrown by the way side in favor of requiring / importing all your dependencies in Node. This meant that your dependencies were hard coded in each file, resulting in code that was not easily unit testable, nor replicable without rewrites.

eg.

With RequireJs

import mongoose from 'mongoose';

const connect = async (connectionUrl) => {
  await mongoose.connect(connectionUrl);
};

const find = async (query) => {
  await mongoose.find(query);
};

export { connect };

With DI

export default class DbDao {
  constructor(db) {
    this._db = db;
  }

  async connect(connectionUrl) {
    return this._db.connect(connectionUrl);
  }

  async find(query) {
    return this._db.find(query);
  }
}

As you can see with DI, we can substitute any DB service rather than being stuck with mongoose. This insulates services which use a data store from caring what particular store it is. eg. If our DB becomes slow, we can simply substitute a CacheDao instead, and no other services would have to change.

Readme

Keywords

none

Package Sidebar

Install

npm i rafter

Weekly Downloads

175

Version

0.8.75

License

GPL-3.0-or-later

Unpacked Size

200 kB

Total Files

184

Last publish

Collaborators

  • crimsonronin