{"id":13650486,"url":"https://github.com/moleculerjs/moleculer-io","last_synced_at":"2025-06-29T15:33:40.129Z","repository":{"id":43572571,"uuid":"138617881","full_name":"moleculerjs/moleculer-io","owner":"moleculerjs","description":"Socket.io API GateWay service for Moleculer framework","archived":false,"fork":false,"pushed_at":"2022-11-07T18:32:34.000Z","size":854,"stargazers_count":84,"open_issues_count":6,"forks_count":30,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-06-09T15:46:17.366Z","etag":null,"topics":["api-gateway","hacktoberfest","microservice","moleculer","nodejs","socket-io","socketio","websocket"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/moleculerjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-06-25T15:59:42.000Z","updated_at":"2025-04-16T16:28:41.000Z","dependencies_parsed_at":"2023-01-21T06:01:55.801Z","dependency_job_id":null,"html_url":"https://github.com/moleculerjs/moleculer-io","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/moleculerjs/moleculer-io","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moleculerjs%2Fmoleculer-io","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moleculerjs%2Fmoleculer-io/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moleculerjs%2Fmoleculer-io/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moleculerjs%2Fmoleculer-io/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moleculerjs","download_url":"https://codeload.github.com/moleculerjs/moleculer-io/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moleculerjs%2Fmoleculer-io/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260576381,"owners_count":23030582,"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","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":["api-gateway","hacktoberfest","microservice","moleculer","nodejs","socket-io","socketio","websocket"],"created_at":"2024-08-02T02:00:37.139Z","updated_at":"2025-06-29T15:33:40.110Z","avatar_url":"https://github.com/moleculerjs.png","language":"JavaScript","funding_links":[],"categories":["Services"],"sub_categories":["Gateway"],"readme":"[![Moleculer logo](http://moleculer.services/images/banner.png)](https://github.com/moleculerjs/moleculer)\n\n[![CI test](https://github.com/moleculerjs/moleculer-io/actions/workflows/ci.yml/badge.svg)](https://github.com/moleculerjs/moleculer-io/actions/workflows/ci.yml)\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/moleculerjs/moleculer-io/master/LICENSE)\n[![npm](https://img.shields.io/npm/v/moleculer-io.svg)](https://www.npmjs.com/package/moleculer-io)\n[![Known Vulnerabilities](https://snyk.io/test/github/moleculerjs/moleculer-io/badge.svg)](https://snyk.io/test/github/moleculerjs/moleculer-io)\n[![Downloads](https://img.shields.io/npm/dm/moleculer-io.svg)](https://www.npmjs.com/package/moleculer-io)\n\u003ch1\u003eMoleculer-io\u003c/h1\u003e\n\nThe `moleculer-io` is a Websocket gateway service for [Moleculer](https://github.com/moleculerjs/moleculer) using `socket.io`.\n\n\u003ch1\u003eFeatures\u003c/h1\u003e\n- Call moleculer actions by emiting Socket.io events.\n- Support Socket.io authorization (Default: `socket.client.user` =\u003e moleculer `ctx.meta.user`)\n- Whitelist.\n- Middlewares.\n- Broadcast events.\n- Joining and leaving rooms.\n\n\u003ch1\u003eInstall\u003c/h1\u003e\n\n```shell\n$ npm install moleculer-io\n```\n\n\u003ch1\u003eTable of contents\u003c/h1\u003e\n\u003c!-- TOC depthFrom:1 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 --\u003e\n\n- [Usage](#usage)\n  - [Init server](#init-server)\n  - [Handle socket events](#handle-socket-events)\n  - [Handle multiple events](#handle-multiple-events)\n  - [Aliases](#aliases)\n    - [Mapping policy](#mapping-policy)\n  - [Custom handler](#custom-handler)\n  - [Handler hooks](#handler-hooks)\n  - [Calling options](#calling-options)\n  - [Middlewares](#middlewares)\n  - [Authorization](#authorization)\n  - [Joining and leaving rooms](#joining-and-leaving-rooms)\n  - [Broadcast](#broadcast)\n  - [CORS](#cors)\n  - [Using multiple instances](#using-multiple-instances)\n  - [Logging settings](#logging-settings)\n  - [Full settings](#full-settings)\n  - [License](#license)\n  - [Contact](#contact)\n\n\u003c!-- /TOC --\u003e\n\n\n# Usage\n\n## Init server\n\nUsing with Node http server:\n\n```javascript\nconst server = require('http').Server(app)\nconst SocketIOService = require(\"moleculer-io\")\nconst ioService = broker.createService({\n  name: 'io',\n  mixins: [SocketIOService]\n})\n\nioService.initSocketIO(server)\n\n// Once the initSocketIO() was called, you can access the io object from ioService.io\nbroker.start()\nserver.listen(3000)\n```\n\nOr let moleculer-io create a server for you:\n\n```javascript\nbroker.createService({\n  name: 'io',\n  mixins: [SocketIOService],\n  settings: {\n    port: 3000 //will call initSocketIO() on broker.start()\n  }\n})\nbroker.start()\n```\n\nOr maybe you want to use it with `moleculer-web`\n\n```js\nconst ApiService = require(\"moleculer-web\");\nconst SocketIOService = require(\"moleculer-io\")\nbroker.createService({\n  name: 'gateway',\n  mixins: [ApiService, SocketIOService], //Should after moleculer-web\n  settings: {\n    port: 3000\n  }\n})\nbroker.start()\n```\nIn this case, `moleculer-io` will use the server created by `moleculer-web` .\n\n## Handle socket events\n\nServer:\n\n```javascript\nconst IO = require('socket.io')\nconst {\n  ServiceBroker\n} = require('moleculer')\nconst SocketIOService = require('moleculer-io')\n\nconst broker = new ServiceBroker()\n\nbroker.createService({\n  name: \"math\",\n  actions: {\n    add(ctx) {\n      return Number(ctx.params.a) + Number(ctx.params.b);\n    }\n  }\n})\n\nconst ioService = broker.createService({\n  name: 'io',\n  mixins: [SocketIOService],\n  settings: {\n    port: 3000\n  }\n})\n\nbroker.start()\n```\n\nBy default, `moleculer-io` handle the `call` event which will proxy to moleculer's `broker.call`\n\nExamples:\n\n-   Call `test.hello` action without params: `socket.emit('call','test.hello', callback)`\n-   Call `math.add` action with params: `socket.emit('call','math.add', {a:25, b:13}, callback)`\n-   Get health info of node: `socket.emit('call','$node.health', callback)`\n-   List all actions: `socket.emit('call', '$node.actions', callback)`\n\n**Example client:**\n\n```javascript\nconst io = require('socket.io-client')\nconst socket = io('http://localhost:3000')\nsocket.emit('call', 'math.add', { a: 123, b: 456},\nfunction(err, res) {\n  if (err) {\n    console.error(err)\n  } else {\n    console.log('call success:', res)\n  }\n})\n```\n\n## Handle multiple events\n\nYou can create multiple routes with different whitelist, calling options \u0026 authorization.\n\n```javascript\nbroker.createService({\n  name: 'io',\n  mixins: [SocketIOService],\n  settings: {\n    port: 3000,\n    io: {\n      namespaces: {\n        '/': {\n          events: {\n            'call': {\n              whitelist: [\n                'math.add'\n              ],\n              callOptions: {}\n            },\n            'adminCall': {\n              whitelist: [\n                'users.*',\n                '$node.*'\n              ]\n            }\n          }\n        }\n      }\n    }\n  }\n})\n```\n\n## Aliases\n\nYou can use alias names instead of action names.\n\n```javascript\nbroker.createService({\n  name: 'io',\n  mixins: [SocketIOService],\n  settings: {\n    port: 3000,\n    io: {\n      namespaces: {\n        '/': {\n          events: {\n            'call': {\n              aliases: {\n                'add': 'math.add'\n              },\n              whitelist: [\n                'math.add'\n              ],\n              callOptions: {}\n            }\n          }\n        }\n      }\n    }\n  }\n})\n```\n\nThen doing `socket.emit('call','math.add', {a:25, b:13}, callback)` on the client side will be equivalent to `socket.emit('call','add', {a:25, b:13}, callback)`.\n\n### Mapping policy\n\nThe `event` has a `mappingPolicy` property to handle events without aliases.\n\n*Available options:*\n* `all` - enable to handle all actions with or without aliases (default)\n* `restrict` - enable to handle only the actions with aliases\n\n```javascript\nbroker.createService({\n  name: 'io',\n  mixins: [SocketIOService],\n  settings: {\n    port: 3000,\n    io: {\n      namespaces: {\n        '/': {\n          events: {\n            'call': {\n              mappingPolicy: 'restrict',\n              aliases: {\n                'add': 'math.add'\n              },\n              callOptions: {}\n            }\n          }\n        }\n      }\n    }\n  }\n})\n```\n\n## Custom handler\n\nYou can make use of custom functions within the declaration of an event handler.\n\n```javascript\nbroker.createService({\n  name: 'io',\n  mixins: [SocketIOService],\n  settings: {\n    port: 3000,\n    io: {\n      namespaces: {\n        '/': {\n          events: {\n            'call': {},\n            'myCustomEventHandler': function(data, ack) { // write your handler function here.\n              let socket = this\n              socket.emit('hello', 'world')\n              socket.$service.broker.call('math.add', {\n                a: 123,\n                b: 456\n              })\n            }\n          }\n        }\n      }\n    }\n  }\n})\n```\nThere is an internal pointer in socket objects:\n- `socket.$service` is pointed to this service instance.\n\n\n## Handler hooks\n\nThe event handler has before \u0026 after call hooks. You can use it to set ctx.meta, access socket object or modify the response data.\n\n```javascript\nbroker.createService({\n  name: 'io',\n  mixins: [SocketIOService],\n  settings: {\n    io: {\n      namespaces: {\n        '/': {\n          events: {\n            'call': {\n              whitelist: [\n                'math.*'\n              ],\n              onBeforeCall: async function(ctx, socket, action, params, callOptions) { //before hook\n                  console.log('before hook:', params)\n                },\n              onAfterCall: async function(ctx, socket, res) { //after hook\n                console.log('after hook', res)\n                // res: The respose data.\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n})\n```\n\n## Calling options\n\nThe handler has a callOptions property which is passed to broker.call. So you can set timeout, retryCount or fallbackResponse options for routes.\n\n```javascript\nbroker.createService({\n  name: 'io',\n  mixins: [SocketIOService],\n  settings: {\n    io: {\n      namespaces: {\n        '/': {\n          events: {\n            'call': {\n              callOptions: {\n                timeout: 500,\n                retryCount: 0,\n                fallbackResponse(ctx, err) { ...\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n})\n```\n\nNote: If you provide a meta field here, it replaces the socketGetMeta method's result.\n\n## Middlewares\n\nRegister middlewares. Both namespace middlewares and packet middlewares are supported.\n\n```javascript\nbroker.createService({\n  name: 'io',\n  mixins: [SocketIOService],\n  settings: {\n    io: {\n      namespaces: {\n        '/': {\n          middlewares: [ //Namespace level middlewares, equipment to namespace.use()\n            function(socket, next) {\n              if (socket.request.headers.cookie) return next();\n              next(new Error('Authentication error'));\n            }\n          ],\n          packetMiddlewares: [ // equipment to socket.use()\n            function(packet, next) {\n              if (packet.doge === true) return next();\n              next(new Error('Not a doge error'));\n            }\n          ],\n          events: {\n            'call': {}\n          }\n        }\n      }\n    }\n  }\n})\n```\n\n**Note:** In middlewares the `this` is always pointed to the Service instance.\n\n## Authorization\n\nYou can implement authorization. Do 2 things to enable it.\n\n- Set `authorization: true` in your namespace\n- Define the `socketAuthorize` method in service.\n\n```javascript\nbroker.createService({\n  name: 'io',\n  mixins: [SocketIOService],\n  settings: {\n    io: {\n      namespaces: {\n        '/': {\n          authorization: true, // First thing\n          events: {\n            'call': {\n              whitelist: [\n                'math.*',\n                'accounts.*'\n              ]\n            }\n          }\n        }\n      }\n    }\n  },\n  methods: {\n    // Second thing\n    async socketAuthorize(socket, eventHandler){\n      let accessToken = socket.handshake.query.token\n      if (accessToken) {\n        try{\n          let user = await this.broker.call(\"user.verifyToken\", {accessToken})\n          return {id: user.id, email: user.email, token: accessToken}  // valid credential, return the user\n        }catch(err){\n          throw new UnAuthorizedError() // invalid credentials\n        }\n      } else {\n        // anonymous user\n        return\n      }\n    }\n  }\n})\n```\n\nClient:\n\n```javascript\nconst socket = io({\n  query: {\n    token: '12345'\n  }\n})\n```\n\nSee [`examples/full`](examples/full)\n\nAlso you could overwrite the `socketGetMeta` method to add more addition meta info. The default `socketGetMeta` method is:\n\n```javascript\nsocketGetMeta(socket){\n  return {\n    user: socket.client.user,\n    $rooms: Object.keys(socket.rooms)\n  }\n}\n```\n\nExample to add more additional info:\n\n```javascript\nbroker.createService({\n  name: 'io',\n  mixins: [SocketIOService],\n  methods: {\n    socketGetMeta(socket) { //construct the meta object.\n      return {\n        user: socket.client.user,\n        $rooms: Object.keys(socket.rooms),\n        socketId: socket.id\n      }\n    },\n    // In addition, you can also customize the place where user is stored.\n    // Here is the default method the save user:\n    socketSaveMeta(socket, ctx) {\n      socket.client.user = ctx.meta.user\n    }\n  }\n})\n```\n\nIf you want to authorize a user after socket connected, you can write an action to do it.\n```js\nbroker.createService({\n  name: 'accounts',\n  actions: {\n    login(ctx){\n      if(ctx.params.user == 'tiaod' \u0026\u0026 ctx.params.password == 'pass'){\n        ctx.meta.user = {id:'tiaod'}\n      }\n    },\n    getUserInfo(ctx){\n      return ctx.meta.user\n    }\n  }\n})\n```\n## Joining and leaving rooms\n\nIn your action, set `ctx.meta.$join` or `ctx.meta.$leave` to the rooms you want to join or leave.\n\neg.\n\n```javascript\nctx.meta.$join = 'room1' //Join room1\nctx.meta.$join = ['room1', 'room2'] // Join room1 and room2\n\nctx.meta.$leave = 'room1' //Leave room1\nctx.meta.$leave = ['room1', 'room2'] // Leave room1 and room2\n```\n\nAfter the action is finished, `moleculer-io` will join or leave the room you specified.\n\n**Example room management service:**\n\n```javascript\nbroker.createService({\n  name: 'rooms',\n  actions: {\n    join(ctx){\n      ctx.meta.$join = ctx.params.room\n    },\n    leave(ctx){\n      ctx.meta.$leave = ctx.params.room\n    },\n    list(ctx){\n      return ctx.meta.$rooms\n    }\n  }\n})\n```\n\n## Broadcast\n\nIf you want to broadcast event to socket.io from moleculer service:\n\n```javascript\nbroker.call('io.broadcast', {\n  namespace:'/', //optional\n  event:'hello',\n  args: ['my', 'friends','!'], //optional\n  volatile: true, //optional\n  local: true, //optional\n  rooms: ['room1', 'room2'] //optional\n})\n```\n\n_Note: You should change the 'io' to the service name you created._\n\n## CORS\n`moleculer-io` will pick the `settings.cors.origin` option and use it to validate the request. (Which is also compatible with `moleculer-web`! )\n\n```js\nbroker.createService({\n  name: 'io',\n  mixins: [ApiGateway, SocketIOService],\n  settings:{\n\t\tcors: {\n\t\t\torigin: [\"http://example.com\"], //Moleculer-io only pick up this option and set it to io.origins()\n\t\t\tmethods: [\"GET\", \"OPTIONS\", \"POST\", \"PUT\", \"DELETE\"],\n\t\t\tallowedHeaders: [],\n\t\t\texposedHeaders: [],\n\t\t\tcredentials: false,\n\t\t\tmaxAge: 3600\n\t\t}\n\t}\n})\n```\nFor detail see https://socket.io/docs/server-api/#server-origins-fn\n\n## Using multiple instances\n\nIf you plan for a highly available setup (launching multiple instances of this service behind a Load Balancer),\nyou will have to take some extra steps. Due to the nature of WebSockets these instances will need a PubSub capable broker\nto connect to, in order to broadcast messages to sockets that are connected to other instances. For a more\nin depth explanation of this concept, and additional steps that have to be taken (such as Load Balancer configuration), refer to the [Socket.io Documentation](https://socket.io/docs/using-multiple-nodes/).\n\nIn order to interconnect this service with other services, start the service with an adapter:\n\n```javascript\nconst broker = new ServiceBroker({\n    transporter: \"redis://redis:6379\"\n})\nbroker.createService({\n  name: 'io',\n  mixins: [SocketIOService],\n  settings: {\n    port: 3000,\n    io: {\n      options: {\n        adapter: require(\"socket.io-redis\")(\"redis://redis:6379\")\n      }\n    }\n  }\n})\n```\n\n## Logging settings\n\nIf you want to keep clean your project console or have a more deep way to debug the data sent over the socket you can just change some settings in your service to add/remove logs:\n\n- **logRequest**: Log all the incoming request through the socket\n- **logRequestParams**: Log the request params\n- **logResponse**: Log response data\n- **logBroadcastRequest**: Log the request to forward to the sockets\n- **logClientConnection**: Log when a client gets connected\n\nTo start logging something indicate whatever logging level you want:\n\n```javascript\nconst broker = new ServiceBroker({\n    transporter: \"redis://redis:6379\"\n})\nbroker.createService({\n  name: 'io',\n  mixins: [SocketIOService],\n  settings: {\n    logClientConnection: 'info'\n    port: 3000,\n    io: {\n      options: {\n        adapter: require(\"socket.io-redis\")(\"redis://redis:6379\")\n      }\n    }\n  }\n})\n```\n\nlogRequest, logRequestParams, logResponse are adopted from the API gateway, the other ones are managed only in this one and by default they are disabled\n\n## Full settings\n\n```javascript\nsettings: {\n  port: 3000,\n  io: {\n    options: {}, //socket.io options\n    namespaces: {\n      '/':{\n        authorization: false,\n        middlewares: [],\n        packetMiddlewares:[],\n        events: {\n          call: {\n            mappingPolicy: 'all',\n            aliases: {\n              'add': 'math.add'\n            },\n            whitelist: [\n              'math.*'\n            ],\n            callOptions:{},\n            onBeforeCall: async function(ctx, socket, action, params, callOptions){\n              ctx.meta.socketid = socket.id\n            },\n            onAfterCall:async function(ctx, socket, res){\n             socket.emit('afterCall', res)\n            }\n          }\n        }\n      }\n    }\n  }\n}\n```\n## License\n\nThe project is available under the [MIT license](https://tldrlegal.com/license/mit-license).\n\n## Contact\n\nCopyright (c) 2021 MoleculerJS\n\n[![@MoleculerJS](https://img.shields.io/badge/github-moleculerjs-green.svg)](https://github.com/moleculerjs) [![@MoleculerJS](https://img.shields.io/badge/twitter-MoleculerJS-blue.svg)](https://twitter.com/MoleculerJS)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoleculerjs%2Fmoleculer-io","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoleculerjs%2Fmoleculer-io","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoleculerjs%2Fmoleculer-io/lists"}