# Rafter Framework

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

### Rafter:

- is built on top of [Expressjs](https://expressjs.com/).
- 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

```typescript
npm install --save rafter
```

OR

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
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.

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

An example of the `connectDbService` would be:

```typescript
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:

```typescript
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](https://github.com/joshystuart/rafter-skeleton-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

```typescript
import mongoose from 'mongoose';

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

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

export { connect };
```

### With DI

```typescript
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.
