{"id":18829914,"url":"https://github.com/rxstack/rxstack","last_synced_at":"2026-03-13T07:34:18.198Z","repository":{"id":26894711,"uuid":"110274587","full_name":"rxstack/rxstack","owner":"rxstack","description":"RxStack is a realtime object-oriented framework which helps you build a micro service web applications on top of other frameworks like express and socketio by adding an abstraction layer.","archived":false,"fork":false,"pushed_at":"2025-02-18T07:24:51.000Z","size":2337,"stargazers_count":22,"open_issues_count":44,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-11-12T10:04:27.252Z","etag":null,"topics":["framework","nodejs","realtime-messaging","rxstack","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rxstack.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2017-11-10T17:32:32.000Z","updated_at":"2024-11-03T05:49:54.000Z","dependencies_parsed_at":"2024-06-21T14:18:54.640Z","dependency_job_id":"a9398092-dd76-4880-9569-abc35d54e21b","html_url":"https://github.com/rxstack/rxstack","commit_stats":{"total_commits":46,"total_committers":4,"mean_commits":11.5,"dds":"0.34782608695652173","last_synced_commit":"5f7069abeeb8f2f45495ab7f4d288e690b57e215"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/rxstack/rxstack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rxstack%2Frxstack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rxstack%2Frxstack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rxstack%2Frxstack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rxstack%2Frxstack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rxstack","download_url":"https://codeload.github.com/rxstack/rxstack/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rxstack%2Frxstack/sbom","scorecard":{"id":791608,"data":{"date":"2025-08-11","repo":{"name":"github.com/rxstack/rxstack","commit":"5f7069abeeb8f2f45495ab7f4d288e690b57e215"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.7,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/28 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/node.js.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Pinned-Dependencies","score":3,"reason":"dependency not pinned by hash detected -- score normalized to 3","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/node.js.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/rxstack/rxstack/node.js.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/node.js.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/rxstack/rxstack/node.js.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/node.js.yml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/rxstack/rxstack/node.js.yml/master?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/node.js.yml:31","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   1 out of   2 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 15 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"13 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-7q7g-4xm8-89cq","Warn: Project is vulnerable to: GHSA-xffm-g5w8-qvg7","Warn: Project is vulnerable to: GHSA-h5c3-5r3r-rr8q","Warn: Project is vulnerable to: GHSA-rmvr-2pp2-xj38","Warn: Project is vulnerable to: GHSA-xx4v-prfh-6cgc","Warn: Project is vulnerable to: GHSA-jr5f-v2jv-69x6","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-76c9-3jph-rj3q","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-23T07:42:27.481Z","repository_id":26894711,"created_at":"2025-08-23T07:42:27.481Z","updated_at":"2025-08-23T07:42:27.481Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30460086,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-13T03:55:51.346Z","status":"ssl_error","status_checked_at":"2026-03-13T03:55:33.055Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["framework","nodejs","realtime-messaging","rxstack","typescript"],"created_at":"2024-11-08T01:47:01.449Z","updated_at":"2026-03-13T07:34:18.180Z","avatar_url":"https://github.com/rxstack.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RxStack Framework\n\n[![Node.js CI](https://github.com/rxstack/rxstack/actions/workflows/node.js.yml/badge.svg)](https://github.com/rxstack/rxstack/actions/workflows/node.js.yml)\n[![Maintainability](https://api.codeclimate.com/v1/badges/9bb1b2d02b93df616908/maintainability)](https://codeclimate.com/github/rxstack/rxstack/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/9bb1b2d02b93df616908/test_coverage)](https://codeclimate.com/github/rxstack/rxstack/test_coverage)\n\n\u003e RxStack is a realtime object-oriented framework which helps you build a micro service web applications\non top of other frameworks like `express` and `socketio` by adding an abstraction layer.\n\n\u003e Switch to [Rapid Application Development Platform](https://github.com/rxstack/rxstack/tree/master/packages/platform) - \nThe fastest way to build web applications\n\n## Table of content\n\n- [Prerequisites](#prerequisites)\n- [Installation](#installation)\n- [Controllers](#controllers)\n    - [Creating a Controller](#controllers-create)\n    - [Mapping a URL and socket event to a Controller](#controllers-mapping)\n    - [Managing errors](#controllers-errors)\n    - [The Request and Response Object](#controllers-request-response)\n- [Event Listeners](#event-listeners)\n- [Console](#console)\n- [Security](#security)\n    - [Installation](#security-installation)\n    - [Configurations](#security-configuration)\n    - [Registering a user provider](#security-user-provider)\n    - [Securing a controller](#security-controller)\n    - [Obtaining the token](#security-obtaining-token)\n    - [Securing controller with authentication listener](#security-listener)\n    - [References](https://github.com/rxstack/rxstack/tree/master/packages/security)\n- [Servers](#servers)\n- [Workers](#workers)\n- [Databases](#databases)\n- [Testing](#testing)\n    - [Unit](#testing-unit)\n    - [Integration](#testing-integration)\n    - [Functional](#testing-functional)\n- [References]()\n    - [Skeleton Application](https://github.com/rxstack/skeleton)\n    - [Async Event Dispatcher](https://github.com/rxstack/rxstack/tree/master/packages/async-event-dispatcher)\n    - [Configuration](https://github.com/rxstack/rxstack/tree/master/packages/configuration)\n    - [Core](https://github.com/rxstack/rxstack/tree/master/packages/core)\n    - [Data Fixtures](https://github.com/rxstack/rxstack/tree/master/packages/data-fixtures)\n    - [Exceptions](https://github.com/rxstack/rxstack/tree/master/packages/exceptions)\n    - [Express Server](https://github.com/rxstack/rxstack/tree/master/packages/express-server)\n    - [Query Filter](https://github.com/rxstack/rxstack/tree/master/packages/query-filter)\n    - [Security](https://github.com/rxstack/rxstack/tree/master/packages/security)\n    - [Service Registry](https://github.com/rxstack/rxstack/tree/master/packages/service-registry)\n    - [Socketio Server](https://github.com/rxstack/rxstack/tree/master/packages/socketio-server)\n    - [Utils](https://github.com/rxstack/rxstack/tree/master/packages/utils)\n    - [Platform](https://github.com/rxstack/rxstack/tree/master/packages/platform)\n    - [Platform Helpers](https://github.com/rxstack/platform-callbacks)\n    - [Memory Service](https://github.com/rxstack/memory-service)\n    - [Mongoose Service](https://github.com/rxstack/mongoose-service)\n    - [Sequelize Service](https://github.com/rxstack/sequelize-service)\n\n## \u003ca name=\"prerequisites\"\u003e\u003c/a\u003e Prerequisites\n`RxStack` requires `Node v12.0.0` and later. On MacOS and other Unix systems the \n[Node Version Manager](https://github.com/creationix/nvm) is a good way \nto quickly install the latest version of NodeJS and keep up it up to date. You'll also need git installed.\nAfter successful installation, the node, npm and git commands should be available on the terminal \nand show something similar when running the following commands:\n\n```bash\n$ node --version\nv12.6.0\n```\n\n```bash\n$ npm --version\n6.9.0\n```\n\n```bash\n$ git --version\ngit version 2.17.1\n```\n\n## \u003ca name=\"installation\"\u003e\u003c/a\u003e Installation\n\nLet's clone the pre-configured [skeleton application](https://github.com/rxstack/skeleton):\n\n```bash\n$ git clone https://github.com/rxstack/skeleton.git my-project\n$ cd my-project\n$ npm install\n$ npm run dev\n```\n\nOpen [localhost](http://localhost:3000/) in the browser and you should see the welcome page or you can access it via websockets:\n\n```typescript\nconst io = require('socket.io-client');\nconst conn = io('ws://localhost:4000', {transports: ['websocket']});\n\nconn.emit('app_index', null, function (response: any) {\n  console.log(response); // should output Response object\n});\n```\n      \n## \u003ca name=\"controllers\"\u003e\u003c/a\u003e Controllers\nA controller is a typescript function you create that reads information from the `Request` object \nand creates and returns a `Response` object. The response could be an HTML page, JSON, XML, a file download, \na 404 error or anything else you can dream up. \nThe controller executes whatever arbitrary logic your application needs to send a response to the client.\n \n### \u003ca name=\"controllers-create\"\u003e\u003c/a\u003e Creating a controller \nA controller action is usually a method inside a controller class:\n\n```typescript\n// my-project/src/app/controllers/lucky.controller.ts\n\nimport {Injectable} from 'injection-js';\nimport {Http, Request, Response, WebSocket} from '@rxstack/core';\n\n@Injectable()\nexport class LuckyController {\n\n  @Http('GET', '/lucky/number/:max', 'app_lucky_number')\n  @WebSocket('app_lucky_number')\n  async number(request: Request): Promise\u003cResponse\u003e {\n    const num: number = Math.floor(Math.random() * Math.floor(request.params.get('max')));\n    return new Response({num});\n  }\n}\n```\n\nThis controller is pretty straightforward:\n\n- a `LuckyController` class is created and [@Injectable()](https://github.com/mgechev/injection-js) annotation is applied.\n- The `number()` method is created and \n[`@http()` and `@websocket()`](https://github.com/rxstack/rxstack/blob/master/packages/core/docs/kernel.md#decorators) \nannotations are applied \nin order to register it in the `Kernel`.\n- A [`Request`](https://github.com/rxstack/rxstack/blob/master/packages/core/docs/kernel.md#request-object) object is passed as a method argument.\n- A promise of [`Response`](https://github.com/rxstack/rxstack/blob/master/packages/core/docs/kernel.md#response-object) object is returned.\n\n\u003e you need to register `LuckyController` in the controller providers:\n\n```typescript\n// my-project/src/app/controllers/APP_CONTROLLER_PROVIDERS.ts\nimport {ProviderDefinition} from '@rxstack/core';\nimport {LuckyController} from './lucky-controller';\n\nexport const APP_CONTROLLER_PROVIDERS: ProviderDefinition[] = [\n  { provide: LuckyController, useClass: LuckyController }\n];\n```\n\n### \u003ca name=\"controllers-mapping\"\u003e\u003c/a\u003e Mapping a URL and socket event to a Controller\n\nIn order to view the result of this controller, you need to map a URL to it via a `@http()` decorator.\n\n```typescript\n@Http('GET', '/lucky/number/:max', 'app_lucky_number')\n```\n\nthen you can access it via http:\n            \n```bash\ncurl http://localhost:3000/lucky/number/10\n```\n\nand setting an event name via a `@websocket()` decorator.\n\n```typescript\n@WebSocket('app_lucky_number')\n```\n\nthen you can access it using socketio-client:\n\n```typescript\nconst io = require('socket.io-client');\nconst conn = io('ws://localhost:4000', {transports: ['websocket']});\n\nconn.emit('app_lucky_number', {params: {max: 10}}, function (response: any) {\n  console.log(response); // should output Response object\n});\n```\n\n### \u003ca name=\"controllers-errors\"\u003e\u003c/a\u003e Managing errors\n\nIf you throw an exception that extends or is an instance of [`HttpException`](https://github.com/rxstack/rxstack/blob/master/packages/exceptions/src/http.exception.ts), \n`RxStack` will use the appropriate HTTP status code. \nOtherwise, the response will have a 500 HTTP status code:\n\n```typescript\n// my-project/src/app/controllers/lucky.controller.ts\n\nimport {Injectable} from 'injection-js';\nimport {Http, Request, Response, WebSocket} from '@rxstack/core';\nimport {BadRequestException} from '@rxstack/exceptions';\n\n@Injectable()\nexport class LuckyController {\n  // ...\n  async number(request: Request): Promise\u003cResponse\u003e {\n    if (parseInt(request.params.get('max')) \u003c 3) {\n      throw new BadRequestException('Number should be greater than 3.');\n    }\n    // ...\n  }\n}\n```\n\n### Learn more:\n\n- [Application](https://github.com/rxstack/rxstack/blob/master/packages/core/docs/application.md)\n- [Kernel](https://github.com/rxstack/rxstack/blob/master/packages/core/docs/kernel.md)\n- [Exceptions](https://github.com/rxstack/rxstack/tree/master/packages/exceptions)\n\n### \u003ca name=\"controllers-request-response\"\u003e\u003c/a\u003e The Request and Response Object\n\nThe `Request` object is created from the underlying framework incoming request. It lives only in the controller method.\nIt has several public properties that return all information you need about the request.\n\nLearn more about [Request Object](https://github.com/rxstack/rxstack/blob/master/packages/core/docs/kernel.md#request-object).\n\nThe `Response` object passes information to the underlying framework to construct the response and send it to the client.\n\nLearn more about [Response Object](https://github.com/rxstack/rxstack/blob/master/packages/core/docs/kernel.md#response-object).\n\n## \u003ca name=\"event-listeners\"\u003e\u003c/a\u003e Events and Event Listeners\nDuring the execution of a `RxStack` application, some event notifications are triggered. \nYour application can listen to these notifications and respond to them by executing any piece of code.\n\n`RxStack` triggers several events related to the kernel while processing the Request. \nThird-party modules may also dispatch events, and you can even dispatch custom events from your own code.\n\n### \u003ca name=\"creating-listener\"\u003e\u003c/a\u003e Creating an event listener\nThe most common way to listen to an event is to register an event listener:\n\n```typescript\n// my-project/src/app/event-listeners/exception.listener.ts\nimport {Injectable} from 'injection-js';\nimport {ExceptionEvent, KernelEvents, Response} from '@rxstack/core';\nimport {HttpException} from '@rxstack/exceptions';\nimport {Observe} from '@rxstack/async-event-dispatcher';\n\n@Injectable()\nexport class ExceptionListener {\n\n  @Observe(KernelEvents.KERNEL_EXCEPTION)\n  async onException(event: ExceptionEvent): Promise\u003cvoid\u003e {\n    // make sure it is applied only on LuckyController.number\n    if (event.getRequest().routeName !== 'app_lucky_number') {\n      return;\n    }\n    // You get the exception object from the received event\n    const exception = event.getException();\n    const errMsg = `My error says: ${exception.message}`;\n\n    // Customize your response object to display the exception details\n    const response = new Response(errMsg);\n\n    if (exception instanceof HttpException) {\n      response.statusCode = exception.statusCode;\n    } else {\n      response.statusCode = 500;\n    }\n\n    // sends the modified response object to the event\n    event.setResponse(response);\n  }\n}\n```\n\n\u003e you need to register `ExceptionListener` in the application providers:\n\n```typescript\n// my-project/src/app/event-listeners/APP_EVENT_LISTENERS_PROVIDERS.ts\nimport {ProviderDefinition} from '@rxstack/core';\nimport {ExceptionListener} from './exception.listener';\n\nexport const APP_LISTENERS_PROVIDERS: ProviderDefinition[] = [\n  // ...\n  { provide: ExceptionListener, useClass: ExceptionListener }\n];\n```\n\n\n### Learn more:\n\n- [Async Event Dispatcher](https://github.com/rxstack/rxstack/tree/master/packages/async-event-dispatcher)\n- [Application Events](https://github.com/rxstack/rxstack/blob/master/packages/core/docs/application.md#bootstrap-event)\n- [Kernel Events](https://github.com/rxstack/rxstack/blob/master/packages/core/docs/kernel.md#events)\n- [Server Events](https://github.com/rxstack/rxstack/blob/master/packages/core/docs/server.md#server-events)\n\n## \u003ca name=\"console\"\u003e\u003c/a\u003e Console\nYour console commands can be used for any recurring task, such as cronjobs, imports, or other batch jobs.\n\nTo see the available commands you can run:\n\n```\n    $ npm run cli -- -h\n```\n\n- `$ npm run cli debug:http-metadata` - Prints http metadata for an application\n- `$ npm run cli debug:web-socket-metadata` - Prints web socket metadata for an application.\n\n\n[Learn More](https://github.com/rxstack/rxstack/blob/master/packages/core/docs/console.md)\n\n\n## \u003ca name=\"security\"\u003e\u003c/a\u003e Security\nIn this article you'll learn how to set up your application's security step-by-step, from configuring your application \nand how you load users, to denying access and fetching the `User` object.\n\n### \u003ca name=\"security-installation\"\u003e\u003c/a\u003e Installation\n\n```bash\nnpm install @rxstack/security --save\n```\n\n### \u003ca name=\"security-configuration\"\u003e\u003c/a\u003e Configurations\n\nAdd the following configurations to the environment file:\n\n```typescript\n// my-project/src/environments/environment.ts\n\n// ...\n  security: {\n    local_authentication: true,\n    token_extractors: {\n      authorization_header: {\n        enabled: true\n      }\n    },\n    ttl: 300,\n    default_issuer: 'default',\n    secret_configurations: [\n      {\n        issuer: 'default',\n        secret: 'my_secret',\n        signature_algorithm: 'HS512',\n      }\n    ]\n  }\n```\n\nand register the module:\n\n```typescript\n// my-project/src/app/APP_OPTIONS.ts\n// ...\n\nimport {ApplicationOptions} from '@rxstack/core';\nimport {environment} from '../environments/environment';\nimport {SecurityModule} from '@rxstack/security';\n\nexport const APP_OPTIONS: ApplicationOptions = {\n  // ...\n  imports: [\n    // ...\n    SecurityModule.configure(environment.security)\n  ]\n};\n```\n\n[Learn more about security configurations](https://github.com/rxstack/rxstack/tree/master/packages/security#configurations)\n\n\n### \u003ca name=\"security-user-provider\"\u003e\u003c/a\u003e Registering a user provider\nThe easiest (but most limited) way, is to configure `RxStack` to load hardcoded users directly from configurations. \nThis is called an \"in memory\" provider, but it's better to think of it as an `in configuration` provider:\n\n- Let's create the `User` model:\n\n```typescript\n// my-project/src/app/models/user.ts\nimport {EncoderAwareInterface, PlainTextPasswordEncoder, User as BaseUser} from '@rxstack/security';\n\nexport class User extends BaseUser implements EncoderAwareInterface {\n  getEncoderName(): string {\n    return PlainTextPasswordEncoder.ENCODER_NAME;\n  }\n}\n```\n\nWe extend `User` from `@rxstack/security` and tell the model to use a specific password encoder.\n\n[Lear more about password encoders](https://github.com/rxstack/rxstack/tree/master/packages/security#password-encoders)\n\n- Let's add users to `environment.ts` file:\n\n```typescript\n// my-project/src/environments/environment.ts\n\n// ...\n  users: [\n    {\n      username: 'admin',\n      password: 'admin',\n      roles: ['ROLE_ADMIN']\n    },\n    {\n      username: 'user',\n      password: 'user',\n      roles: ['ROLE_USER']\n    }\n  ]\n```\n\n\u003e We added two users with different roles `ROLE_ADMIN` and `ROLE_USER`.\n\n- Let's register the in-memory user provider in the application providers:\n\n\n```typescript\n// my_project/src/app/security/APP_SECURITY_PROVIDERS.ts \n\nimport {ProviderDefinition, UserInterface} from '@rxstack/core';\nimport {InMemoryUserProvider, USER_PROVIDER_REGISTRY} from '@rxstack/security';\nimport {environment} from '../../environments/environment';\nimport {User} from '../models/user';\n\nexport const APP_SECURITY_PROVIDERS: ProviderDefinition[] = [\n  {\n    provide: USER_PROVIDER_REGISTRY,\n    useFactory: () =\u003e {\n      return new InMemoryUserProvider\u003cUserInterface\u003e(\n        environment.users,\n        (data: any) =\u003e new User(data.username, data.password, data.roles)\n      );\n    },\n    deps: [],\n    multi: true\n  },\n];\n```\n\nregister the security providers in the application options:\n\n```typescript\n// my-project/src/app/APP_OPTIONS.ts\n// ...\n\nimport {ApplicationOptions} from '@rxstack/core';\nimport {APP_SECURITY_PROVIDERS} from './security/providers';\n\nexport const APP_OPTIONS: ApplicationOptions = {\n  // ...\n  providers: [\n    // ...\n    ...APP_SECURITY_PROVIDERS\n  ]\n};\n```\n\n[Learn more about user providers](https://github.com/rxstack/rxstack/tree/master/packages/security#user-providers)\n\n### \u003ca name=\"security-controller\"\u003e\u003c/a\u003e Securing a controller\nAs we successfully set up and configured security module, let's create our first secured controller:\n\n```typescript\n// my-project/src/app/controllers/secured.controller.ts\nimport {Injectable} from 'injection-js';\nimport {Http, Request, Response, WebSocket} from '@rxstack/core';\nimport {ForbiddenException} from '@rxstack/exceptions';\n\n@Injectable()\nexport class SecuredController {\n\n  @Http('GET', '/secured/admin', 'app_secured_admin')\n  @WebSocket('app_secured_admin')\n  async adminAction(request: Request): Promise\u003cResponse\u003e {\n    if (!request.token.hasRole('ROLE_ADMIN')) {\n      throw new ForbiddenException();\n    }\n    return new Response('secured admin action');\n  }\n}\n```\n\n\u003e you need to register `SecuredController` in the application controller providers:\n\n```typescript\n// my-project/src/app/controllers/APP_CONTROLLER_PROVIDERS.ts\nimport {ProviderDefinition} from '@rxstack/core';\nimport {SecuredController} from './secured.controller';\n\nexport const APP_CONTROLLER_PROVIDERS: ProviderDefinition[] = [\n  // ...\n  { provide: SecuredController, useClass: SecuredController }\n];\n```\n\nAs you see in the `Request` object we retrieve the security token and check if the logged in user has a certain role.\n\n[Learn more about tokens](https://github.com/rxstack/rxstack/tree/master/packages/security#working-with-token)\n\n### \u003ca name=\"security-obtaining-token\"\u003e\u003c/a\u003e Obtaining the token\nBy default `@rxstack/security` is using JWT. The token could be generated on a dedicated authentication server \nor in the `RxStack` application if `local_authentication` is enabled.\n\nLet's obtain the token:\n\n```bash\ncurl -X POST \\\n  http://localhost:3000/security/login \\\n  -H 'content-type: application/json' \\\n  -d '{\n\t\"username\": \"admin\",\n\t\"password\": \"admin\"\n}'\n```\n\n\u003e Token expiration time is set in the `ttl` option in the security module.\n\n\nAs we now have the token we can try to access `app_secured_admin` via http:\n\n```bash\ncurl -X GET \\\n  http://localhost:3000/secured/admin \\\n  -H 'authorization: Bearer your-generated-token' \n```\n\nTo access secured controller actions via websocket, you first need to authenticate:\n\n```typescript\n// ...\n\nconn.emit('security_authenticate', {params: {bearer: 'your-generated-token'}}, function (response: any) {\n  console.log(response.statusCode); // should output 204 or 401\n  \n  // now you can access the secured action\n  conn.emit('app_secured_admin', null, function (response: any) {\n    console.log(response.statusCode); // should output 200\n  });\n});\n```\n\n[Learn more about local authentication](https://github.com/rxstack/rxstack/tree/master/packages/security#local-authentication)\n\n### \u003ca name=\"security-listener\"\u003e\u003c/a\u003e Securing with authentication listener\nIf you need to restrict the access on application level then you need to create an authentication listener.\n\nLet's add another action to the `SecuredController`:\n\n```typescript\n// my-project/src/app/controllers/secured.controller.ts\nimport {Injectable} from 'injection-js';\nimport {Http, Request, Response, WebSocket} from '@rxstack/core';\n\n@Injectable()\nexport class SecuredController {\n  // ...\n  \n  @Http('GET', '/secured/user', 'app_secured_user')\n  @WebSocket('app_secured_user')\n  async userAction(request: Request): Promise\u003cResponse\u003e {\n    return new Response('secured user action');\n  }\n}\n```\n\nAs you see the `userAction` is not secured. Let's create the listener:\n\n```typescript\n// my-project/src/app/listeners/authentication.listener.ts\nimport {Injectable} from 'injection-js';\nimport {KernelEvents, RequestEvent} from '@rxstack/core';\nimport {ForbiddenException} from '@rxstack/exceptions';\nimport {Observe} from '@rxstack/async-event-dispatcher';\n\n@Injectable()\nexport class AuthenticationListener {\n\n  @Observe(KernelEvents.KERNEL_REQUEST)\n  async onRequest(event: RequestEvent): Promise\u003cvoid\u003e {\n    // make sure route/event name starts with \"app_secured_\"\n    if (event.getRequest().routeName.search('^app_secured_') === -1) {\n      return;\n    }\n    // checks whether user is authenticated or not\n    if (!event.getRequest().token.isAuthenticated()) {\n      throw new ForbiddenException();\n    }\n  }\n}\n```\n\n\u003e you need to register `AuthenticationListener` in the application listener providers:\n\n```typescript\n// my-project/src/app/listener/APP_EVENT_LISTENERS_PROVIDERS.ts\nimport {ProviderDefinition} from '@rxstack/core';\nimport {AuthenticationListener} from './authentication.listener';\n\nexport const APP_EVENT_LISTENERS_PROVIDERS: ProviderDefinition[] = [\n  // ...\n  { provide: AuthenticationListener, useClass: AuthenticationListener },\n];\n```\n\nand now let's try it:\n\n```bash\ncurl -X GET \\\n  http://localhost:3000/secured/user \\\n  -H 'authorization: Bearer your-generated-token' \n```\n\nAs you see `RxStack` security module provides powerful and flexible authentication system.\n\n[Complete security module documentations](https://github.com/rxstack/rxstack/tree/master/packages/security)\n\n## \u003ca name=\"servers\"\u003e\u003c/a\u003e Servers\nThe whole point of `RxStack` is staying as a platform-agnostic. A framework's architecture is focused on being applicable \nto any kind of server-side solution. Build once, use everywhere!\n\nThere are two build-in server modules:\n\n- [ExpressServerModule](https://github.com/rxstack/rxstack/tree/master/packages/express-server)\n- [SocketioServerModule](https://github.com/rxstack/rxstack/tree/master/packages/socketio-server)\n\n## \u003ca name=\"workers\"\u003e\u003c/a\u003e Workers\n`RxStack` offers an easy way to create a pool of workers for offloading computations as well as managing a pool of workers.\n\n[More details](https://github.com/rxstack/rxstack/tree/master/packages/worker-threads-pool)\n\n## \u003ca name=\"databases\"\u003e\u003c/a\u003e Databases\n\n`RxStack` provides several modules to work with databases via \n[`@rxstack/platform`](https://github.com/rxstack/rxstack/tree/master/packages/platform#services).\n\nHere is the list of built-in platform database services:\n\n\u003e You can use these services outside `@rxstack/platform`\n\n- [Memory Service](https://github.com/rxstack/memory-service)\n- [Mongoose Service](https://github.com/rxstack/mongoose-service)\n- [Sequelize Service](https://github.com/rxstack/sequelize-service)\n\nHere is an example of integrating [TypeORM](http://typeorm.io):\n\n\u003e We assume that mysql is installed and running on your machine.\n\nLet's install dependencies: \n\n```bash\n$ npm install --save typeorm mysql\n```\n\nNext step is to add the configurations:\n\n```typescript\n// my-project/src/environments/environment.ts\n\nimport {configuration} from '@rxstack/configuration';\n\n// ...\n  typeorm: {\n    type: 'mysql',\n    host: 'localhost',\n    port: 3306,\n    username: 'root',\n    password: 'root',\n    database: 'demo',\n    entities: [\n      configuration.getRootPath() + '/dist/app/entities/*.js'\n    ],\n    synchronize: true // set it to false in production\n  }\n```\n\nnow we can create the typeorm connection provider:\n\n```typescript\n// my-project/src/app/APP_COMMON_PROVIDERS.ts\nimport {ProviderDefinition} from '@rxstack/core';\nimport {Provider} from 'injection-js';\nimport {environment} from '../environments/environment';\nimport {createConnection, Connection as TypeormConnection} from 'typeorm';\nimport {MysqlConnectionOptions} from 'typeorm/driver/mysql/MysqlConnectionOptions';\n\nconst typeormProvider =  async function(): Promise\u003cProvider\u003e {\n  const connection: TypeormConnection = await createConnection(\u003cMysqlConnectionOptions\u003eenvironment.typeorm);\n  return { provide: TypeormConnection, useValue: connection};\n};\n\nexport const APP_COMMON_PROVIDERS: ProviderDefinition[] = [\n  // ...\n  typeormProvider()\n];\n```\n\n\u003e Pay attention how we register async providers\n\nLet's create the entity:\n\n```typescript\n// my-project/src/app/entities/cat.ts\n\nimport {Column, Entity, PrimaryGeneratedColumn} from 'typeorm';\n\n@Entity()\nexport class Cat {\n  @PrimaryGeneratedColumn()\n  id: number;\n\n  @Column()\n  name: string;\n\n  @Column()\n  age: number;\n\n  @Column()\n  breed: string;\n}\n```\n\nController usage:\n\n```typescript\nimport {Injectable} from 'injection-js';\nimport {Http, Request, Response, WebSocket} from '@rxstack/core';\nimport {ForbiddenException} from '@rxstack/exceptions';\nimport {Cat} from '../entities/cat';\nimport {Connection} from 'typeorm';\n\n@Injectable()\nexport class CatController {\n  \n  constructor(private connection: Connection) { }\n\n  @Http('POST', '/cats', 'app_cat_create')\n  @WebSocket('app_cat_create')\n  async createAction(request: Request): Promise\u003cResponse\u003e {\n    const repo = connection.getRepository(Cat);\n    const cat = await repo.merge(this.repo.create(), request.body);\n    return new Response(cat, 201);\n  }\n}\n```\n\nAs you see you can integrate any database framework with ease.\n\n## \u003ca name=\"testing\"\u003e\u003c/a\u003e Testing\nAutomated tests are an essential part of the fully functional software product. That is very critical to cover at least \nthe most sensitive parts of your system. In order to achieve that goal, we produce a set of different tests \nlike integration tests, unit tests, functional tests, and so on.\n\n\u003e `RxStack` uses [jest](https://jestjs.io/) testing frameworks. \n\n\n##### \u003ca name=\"testing-unit\"\u003e\u003c/a\u003e Unit Tests\n\nServices are often the easiest files to unit test. Let's create a `ValueService` :\n\n```typescript\n// my-project/src/app/services/value.service.ts\n\nexport class ValueService {\n  getValue(): string {\n    return 'real value';\n  }\n}\n```\n\nand test it:\n\n```typescript\n// my-project/test/unit/services/value.service.spec.ts\nimport {describe, expect, it} from '@jest/globals';\nimport {ValueService} from '../../../src/app/services/value.service';\n\ndescribe('Unit:ValueService', () =\u003e {\n  it('#getValue should return real value', async () =\u003e {\n    const valueService = new ValueService();\n    expect(valueService.getValue()).toBe('real value');\n  });\n});\n```\n\nServices often depend on other services, but injecting the real service rarely works well as most dependent \nservices are difficult to create and control.\nInstead you can mock the dependency, use a dummy value, or create a spy on the pertinent service method.\n\nLet's create a `MasterService` which depends on `ValueService`:\n\n```typescript\n// my-project/src/app/services/value.service.ts\n\nimport {ValueService} from './value.service';\n\nexport class MasterService {\n\n  constructor(private valueService: ValueService) { }\n\n  getValue(): string {\n    return this.valueService.getValue();\n  }\n}\n```\n\nand test it:\n\n```typescript\n// my-project/test/unit/services/master.service.spec.ts\nimport {describe, expect, it} from '@jest/globals';\nimport {MasterService} from '../../../src/app/services/master.service';\nimport {ValueService} from '../../../src/app/services/value.service';\n\nconst sinon = require('sinon');\n\ndescribe('Unit:MasterService', () =\u003e {\n\n  it('#getValue should return fake value', async () =\u003e {\n    const valueService = sinon.createStubInstance(ValueService);\n    valueService.getValue.returns('fake value');\n    const masterService = new MasterService(valueService);\n    expect(masterService.getValue()).toBe('fake value');\n  });\n});\n```\n\nThese standard testing techniques are great for unit testing services in isolation.\n\n### \u003ca name=\"testing-integration\"\u003e\u003c/a\u003e Integration Tests\n\nIntegration tests determine if independently developed units of software work correctly when they are connected to each other. \nTo test these services we need to bootstrap the application and pull them from the `Injector` and \nif needed we can replace services with stubs.\n\nLet's make our `MasterService` and `ValueService` services injectable:\n\n```typescript\nimport {Injectable} from 'injection-js';\n\n@Injectable()\nexport class MasterService {\n  // ...\n}\n\n@Injectable()\nexport class ValueService {\n  // ...\n}\n```\n\nand now we need to register them in service providers:\n\n```typescript\n// my-project/src/app/services/APP_SERVICE_PROVIDERS.ts\nimport {ProviderDefinition} from '@rxstack/core';\nimport {MasterService} from './master.service';\nimport {ValueService} from './value.service';\n\nexport const APP_SERVICE_PROVIDERS: ProviderDefinition[] = [\n  // ...\n  {\n    provide: MasterService,\n    useClass: MasterService\n  },\n  {\n    provide: ValueService,\n    useClass: ValueService\n  }\n];\n```\n\nand now let's test it:\n\n```typescript\n//  my-project/test/integration/services/master.service.spec.ts\n\nimport 'reflect-metadata';\nimport {describe, expect, it, beforeAll} from '@jest/globals';\nimport {Injector} from 'injection-js';\nimport {MasterService} from '../../../src/app/services/master.service';\nimport {app} from '../../../src/app/app';\n\ndescribe('Integration:MasterService', () =\u003e {\n\n  // Setup application\n  let injector: Injector;\n  let masterService: MasterService;\n\n  beforeAll(async () =\u003e {\n    await app.run();\n    injector = app.getInjector();\n    masterService = injector.get(MasterService);\n  });\n\n  it('#getValue should return real value', async () =\u003e {\n    expect(masterService.getValue()).toBe('real value');\n  });\n});\n```\n\nsometimes you need to replace the real service with the mock one:\n\n```typescript\n//  my-project/test/integration/services/master.service.spec.ts\nimport 'reflect-metadata';\nimport {describe, expect, it, beforeAll} from '@jest/globals';\nimport {configuration} from '@rxstack/configuration';\nconfiguration.initialize(configuration.getRootPath() + '/src/environments');\nimport {MasterService} from '../../../src/app/services/master.service';\nimport {Application} from '@rxstack/core';\nimport {Injector} from 'injection-js';\nimport {ValueService} from '../../../src/app/services/value.service';\nimport * as _ from 'lodash';\nimport {APP_OPTIONS} from '../../../src/app/APP_OPTIONS';\n\nconst sinon = require('sinon');\n\ndescribe('Integration:MasterService', () =\u003e {\n\n  // Setup application\n  const opt = _.cloneDeep(APP_OPTIONS); // clone it otherwise it will affect other tests\n  const valueService = sinon.createStubInstance(ValueService);\n  valueService.getValue.returns('fake value');\n\n  // replace the real service\n  opt.providers.push({\n    provide: ValueService,\n    useValue: valueService\n  });\n\n  const app = new Application(opt);\n  let injector: Injector;\n  let masterService: MasterService;\n\n  beforeAll(async () =\u003e {\n    injector = await app.run();\n    masterService = injector.get(MasterService);\n  });\n\n  it('#getValue should return fake value', async () =\u003e {\n    expect(masterService.getValue()).toBe('fake value');\n  });\n});\n```\n\n### \u003ca name=\"testing-functional\"\u003e\u003c/a\u003e Functional Tests \n \nFunctional tests let you check a controller action response:\n\n- Make a request (http or socket)\n- Test the response\n- Rinse and repeat\n\nAs an example, a test could look like this using [request-promise](https://github.com/request/request-promise) \nand [socket.io-client](https://github.com/socketio/socket.io-client) :\n\n\u003e You can use you any other http or socket client\n\n```typescript\nimport {describe, expect, it, beforeAll, afterAll} from '@jest/globals';\nimport {Injector} from 'injection-js';\nimport {ServerManager} from '@rxstack/core';\n\nconst fetch = require('node-fetch');\nconst io = require('socket.io-client');\n\ndescribe('Functional:Controllers:HelloController', () =\u003e {\n\n  // Setup application\n  let injector: Injector;\n  let httpHost: string;\n  let wsHost: string;\n  let conn: any;\n\n  beforeAll(async () =\u003e {\n    await app.start();\n    injector = app.getInjector();\n    httpHost = injector.get(ServerManager).getByName('express').getHost();\n    wsHost = injector.get(ServerManager).getByName('socketio').getHost();\n    conn = io(wsHost, {transports: ['websocket']});\n  });\n\n  afterAll(async () =\u003e {\n    await conn.close();\n    await app.stop();\n  });\n\n  it('#sayHello over http should return hello', async () =\u003e {\n    const options = {\n      uri: httpHost + '/hello',\n      resolveWithFullResponse: true,\n      json: false\n    };\n    \n    const response: any = await fetch( httpHost + '/hello');\n    const headers = response.headers;\n    const content = await response.text();\n    headers.get('x-powered-by').should.be.equal('Express');\n    expect(response.status).toBe(200);\n    expect(content).toBe('hello');\n    \n  });\n\n  it('#sayHello over socket should return hello', (done: Function) =\u003e {\n    conn.emit('app_hello', null, function (response: any) {\n      expect(response['statusCode']).toBe(200);\n      expect(response['content']).toBe('hello');\n      done();\n    });\n  });\n});\n```\n\n\u003e Tip: you can test the response content against JSON schema\n\nFor more examples how to test controller actions please check the `test` folder in \nthe [`@rxstack/skeleton`](https://github.com/rxstack/skeleton) application.\n\n\n## License\n\nLicensed under the [MIT license](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frxstack%2Frxstack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frxstack%2Frxstack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frxstack%2Frxstack/lists"}