A slightly opinionated JS framework that makes building apps easy and testable
npm install rafterRafter is a lightweight, slightly opinionated Javascript framework for rapid development of web applications.
- 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.
``typescript`
npm install --save rafter
OR
`typescript`
yarn add rafter
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.
The config file (config.ts) is a place to define all your application style config.
`typescriptHello Mars
export default {
db: {
connectionUrl: 'mongodb://localhost:27000/rafter' || process.env.NODE_DB_CONNECTION,
},
server: {
port: 3000,
},
example: {
message: ,`
},
};
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.
`typescriptcorsMiddleware
export default (): IMiddlewares => new Set, authenticationMiddleware]);`
Note; the middleware must be registered in the .services.ts config.
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
{
endpoint: ,exampleController
controller: ,index
action: ,get
method: ,`
},
]);
This would call exampleController.index(req, res) when the route GET / is hit.
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.
`typescriptconnectDbService
export default (): IPreStartHooks => new Set]);`
An example of the connectDbService would be:
`typescriptConnecting to the database
export default (dbDao, logger) => {
return async () => {
logger.info();`
return dbDao.connect();
};
};
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 repository.
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
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.
`typescript
import mongoose from 'mongoose';
const connect = async (connectionUrl) => {
await mongoose.connect(connectionUrl);
};
const find = async (query) => {
await mongoose.find(query);
};
export { connect };
`
`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.