{"id":26901627,"url":"https://github.com/rafterjs/rafter","last_synced_at":"2025-07-30T07:07:44.894Z","repository":{"id":37013344,"uuid":"120706384","full_name":"rafterjs/rafter","owner":"rafterjs","description":"Rafter JS Framework","archived":false,"fork":false,"pushed_at":"2025-07-23T07:13:46.000Z","size":4962,"stargazers_count":2,"open_issues_count":13,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-23T09:15:50.127Z","etag":null,"topics":["dependency-injection","es2016","es2017","framework","js","nodejs","separation-of-concerns","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rafterjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-02-08T03:36:48.000Z","updated_at":"2025-01-01T02:09:30.000Z","dependencies_parsed_at":"2024-01-27T20:28:59.683Z","dependency_job_id":"e2852760-c93e-4e13-9c49-228d2ebce67c","html_url":"https://github.com/rafterjs/rafter","commit_stats":{"total_commits":306,"total_committers":9,"mean_commits":34.0,"dds":0.696078431372549,"last_synced_commit":"282d89aef080278ddc1414745a8b2b05466f24a5"},"previous_names":[],"tags_count":104,"template":false,"template_full_name":null,"purl":"pkg:github/rafterjs/rafter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafterjs%2Frafter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafterjs%2Frafter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafterjs%2Frafter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafterjs%2Frafter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rafterjs","download_url":"https://codeload.github.com/rafterjs/rafter/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafterjs%2Frafter/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267827502,"owners_count":24150350,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-30T02:00:09.044Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dependency-injection","es2016","es2017","framework","js","nodejs","separation-of-concerns","typescript"],"created_at":"2025-04-01T08:54:48.795Z","updated_at":"2025-07-30T07:07:44.861Z","avatar_url":"https://github.com/rafterjs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Rafter\n\nRafter is a lightweight, slightly opinionated Javascript framework for rapid development of web applications.\n\n### Rafter:\n\n- is built using [Typescript](https://www.typescriptlang.org/).\n- is built on top of [Expressjs](https://expressjs.com/).\n- eliminates the tedious wiring of routes, middleware and services.\n- allows decoupling of services by utilizing dependency injection via the autoloading service\n  container [Awilix](https://github.com/jeffijoe/awilix).\n- is flexible, reusable and testable.\n\n## Install\n\n```\nyarn add rafter\n```\n\n## Run\n\n```\nyarn bootstrap \u0026 yarn build \u0026 yarn start:example:simple\n```\n\nThis will build and run the simple example project. You can access it via [http://localhost:3000](http://localhost:3000).\n\n## Building your own Rafter application\n\n### Dependency autoloading\n\nDependency autoloading is at the heart of _Rafter_, and the most opinionated portion of the framework. Rafter\nutilizes [Awilix](https://github.com/jeffijoe/awilix) under the hood\nto [automatically scan your application directory and register services](https://github.com/jeffijoe/awilix#containerloadmodules)\n. So there's no need to maintain configuration or add annotations, as long as the function or constructor arguments are\nthe same name, it will wire everything up automatically.\n\n_Logger.ts_\n\n```typescript\nclass Logger implements ILogger {\n  public log(...args: any[]): void {\n    console.log(args);\n  }\n}\n```\n\n_MyService.ts_\n\n```typescript\nclass MyService {\n  constructor(private readonly logger: ILogger) {}\n\n  public run(): void {\n    this.logger.log('I have been autowired');\n  }\n}\n```\n\nThe _Rafter_ autoloader will look recursively throughout your project for services, functions and config. This means you\ndo not need to statically `import` all your dependencies, which maintains separation of concerns and improves\nreusability.\n\n## Rafter specific config files\n\nThe following configuration files are autoloaded by _Rafter_.\n\n- `config.ts`: a general application or module config.\n- `middleware.ts`: registers services as middleware and loads them into the routes stack.\n- `routes.ts`: links controller services to route definitions.\n- `preStartHooks.ts`: loads defined services before Rafter has started the server.\n- `plugins.ts`: a config file outlining which plugins to load.\n\n#### Config\n\nThe config file (`config.ts`) is a place to define all your application style config.\n\n```typescript\nexport default () =\u003e ({\n  db: {\n    connection: 'mongodb://localhost:27000/rafter' || process.env.NODE_DB_CONNECTION,\n  },\n  server: {\n    port: 3000,\n  },\n  example: {\n    message: `Hello Mars`,\n  },\n});\n```\n\nThis config can be referenced within the injected dependencies.\n\n#### Middleware\n\nThe middleware file (`middleware.js`) exports an array of service name references which will be loaded/registered in the\norder in which they were defined. eg.\n\n```typescript\nexport default (): IMiddlewares =\u003e new Set\u003cIMiddlewareConfig\u003e([`corsMiddleware`, `authenticationMiddleware`]);\n```\n\n#### Routes\n\nThe routes file (`routes.ts`) exports an array of objects which define the http method, route, controller and action.\neg.\n\n```typescript\nexport default (): IRoutes =\u003e\n  new Set\u003cIRouteConfig\u003e([\n    {\n      endpoint: `/`,\n      controller: `exampleController`,\n      action: `index`,\n      method: `get`,\n    },\n  ]);\n```\n\nThis would call `exampleController.index(req, res)` when the route `GET /` is hit. `exampleController` will be the name\nof the autoloaded service.\n\n#### Pre start hooks\n\nThe routes file (`pre-start-hooks.js`) exports an array of service references that will be executed before Rafter has\nstarted, in the order in which they were defined. This is useful for instantiating DB connections, logging etc.\n\n```typescript\nexport default (): IPreStartHooks =\u003e new Set\u003cIPreStartHookConfig\u003e([`connectDbService`]);\n```\n\nAn example of the `connectDbService` pre start hook would be:\n\n```typescript\nexport default (dbDao: IDBDatabaseDao, logger: ILogger) =\u003e {\n  return async function connect(): Promise\u003cIDbConnection\u003e {\n    logger.info(`Connecting to the database`);\n    return dbDao.connect();\n  };\n};\n```\n\nBy adding `async` to the function, _Rafter_ will wait for it to be successfully returned before continuing to the next\npre start hook, or will finish starting up if there are no more hooks.\n\n### Starting your Rafter application\n\nAlong with the aforementioned configs, all that is required to run Rafter is the following in an `test.ts` file:\n\n```typescript\nimport rafter from 'rafter';\n\nconst run = async () =\u003e {\n  // define the paths you want to autoload\n  const paths = [join(__dirname, '/**/!(*.spec).@(ts|js)')];\n\n  // instantiate rafter\n  const rafterServer = rafter({ paths });\n\n  // start rafter server\n  await rafterServer.start();\n};\n\nrun();\n```\n\nOnce `start()` is called, Rafter will:\n\n1. Scan through all your directories looking for config files, plugins and services.\n2. Autoload all `plugins`.\n3. Autoload all other services, injecting their dependencies.\n4. Run all the `pre-start-hooks`.\n5. Apply all the `middleware`.\n6. Register all the `routes`.\n7. Start the server.\n\nTo see an example project, visit the [skeleton rafter app](https://github.com/joshystuart/rafter-skeleton-app)\nrepository, or look at the included `simple example` application\nwithin [packages](https://github.com/joshystuart/rafter/tree/master/examples/simple-app).\n\n# Going deeper\n\nRafter is slightly opinionated; which means we have outlined specific ways of doing some things. Not as much as say,\nSails or Ruby on Rails, but just enough to provide a simple and fast foundation for your project.\n\nThe foundations of the Rafter framework are:\n\n- Dependency injection\n- Autoloading services\n- Configuration\n\n## Dependency injection (DI)\n\nWith the advent of `RequireJs`, dependency injection (DI) had largely been thrown by the wayside in favor of requiring /\nimporting all your dependencies. This meant that your dependencies were hard coded in each file, resulting in code that\nwas not easily unit tested, nor replicable without rewrites.\n\neg.\n\n### With RequireJs\n\n```typescript\nimport mongoose from 'mongoose';\n\nconst connect = async (connectionUrl) =\u003e {\n  await mongoose.connect(connectionUrl);\n};\n\nconst find = async (query) =\u003e {\n  await mongoose.find(query);\n};\n\nexport { connect, find };\n```\n\n### With DI\n\n```typescript\nexport default class DbDao {\n  constructor(private readonly db: IDatabaseDao, private readonly config: { connectionUrl: string }) {}\n\n  public async connect(): Promise\u003cIDatabaseConnection\u003e {\n    return this.db.connect(this.config.connectionUrl);\n  }\n\n  public async find\u003cT\u003e(query: any): Promise\u003cT\u003e {\n    return this.db.find(query);\n  }\n}\n```\n\nAs you can see with DI, we can substitute any DB service rather than being stuck with mongoose. This insulates services\nwhich use a data store from caring what particular store it is. eg. If our DB becomes slow, we can simply substitute\na `CacheDao` instead, and no other services would have to change.\n\n## How DI Works in Rafter\n\nMany other DI frameworks require you to use special decorators to specify which services are injected where. Rafter on\nthe other hand, utilizes the KISS method of dependency injection;\n\n1. All services you want to be available in the DI container must have a `default` export.\n2. The `file name` of the service is the `camelCase` reference in the DI container eg.\n   - File name: `lib/users/UserManager.ts`\n   - Reference in DI container: `userManager`\n3. The variable names in the `class constructor` or `function arguments` match the reference names (see above) in the DI\n   container.\n\n### Example\n\nWe have the following service with the filename: `lib/CommentManager.ts`\n\n```typescript\nexport default class CommentManager {\n  constructor(private readonly dbDao: DbDao, private readonly logger: ILogger) {}\n\n  public async getComment(id: string): Promise\u003cComment\u003e {\n    this.logger.info(`Getting comment for id: ${id}`);\n    return this.dbDao.find(`SELECT * FROM comments WHERE id=${id}`);\n  }\n}\n```\n\nIf we want to inject the `CommentManager` into another service we must name the variable in the constructor\n`commentManager`. eg.\n\n```typescript\nexport default class CommentController {\n  constructor(private readonly commentManager: CommentManager) {}\n\n  public async index(request: IRequest, response: IResponse): Promise\u003cvoid\u003e {\n    const comment = await this.commentManager.getComment(1);\n    response.json(comment);\n  }\n}\n```\n\nOr, if you prefer to use functional programming, make sure the argument name is a `camelCase` version of\nthe `file name`:\n\n```typescript\nexport default (commentManager: CommentManager) =\u003e {\n  return async (request: IRequest, response: IResponse): Promise\u003cvoid\u003e =\u003e {\n    const comment = await this.commentManager.getComment(1);\n    response.json(comment);\n  };\n};\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafterjs%2Frafter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frafterjs%2Frafter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafterjs%2Frafter/lists"}