{"id":20724498,"url":"https://github.com/hearit-io/fastify-redis-channels","last_synced_at":"2025-07-17T06:34:25.022Z","repository":{"id":56269007,"uuid":"312587356","full_name":"hearit-io/fastify-redis-channels","owner":"hearit-io","description":"A Fastify plugin for channels implementation based on Redis streams.","archived":false,"fork":false,"pushed_at":"2024-06-25T12:26:49.000Z","size":479,"stargazers_count":6,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-07-01T11:54:44.753Z","etag":null,"topics":["fastify","fastify-plugin","fastify-redis-channels","redis","redis-streams"],"latest_commit_sha":null,"homepage":null,"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/hearit-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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},"funding":{"github":"hearit-io","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2020-11-13T13:45:53.000Z","updated_at":"2024-06-25T12:24:12.000Z","dependencies_parsed_at":"2025-04-23T18:04:48.326Z","dependency_job_id":"4c6b5075-2302-46b4-8576-e78565f90546","html_url":"https://github.com/hearit-io/fastify-redis-channels","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/hearit-io/fastify-redis-channels","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hearit-io%2Ffastify-redis-channels","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hearit-io%2Ffastify-redis-channels/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hearit-io%2Ffastify-redis-channels/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hearit-io%2Ffastify-redis-channels/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hearit-io","download_url":"https://codeload.github.com/hearit-io/fastify-redis-channels/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hearit-io%2Ffastify-redis-channels/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265573561,"owners_count":23790464,"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":["fastify","fastify-plugin","fastify-redis-channels","redis","redis-streams"],"created_at":"2024-11-17T04:15:01.092Z","updated_at":"2025-07-17T06:34:24.957Z","avatar_url":"https://github.com/hearit-io.png","language":"JavaScript","funding_links":["https://github.com/sponsors/hearit-io"],"categories":[],"sub_categories":[],"readme":"# fastify-redis-channels\r\n\r\n[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](code_of_conduct.md)\r\n[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/)\r\n![NPM License](https://img.shields.io/npm/l/fastify-redis-channels)\r\n![NPM Downloads](https://img.shields.io/npm/dt/fastify-redis-channels)\r\n\r\nA Fastify plugin for fast, reliable, and scalable message broker implementation based on Redis streams.\r\n\r\nSuitable for IoT applications with massive network traffic, pub/sub-use cases, or any implementation with multiple producers/consumers.\r\n\r\nImplements a possibility to scale the message processing across different consumers, without single consumers having to process all the messages. A group of consumers, working as a team, can cooperate and consume a separate portion of those messages from the same channel.\r\n\r\nCan be used with a single Redis instance and later updated easily to a cluster configuration without need of any application change.\r\n\r\nThe implementation uses native Promises.\r\n\r\nDo you want your project to grow? Then start right from the begging.\r\n\r\n\r\nUnder the hood [@hearit-io/redis-channels](https://github.com/hearit-io/redis-channels#readme) is used, the options you pass to a register will be passed to the RedisChannels instance.\r\n\r\n\r\n## Table of Contents\r\n\r\n* [Install](#install)\r\n* [Usage](#usage)\r\n* [Learn by doing](#learn-by-doing)\r\n  * [Chat server](#chat-server)\r\n  * [Statistic worker](#statistic-worker)\r\n  * [Service Send Events example](#service-send-events-example)\r\n* [Project status](#project-status)\r\n* [Todo](#todo)\r\n* [Authors and acknowledgment](#authors-and-acknowledgment)\r\n* [License](#license)\r\n\r\n\r\n## Install\r\n\r\n```shell\r\n$ npm install fastify-redis-channels --save\r\n```\r\n\r\n## Usage\r\n\r\nAdd it to your project with register and that's it! \r\n\r\nYou can access the RedisChannels instance via `fastify.channels`. A RedisChannelsError object is accessible via `fastify.RedisChannelsError`.  \r\n\r\nIf you want to define a separate RedisChannels instace for a given namespace you can add a `namespace` property in the options. You can access the namespaced instance via `fastify.channels[namespace]`\r\n\r\nAll channels are automatically closed when a fastify instance is closed.\r\n\r\n## Learn by doing \r\n\r\n### Chat server\r\n\r\nWe will create a basic chat server based on websockets in this example.\r\n\r\n#### Step 1 - Install all required packages\r\n\r\nCreate an empty folder for your application and initialise it:\r\n\r\n```shell\r\nmkdir chat\r\ncd chat\r\nnpm init\r\n```\r\n\r\nInstall all required packages:\r\n\r\n```shell\r\nnpm install --save fastify fastify-websocket fastify-redis-channels\r\n```\r\n\r\n#### Step 2 - Create a chat room page\r\n\r\nThis step implements a chat room page where a user can send/receive messages to/from other users. \r\n\r\nIt automatically creates a chat room for everything in the root path of the URL.\r\n\r\nFor example, visiting `http://localhost/room1` and `http://localhost/room2` will create chat rooms `room1` and `room2`.\r\n\r\nCreate a `room.js` file with a following content:\r\n\r\n```javascript\r\n'use strict'\r\n\r\nfunction fastifyPluginRoom (fastify, opts, done) {\r\n\r\n  // Route to the room\r\n  fastify.get('/:room', (request, reply) =\u003e {\r\n    reply.type('text/html').send(view(request.params.room))\r\n  })\r\n\r\n  done()\r\n}\r\n\r\n// Builds a page view with a text area, input field and a submit button.\r\nfunction view (room) {\r\n  const page = `\r\n\r\n  \u003c!DOCTYPE html\u003e\r\n  \u003chtml\u003e\r\n  \u003chead\u003e\r\n    \u003cmeta charset=\"utf-8\"/\u003e\r\n    \u003ctitle\u003eChat room '${room}'\u003c/title\u003e\r\n  \u003c/head\u003e\r\n  \u003cbody\u003e\r\n    \u003ctextarea id=\"log\" cols=\"100\" rows=\"20\" readonly\u003e\u003c/textarea\u003e\u003cbr\u003e\r\n    \u003cinput id=\"input\" type=\"text\" size=\"100\"\u003e\u003cbr\u003e\r\n    \u003cinput id=\"submit\" type=\"button\" value=\"Send\"\u003e to room '${room}'\r\n    \r\n    \u003cscript\u003e\r\n      const ws = new WebSocket(\r\n        'ws://' + window.location.host + '/ws/' + '${room}'\r\n      )\r\n\r\n      ws.onmessage = function(e) {\r\n        const data = JSON.parse(e.data)\r\n        document.querySelector('#log').value += (data.message + '\\\\n')\r\n      }\r\n\r\n      ws.onclose = function(e) {\r\n        console.error('socket closed')\r\n      }\r\n\r\n      document.querySelector('#input').focus()\r\n      document.querySelector('#input').onkeyup = function(e) {\r\n        if (e.keyCode === 13) {\r\n          document.querySelector('#submit').click()\r\n        }\r\n      }\r\n\r\n      document.querySelector('#submit').onclick = function(e) {\r\n        const inputElem = document.querySelector('#input')\r\n        ws.send(JSON.stringify({ 'message': inputElem.value }))\r\n        inputElem.value = ''\r\n      }\r\n    \u003c/script\u003e\r\n  \u003c/body\u003e\r\n  \u003c/html\u003e\r\n  `\r\n  return page\r\n}\r\n\r\nmodule.exports = fastifyPluginRoom\r\n\r\n```\r\n\r\n#### Step 3 - Create a Fastify server\r\n\r\nIn this step we implement a simple Fastify server listening on port 3000.\r\n\r\nCreate a file `server.js` as shown bellow: \r\n\r\n```javascript\r\n'use strict'\r\n\r\nconst fastify = require('fastify')()\r\n\r\nfastify.register(require('./room'))\r\n\r\nfastify.ready(error =\u003e {\r\n  if (error) console.log(error)\r\n})\r\n\r\nfastify.listen({ port: 3000 }, (error, address) =\u003e {\r\n  if (error) console.log(error)\r\n  console.log('Listen on : ', address)\r\n})\r\n```\r\n\r\nRun the server with the command:\r\n\r\n```\r\nnpm start\r\n```\r\n\r\nYou will get on the console the following output:\r\n\r\n```\r\nListen on :  http://[::]:3000\r\n```\r\n\r\nYou should see your chat `room` on [http://localhost:3000/room]([http://localhost:3000/room)\r\n\r\n\r\n#### Step 4 - Create a consumer using channels\r\n\r\nIn this step we will create a consumer which broadcasts all messages received via websockets to all clients in the corresponding chat rooms.\r\n\r\n\r\nCreate a file `consumer.js` as shown below: \r\n\r\n```javascript\r\n'use strict'\r\n\r\nfunction fastifyConsumerPlugin(fastify, opts, done) {\r\n  try {\r\n\r\n    fastify.get('/ws/:room', { websocket: true }, handler)\r\n\r\n    done()\r\n  } catch(error) {\r\n    done(error)\r\n  }\r\n}\r\n\r\n\r\n// A websocket handle function (called once after a handshake)\r\nasync function handler(connection, req, params) {\r\n  const fastify = this\r\n\r\n  try {\r\n    // Creates a tunnel object to access a channel associated with the room.\r\n    const tunnel = await fastify.channels.use(params.room)\r\n\r\n    // Subscribes for messages.\r\n    await fastify.channels.subscribe(tunnel)\r\n\r\n    // Starts a consumer.\r\n    consume(fastify, connection, tunnel)\r\n      .then((resolve) =\u003e {\r\n        console.log('Consumer finished')\r\n      })\r\n      .catch((reject) =\u003e {\r\n        connection.socket.close()\r\n        return\r\n      })\r\n\r\n    // Produces received from a websocket messages to the corresponding tunnel.\r\n    connection.socket.on('message', async (message) =\u003e {\r\n      try {\r\n        connection.resume()\r\n        await fastify.channels.produce(tunnel, message)\r\n      } catch (error) {\r\n        connection.socket.close()\r\n        return\r\n      }\r\n    })\r\n\r\n    // Unsubscribe on websocket close\r\n    connection.socket.on('close', async () =\u003e {\r\n      await fastify.channels.unsubscribe(tunnel)\r\n    })\r\n  }\r\n  catch(error) {\r\n    connection.socket.close()\r\n  }\r\n}\r\n\r\n// A consumer implementation\r\n// Consumes messages from the tunnel and broadcast them to the websocket.\r\nasync function consume(fastify, connection, tunnel) {\r\n  for await (const messages of fastify.channels.consume(tunnel)) {\r\n    for (const i in messages) {\r\n      connection.socket.send(messages[i].data)\r\n    }\r\n  }\r\n}\r\n\r\nmodule.exports = fastifyConsumerPlugin\r\n```\r\n\r\nRegister all plugins in the fastify server. The file `server.js` should look like this:\r\n\r\n```javascript\r\n'use strict'\r\n\r\nconst fastify = require('fastify')()\r\n\r\nfastify.register(require('fastify-websocket'))\r\nfastify.after(error =\u003e {\r\n   if (error) console.log(error)\r\n})\r\n\r\nfastify.register(require('fastify-redis-channels'), {\r\n  channels: {\r\n    application: 'example',\r\n  },\r\n  redis: {\r\n    host: 'localhost',\r\n    port: 6379\r\n  }\r\n})\r\nfastify.after(error =\u003e {\r\n   if (error) console.log(error)\r\n})\r\n\r\nfastify.register(require('./consumer'))\r\nfastify.after(error =\u003e {\r\n   if (error) console.log(error)\r\n})\r\n\r\nfastify.register(require('./room'))\r\nfastify.ready(error =\u003e {\r\n  if (error) console.log(error)\r\n})\r\n\r\nfastify.listen({ port: 3000 }, (error, address) =\u003e {\r\n  if (error) console.log(error)\r\n  console.log('Listen on : ', address)\r\n})\r\n```\r\n\r\n#### Step 5 - Test your chat application\r\n\r\nBefore you test your chat, room application make sure you have up and running Redis server on the default host `localhost` and port `6379`. For more info about the installation see on Redis [download](https://redis.io/download) page.\r\n\r\n\r\nRun the server with the command:\r\n\r\n```shell\r\nnpm start\r\n```\r\n\r\nYou will get on the console the following output:\r\n\r\n```\r\nListen on :  http://[::]:3000\r\n```\r\n\r\nOpen in two browser window a link to our example chat `room` [http://localhost:3000/room]([http://localhost:3000/room).\r\n\r\nHave a fun with your chat! :)\r\n\r\nThe complete example is available here [fastify-redis-channels-chat-example](https://github.com/hearit-io/fastify-redis-channels-chat-example).\r\n\r\n### Statistic worker\r\n\r\nIn this example, our web server for each request will send through a channel `statistic` a data (user agent and the IP address) to a separate worker process for a further processing. This will offload a server from a resource expensive operations.  \r\n\r\n#### Step 1 - Install all required packages\r\n\r\nCreate an empty folder for your application and initialise it:\r\n\r\n```shell\r\nmkdir worker\r\ncd worker\r\nnpm init\r\n```\r\n\r\nInstall all required packages:\r\n\r\n```shell\r\nnpm install --save fastify fastify-redis-channels @hearit-io/redis-channels\r\n```\r\n#### Step 2 - Create a Fastify server\r\n\r\nIn this step we implement a simple Fastify server listening on port 3000. \r\nFor each requiest on a route `/` the server will produce a message in the channel `statistic`.\r\n\r\n\r\nCreate a file `server.js` as shown bellow: \r\n\r\n```javascript\r\n'use strict'\r\n\r\nconst fastify = require('fastify')()\r\n\r\nfastify.register(require('fastify-redis-channels'), {\r\n  channels: {\r\n    application: 'worker',\r\n  },\r\n  redis: {\r\n    host: 'localhost',\r\n    port: 6379\r\n  }\r\n})\r\nfastify.ready(error =\u003e {\r\n  if (error) console.log(error)\r\n})\r\n\r\nfastify.get('/', async (request, reply) =\u003e {\r\n\r\n  // Produces a statistic message for each request\r\n  const tunnel = await fastify.channels.use('statistic')\r\n  const message = {\r\n    agent: request.headers['user-agent'],\r\n    ip: request.ip\r\n  }\r\n  fastify.channels.produce(tunnel, JSON.stringify(message))\r\n\r\n  reply.type('text/html').send('Hello World')\r\n})\r\n\r\nfastify.listen({ port: 3000 }, (error, address) =\u003e {\r\n  if (error) console.log(error)\r\n  console.log('Listen on : ', address)\r\n})\r\n\r\n```\r\n#### Step 3 - Create a worker process\r\n\r\nIn this step we will implement a consumer worker process with two consumers (working in a team). \r\n\r\nCreate a file `worker.js` as shown bellow: \r\n\r\n```javascript\r\n'use strict'\r\n\r\nconst {RedisChannels} = require('@hearit-io/redis-channels')\r\n\r\n// The channels instance\r\nconst channles = new RedisChannels({\r\n  channels: {\r\n    application: 'worker',\r\n  },\r\n  redis: {\r\n    host: 'localhost',\r\n    port: 6379\r\n  }\r\n})\r\n\r\n// Handle Control-D, Control-C\r\nasync function handle(signal) {\r\n  const channels = this\r\n  await channels.cleanup()\r\n}\r\nprocess.on('SIGINT', handle.bind(channles))\r\nprocess.on('SIGTERM', handle.bind(channles))\r\nprocess.on('SIGHUP', handle.bind(channles))\r\n\r\n// A consumer function\r\nasync function consume(tunnel, channels) {\r\n  for await (const messages of channels.consume(tunnel)) {\r\n    for (const i in messages) {\r\n      // Process a message\r\n      const data = JSON.parse(messages[i].data)\r\n      console.log(data)\r\n    }\r\n  }\r\n}\r\n// The main loop\r\nasync function main () {\r\n  try {\r\n\r\n    // Creates tunnels to 'statistic'\r\n    const tunnelConsumerOne = await channles.use('statistic')\r\n    const tunnelConsumerTwo = await channles.use('statistic')\r\n\r\n    // Subscribe consumers in team\r\n    await channles.subscribe(tunnelConsumerOne, 'team')\r\n    await channles.subscribe(tunnelConsumerTwo, 'team')\r\n\r\n    // Start all consumers\r\n    consume(tunnelConsumerOne, channles).catch(() =\u003e { })\r\n    consume(tunnelConsumerTwo, channles).catch(() =\u003e { })\r\n\r\n  }\r\n  catch (error) {\r\n    console.log(error)\r\n  }\r\n}\r\nmain()\r\n\r\n```\r\n\r\n#### Step 4 - Test a statistic worker\r\n\r\nIn order to test the worker open two console windows and start a Fastify server and a worker process.\r\n\r\nStart the server: \r\n\r\n```shell\r\nnpm start\r\n```\r\n\r\nStart the worker process:\r\n\r\n```shell\r\nnode worker.js\r\n```\r\n\r\nA request to [http://localhost:3000/](http://localhost:3000/) wil result in a worker console output like this:\r\n\r\n```shell\r\n{\r\n  agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 Edg/86.0.622.69',\r\n  ip: '::ffff:78.83.64.18'\r\n}\r\n{\r\n  agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',\r\n  ip: '::ffff:78.83.64.18'\r\n}\r\n\r\n```\r\nThe complete example is available here [fastify-redis-channels-worker-example](https://github.com/hearit-io/fastify-redis-channels-worker-example).\r\n\r\n### Service Send Events example\r\n\r\nThis example presents a real use case of SSE similar to our `HEARIT.IO` implementation. See here the complete [sources](https://github.com/hearit-io/fastify-redis-channels-sse-example).\r\n\r\nThe live SSE frontend implementation from our `HEARIT.IO` project is available [here](https://app.hearit.io/static/app.hearit.io/push.mjs).\r\n\r\nIt demonstrates a reliable way of delivering service send events by using Redis stream ids and detecting gaps.\r\n\r\n## Project status\r\n\r\n### [hearit.io](https://hearit.io)\r\n\r\n\r\n\u003cimg src=\"https://raw.githubusercontent.com/hearit-io/graphics/main/hearing-black-96dp.svg\" width=\"48\" height=\"48\"/\u003e | Smart home automatization designed for visually impaired people.\r\n------------ | -------------\r\n\r\n**@heart-io/redis-channels** is used productive in our progressive web [app](https://app.hearit.io). You can try it with a user `demo@hearit.io` and password: `11223344`\r\n\r\nThe package will be updated and maintained regularly.\r\n\r\nThe main goal of [hearit.io](https://hearit.io) is to make accessible the world of IoT to everyone. We created a speaking home suitable for all.\r\n\r\nWe will be grateful to you if you make awareness to other people of our project. \r\n\r\nTo finance our idea, we would be glad to support and work on your projects. Contact Emil \u003cemil@hearit.io\u003e for details and our availability.\r\n\r\nOther open-source packages, part of our project, will be available soon. We use [Fastify](http://fastify.io) as an application framework.\r\n\r\n## Todo\r\n- [x] Add plugin unit tests.\r\n- [x] Add a possibility to have multiple channels instances (per `namespace`).\r\n- [ ] TypeScript support.\r\n\r\n\r\n## Authors and acknowledgment\r\n\r\nEmil Usunov\r\n\r\n[hearit.io](https://hearit.io)\r\n\r\n\r\n## License\r\n\r\nMIT\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhearit-io%2Ffastify-redis-channels","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhearit-io%2Ffastify-redis-channels","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhearit-io%2Ffastify-redis-channels/lists"}