{"id":21672402,"url":"https://github.com/redis-developer/basic-redis-chat-demo-go","last_synced_at":"2025-04-12T03:52:58.744Z","repository":{"id":45091365,"uuid":"337742021","full_name":"redis-developer/basic-redis-chat-demo-go","owner":"redis-developer","description":"A Basic redis chat demo app written in Go language","archived":false,"fork":false,"pushed_at":"2023-06-27T07:34:10.000Z","size":15596,"stargazers_count":34,"open_issues_count":4,"forks_count":17,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-12T03:52:53.197Z","etag":null,"topics":[],"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/redis-developer.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":"2021-02-10T14:03:49.000Z","updated_at":"2025-02-05T23:14:39.000Z","dependencies_parsed_at":"2024-06-20T04:25:08.229Z","dependency_job_id":"917cf194-504b-4286-a4d1-960a8f27831c","html_url":"https://github.com/redis-developer/basic-redis-chat-demo-go","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redis-developer%2Fbasic-redis-chat-demo-go","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redis-developer%2Fbasic-redis-chat-demo-go/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redis-developer%2Fbasic-redis-chat-demo-go/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redis-developer%2Fbasic-redis-chat-demo-go/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/redis-developer","download_url":"https://codeload.github.com/redis-developer/basic-redis-chat-demo-go/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248514209,"owners_count":21116899,"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":[],"created_at":"2024-11-25T13:29:13.109Z","updated_at":"2025-04-12T03:52:58.724Z","avatar_url":"https://github.com/redis-developer.png","language":"JavaScript","readme":"# Basic Redis Chat App Demo\n\nA basic chat application built with Golang, Websocket and Redis.\n\n\u003ca href=\"https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-dotnet/main/docs/screenshot000.png?raw=true\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-dotnet/main/docs/screenshot000.png?raw=true\" width=\"49%\"\u003e\u003c/a\u003e\n\n\u003ca href=\"https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-dotnet/main/docs/screenshot001.png?raw=true\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/redis-developer/basic-redis-chat-app-demo-dotnet/main/docs/screenshot001.png?raw=true\" width=\"49%\"\u003e\u003c/a\u003e\n\n# Overview video\n\nHere's a short video that explains the project and how it uses Redis:\n\n[![Watch the video on YouTube](https://github.com/redis-developer/basic-redis-chat-demo-go/raw/master/docs/YTThumbnail.png)](https://www.youtube.com/watch?v=miK7xDkDXF0)\n\n## Technical Stacks\n\n- Frontend - _React_, _Socket_\n- Backend - _Go_, _Redis_ (go-redis/redis)\n\n## How it works?\n\n### Client Server\n\nCommunication build on websocket messages.\n\nClient should handle messages from websocket with `onmessage(event)` event and processed it, where `event.data`\nis stringify JSON.\n\nSend request to server available with websocket `send(data)` function, where `data` is `JSON.stringify(Object)`.\n\nServer receive and response stringify JSON object.\n\nUsually for not atomic message sent with `type:\"example\"` server respond result will be with\nsame message type: `type:\"example\"`, for details see `Chat with websocket` block below.\n\nServer not guarantee that responses is ordered as requests order.\n\nAs example:\n\nClient requests\n\n```\n\u003e\u003e\u003e Request A\n\u003e\u003e\u003e Request B\n\u003e\u003e\u003e Request C\n```\n\nMay have responses in other order\n\n```\n\u003c\u003c\u003c Response C\n\u003c\u003c\u003c Response A\n\u003c\u003c\u003c Response B\n```\n\nFor complicated throws client should receive response for previous sent message before send next message.\n\nAs example in a pseudocode:\n\n```\n\u003e\u003e\u003e Open websocket\n\u003c\u003c\u003c Waiting for message type ready\n\u003e\u003e\u003e Send message type signIn\n\u003c\u003c\u003c Waiting for message type authorized\n\u003e\u003e\u003e Send message type channelJoin\n\u003c\u003c\u003c Waiting for message type channelJoin\n\u003e\u003e\u003e Now send multiple message to websocket and multiple receive messages\n```\n\n### Registration\n\n![How it works](docs/screenshot000.png)\n\n#### User sign in\n\nLogin for chatting, if user not exist it will be created.\n\nSend a message to websocket:\n\n```\n{\n    type: \"signIn\",\n    signIn: {\n        username: \"Username\",\n        password: \"Password\"\n    }\n}\n```\n\nReceive a message from websocket:\n\n```\n{\n    type: \"authorized\",\n    authorized: {\n        userUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n        accessKey: \"generated session access key\"\n    }\n}\n```\n\nSend system message to all connected users on success:\n\n```\n{\n    type: \"sys\",\n    sys: {\n        type: \"signIn\",\n        signIn: {\n            uuid: \"123e4567-e89b-12d3-a456-426614174000\",\n            username: \"Username\"\n        }\n    }\n}\n```\n\n##### Redis Commands\n\n- Key for store user index by user UUID: `usersUUIDListIndex:\u003cUserUUID\u003e`\n  - E.g `usersUUIDListIndex:123e4567-e89b-12d3-a456-426614174000`\n\n###### How the data is stored:\n\nUser structure:\n\n```\n{\n    UUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    Username: \"User name\",\n    Password: \"Password\",\n    AccessKey: \"Access key\",\n    OnLine: true\n}\n```\n\n- Add user to the end redis list, will return number of elements in success: `RPUSH users \u003cJSON Stringify user structure\u003e`. User index in the list is `\u003cnumber of elements in list\u003e - 1`\n\n  - E.g `RPUSH users \"{\\\"UUID\\\":\\\"123e4567-e89b-12d3-a456-426614174000\\\",\\\"Username\\\":\\\"User name\\\",\\\"Password\\\":\\\"Password\\\",\\\"AccessKey\\\":\\\"Access key\\\",\\\"OnLine\\\":true}\"`\n\n- Save index for search user by Username:\n\n  - E.g `SET usersUsernameListIndex:123e4567-e89b-12d3-a456-426614174000 4` where 4 is **User Index**\n\n- Save index for search user by UUID:\n\n  - E.g `SET usersUUIDListIndex:123e4567-e89b-12d3-a456-426614174000 4`\n\n- On error, we should remove index for search by Username:\n\n  - E.g `DEL usersUsernameListIndex:123e4567-e89b-12d3-a456-426614174000`\n\n- Set user online status, should expire after 60sec:\n  - E.g `SETEX userStatus:123e4567-e89b-12d3-a456-426614174000 2021-04-06T12:53:10.436Z 60` where we pass the start date in the ISO string format.\n\n###### How the data is accessed:\n\n- Read user by UUID from redis KV, on exist will return user index for users list in redis:\n\n  - E.g `GET usersUUIDListIndex:123e4567-e89b-12d3-a456-426614174000`\n\n- Read user by username from redis KV, on exists will return user index for users list in redis:\n  - E.g `GET usersUsernameListIndex:123e4567-e89b-12d3-a456-426614174000`\n\nKey for store users in LList: `users`\n\n- Read user from the start list by user index in list, will return json stringify on success:\n  - E.g `lindex users 5` where **5** is index.\n\n#### User sign up\n\nNew user registration, will append `signIn` on successful.\n\nSend a message to websocket:\n\n```\n{\n    type: \"signUp\",\n    signUp: {\n        username: \"Username\",\n        password: \"Password\"\n    }\n}\n```\n\nReceive a message from websocket:\n\n```\n{\n    type: \"authorized\",\n    authorized: {\n        userUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n        accessKey: \"generated session access key\"\n    }\n}\n```\n\nEach of authorized messages should contain authorized properties `userUUID` and `sessionUUID`, see details below.\n\nSend system message to all connected users:\n\n```\n{\n    type: \"sys\",\n    sys: {\n        type: \"signIn\",\n        signIn: {\n            uuid: \"123e4567-e89b-12d3-a456-426614174000\",\n            username: \"Username\"\n        }\n    }\n}\n```\n\n##### **Redis Commands**\n\nCheck the `Sign In` section for redis commands, it's the same\n\n#### Logout user\n\nThis is atomic command without message body.\n\nSend a message to websocket:\n\n```\n{\n    type: \"signOut\",\n    userUUID: \"123e4567-e89b-12d3-a456-426614174000\"\n}\n```\n\nReceive a message from websocket:\n\n```\n{\n    type: \"signOut\",\n    signOut: {\n        uuid: \"123e4567-e89b-12d3-a456-426614174000\"\n    }\n}\n```\n\n##### Redis Commands\n\n##### How the data is stored:\n\nOn user exist and signed in, remove sign in data.\n\n- Delete user access key:\n\n  - E.g `DEL access_key:123e4567-e89b-12d3-a456-426614174000`\n\n- Set user offline:\n  - E.g `DEL userStatus:123e4567-e89b-12d3-a456-426614174000`\n\n##### How the data is accessed:\n\n- Check if user exist. Read user index by UUID from redis list:\n\n  - E.g `GET usersUUIDListIndex:123e4567-e89b-12d3-a456-426614174000`\n\n- Read user from redis list by an index:\n\n  - E.g `LINDEX users 4` where 4 is **User Index**\n\n- Read user online status (it called in `UserGet` method):\n  - E.g `GET userStatus:123e4567-e89b-12d3-a456-426614174000`\n\n#### Get users\n\nThis is atomic operation, it not expected `users` block in request.\n\nSend a message to websocket:\n\n```\n{\n    userUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    type: \"users\"\n}\n```\n\nReceive a message from websocket:\n\n```\n{\n    type: \"users\",\n    users: {\n        total: 0,\n        received: 0,\n        users: [\n            {\n                UUID: \"123e4567-e89b-12d3-a456-426614174000\",\n                Username: \"User name\",\n                OnLine: true\n            }\n        ]\n    }\n}\n```\n\n##### Redis Commands\n\n- Read number of users:\n  - E.g `LLEN users`\n- Read all users:\n  - E.g `LRANGE users 0 10` where **10** is number of users.\n\n#### Code Example: Prepare User Data in Redis HashSet\n\n```Go\nfunc (r *Redis) UserCreate(username, password string) (*User, error) {\n    log.Println(\"UserCreate\", fmt.Sprintf(\"[%s|%s]\", username, password))\n\n    if user, err := r.getUserFromListByUsername(username); err == nil {\n        return user, nil\n    }\n\n    user := \u0026User{\n        UUID:     uuid.NewString(),\n        Username: username,\n        Password: password,\n    }\n\n    if err := r.addUser(user); err != nil {\n        return nil, err\n    }\n    return user, nil\n}\n```\n\n### Rooms\n\n![How it works](docs/screenshot001.png)\n\nAfter signIn/signUp client should send `channelJoin` for receive messages from specified channel.\n\nWith empty `channelJoin.recipientUUID` user will join to general channel.\n\nFor private channel set `channelJoin.recipientUUID` with valid `userUUID`.\n\nBefore channel join we should leave other channels if joined, user should have one joined channel.\n\nSend a message to websocket:\n\n```\n{\n    userUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    sessionUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    type: \"channelJoin\",\n    channelJoin: {\n        recipientUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    }\n}\n```\n\nReceive a message from websocket\n\n```\n{\n    type: \"channelJoin\",\n    channelJoin: {\n        recipientUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n        messages: [ // array of messages in channel in desc order\n            {\n                UUID: \"123e4567-e89b-12d3-a456-426614174000\", // message UUID\n                SenderUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n                Sender: {\n                    UUID: \"123e4567-e89b-12d3-a456-426614174000\", //user UUID\n                    Username: \"User name\"\n                },\n                RecipientUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n                Recipient: {\n                    UUID: \"123e4567-e89b-12d3-a456-426614174000\", //user UUID\n                    Username: \"User name\"\n                },\n                Message: \"Text message\",\n                CreatedAt: \"Message send date\"\n            }\n        ],\n        users: [ // array of joined users\n            {\n                UUID: \"123e4567-e89b-12d3-a456-426614174000\", //user UUID\n                Username: \"Username\",\n                OnLine: true\n            }\n        ]\n    }\n}\n```\n\nSend system message to all connected users:\n\n```\n{\n    type: \"sys\",\n    SUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    userUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    user: {\n        UUID: \"123e4567-e89b-12d3-a456-426614174000\",\n        Username: \"User name\",\n        OnLine: true\n    },\n    sys: {\n        type: \"channelJoin\",\n        channelJoin: {\n            recipientUUID: \"123e4567-e89b-12d3-a456-426614174000\"\n        }\n    }\n}\n```\n\n#### Join to channel (room)\n\nAfter signIn/signUp client should send `channelJoin` for receive messages from specified channel.\n\nWith empty `channelJoin.recipientUUID` user will join to general channel.\n\nFor private channel set `channelJoin.recipientUUID` with valid `userUUID`.\n\nBefore channel join we should leave other channels if joined, user should have one joined channel.\n\nSend a message to websocket:\n\n```\n{\n    userUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    sessionUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    type: \"channelJoin\",\n    channelJoin: {\n        recipientUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    }\n}\n```\n\nReceive a message from websocket\n\n```\n{\n    type: \"channelJoin\",\n    channelJoin: {\n        recipientUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n        messages: [ // array of messages in channel in desc order\n            {\n                UUID: \"123e4567-e89b-12d3-a456-426614174000\", // message UUID\n                SenderUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n                Sender: {\n                    UUID: \"123e4567-e89b-12d3-a456-426614174000\", //user UUID\n                    Username: \"User name\"\n                },\n                RecipientUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n                Recipient: {\n                    UUID: \"123e4567-e89b-12d3-a456-426614174000\", //user UUID\n                    Username: \"User name\"\n                },\n                Message: \"Text message\",\n                CreatedAt: \"Message send date\"\n            }\n        ],\n        users: [ // array of joined users\n            {\n                UUID: \"123e4567-e89b-12d3-a456-426614174000\", //user UUID\n                Username: \"Username\",\n                OnLine: true\n            }\n        ]\n    }\n}\n```\n\nSend system message to all connected users:\n\n```\n{\n    type: \"sys\",\n    SUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    userUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    user: {\n        UUID: \"123e4567-e89b-12d3-a456-426614174000\",\n        Username: \"User name\",\n        OnLine: true\n    },\n    sys: {\n        type: \"channelJoin\",\n        channelJoin: {\n            recipientUUID: \"123e4567-e89b-12d3-a456-426614174000\"\n        }\n    }\n}\n```\n\n##### Redis Commands\n\nLeave channel if joined before channel join, see redis flow in `Leave channel` section of this README.\n\n#### How the data is stored:\n\n- Save joined sender to channel `HSET channelUsers:\u003cChannelUUID\u003e \u003cSenderUUID\u003e \u003cJoined date as string\u003e`:\n\n  - E.g `HSET channelUsers:123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426634174000 2021-04-06T13:26:44.415Z`\n\n- Save joined recipient for private channel `HSET channelUsers:\u003cChannelUUID\u003e \u003cRecipientUUID\u003e \u003cJoined date as string\u003e`:\n\n  - E.g `HSET channelUsers:123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426634174000 2021-04-06T13:26:44.415Z`\n\n- Subcribe to channel `SUBSCRIBE \u003cChannelUUID\u003e`:\n  - E.g `SUBSCRIBE 123e4567-e89b-12d3-a456-426614174000`\n\n#### How the data is accessed:\n\n- Read user index by UUID:\n\n  - E.g `GET usersUUIDListIndex:123e4567-e89b-12d3-a456-426614174000`\n\n- Read user list by user index:\n\n  - E.g `LINDEX users 5` where **5** is index\n\n- Read channel UUID `GET channelSenderRecipient:\u003cSenderUUID\u003e:\u003cRecipientUUID\u003e`:\n\n  - E.g `GET channelSenderRecipient:123e4567-e89b-12d3-a456-426614174000:123e4567-e89b-12d3-a456-426614174022`\n\n- Count message in a channel `LLEN channelMessages:\u003cChannelUUID\u003e`:\n\n  - E.g `LLEN channelMessages:5`\n\n- Read last 10 messages from a channel, if number of messages in a channel less than 10:\n\n  - E.g `LRANGE channelMessages:123e4567-e89b-12d3-a456-426614174000 0, 10`\n\n- Read last 10 messages from a channel, if number of messages in a channel more than 10 `\u003cOffset\u003e` is `\u003cNumber of messages\u003e-1`: `LRANGE channelMessages:\u003cChannelUUID\u003e \u003cOffset\u003e, -1`\n\n  - E.g `LRANGE channelMessages:123e4567-e89b-12d3-a456-426614174000 10, -1`\n\n- Read channel users:\n  - E.g `HGETALL channelUsers:123e4567-e89b-12d3-a456-426614174000`\n\n#### Leave channel\n\nSend a message to websocket:\n\n```\n{\n    SUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    type: \"channelLeave\",\n    userUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    channelLeave: {\n        recipientUUID: \"123e4567-e89b-12d3-a456-426614174000\"\n    }\n}\n```\n\nReceive a message from websocket, all connected users will receive it:\n\n```\n{\n    SUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    type: \"channelLeave\",\n    userUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    channelLeave: {\n        recipientUUID: \"123e4567-e89b-12d3-a456-426614174000\"\n    }\n}\n```\n\n##### Redis Commands\n\n###### How the data is stored:\n\nIf channel UUID not found for sender or recipient, we should crate it for both.\n\nGenerate `channelUUID`.\n\n- Set channel UUID for sender `SET channelSenderRecipient:\u003cSenderUUID\u003e:\u003cRecipientUUID\u003e \u003cChannelUUID\u003e`:\n\n  - E.g `SET channelSenderRecipient:123e4567-e89b-12d3-a456-426614174000:123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426614174000`\n\n- Set channel UUID for recipient: `SET channelSenderRecipient:\u003cRecipientUUID\u003e:\u003cSenderUUID\u003e \u003cChannelUUID\u003e`\n  - E.g `SET channelSenderRecipient:123e4567-e89b-12d3-a456-426614174000:123e4567-e89b-12d3-a456-426614174000 123e4567-e89b-12d3-a456-426614174000`\n\n###### How the data is accessed:\n\nGet channel UUID, will return `public` on empty `recipientUUID`.\n\nKey for private channels, first UUID is a sender(userUUID), second is recipient(userUUID):\n\n```\nchannelSenderRecipient:123e4567-e89b-12d3-a456-426614174000:123e4567-e89b-12d3-a456-426614174000\n```\n\nKey for public channels:\n\n```\nchannelSenderRecipient:123e4567-e89b-12d3-a456-426614174000:public\n```\n\n- Read channel UUID:\n  - E.g `GET channelSenderRecipient:123e4567-e89b-12d3-a456-426614174000:public`\n\n#### Code Example: Join Room\n\n```Go\nfunc (r *Redis) ChannelJoin(senderUUID, recipientUUID string) (*ChannelPubSub, string, error) {\n\n\tchannelUUID, err := r.getChannelUUID(senderUUID, recipientUUID)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\n\terr = r.channelJoin(channelUUID, senderUUID, recipientUUID)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tpubSub := r.client.Subscribe(channelUUID)\n\tchannel := r.addChannelPubSub(channelUUID, pubSub)\n\treturn channel, channelUUID, nil\n}\n```\n\n### Messages\n\nSend a message to websocket:\n\n```\n{\n    userUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n    type: \"channelMessage\",\n    channelMessage: {\n        recipientUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n        message: \"Message text\"\n    }\n}\n```\n\nReceive a message from websocket, all joined users received it too:\n\n```\n{\n    type: \"channelMessage\",\n    channelMessage: {\n        SenderUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n        Sender: {\n            UUID: \"123e4567-e89b-12d3-a456-426614174000\",\n            Username: \"User name\",\n            OnLine: true,\n        },\n        RecipientUUID: \"123e4567-e89b-12d3-a456-426614174000\",\n        Recipient: {\n            UUID: \"123e4567-e89b-12d3-a456-426614174000\",\n            Username: \"User name\",\n            OnLine: true,\n        },\n        Message: \"Text message\",\n        CreatedAt: \"0000-00-00T00:00:00.000000000Z\"\n    }\n}\n```\n\n#### **Redis Commands**\n\n#### How the data is stored:\n\n- Publish a message to redis PubSub: `PUBLISH \u003cChannelUUID\u003e \u003cMessage json as string\u003e`\n\n  - E.g `PUBLISH 123e4567-e89b-12d3-a456-426614174000 {\\\"UUID\\\":\\\"123e4567-e89b-12d3-a456-426614174000\\\",\\\"SenderUUID\\\":\\\"123e4567-e89b-12d3-a456-426614174000\\\",\\\"RecipientUUID\\\":\\\"123e4567-e89b-12d3-a456-426614174000\\\",\\\"Message\\\":\\\"Text message\\\",\\\"CreatedAt\\\":\\\"0000-00-00T00:00:00.000000000Z\\\"}`\n\n- Save message in the end of redis list: `RPUSH channelMessages.\u003cChannelUUID\u003e \u003cMessage json as string\u003e`\n  - E.g `RPUSH channelMessages.123e4567-e89b-12d3-a456-426614174000 {\\\"UUID\\\":\\\"123e4567-e89b-12d3-a456-426614174000\\\",\\\"SenderUUID\\\":\\\"123e4567-e89b-12d3-a456-426614174000\\\",\\\"RecipientUUID\\\":\\\"123e4567-e89b-12d3-a456-426614174000\\\",\\\"Message\\\":\\\"Text message\\\",\\\"CreatedAt\\\":\\\"0000-00-00T00:00:00.000000000Z\\\"}`\n\nMessage structure:\n\n```\n{\n    UUID: \"123e4567-e89b-12d3-a456-426614174000\", // message UUID\n    SenderUUID: \"123e4567-e89b-12d3-a456-426614174000\", // user UUID\n    RecipientUUID: \"123e4567-e89b-12d3-a456-426614174000\", // user UUID, or empty for public channel\n    Message: \"Text message\",\n    CreatedAt: \"0000-00-00T00:00:00.000000000Z\"\n}\n```\n\n#### How the data is accessed:\n\nRead channel UUID. See **Redis Commands** in `Channel leave`.\n\n#### Code Example: Send Message\n\n```Go\nfunc channelSessionsSendMessage(skipUserUUID, channelUUID string, write Write, message *Message) {\n\tchannelSessionsSync.RLock()\n\tdefer channelSessionsSync.RUnlock()\n\tfor _, data := range channelSessionsJoins[channelUUID] {\n\t\tif skipUserUUID != \"\" \u0026\u0026 skipUserUUID == data.userUUID {\n\t\t\tcontinue\n\t\t}\n\t\tif err := write(data.conn, ws.OpText, message); err != nil {\n\t\t\tlog.Println(err)\n\t\t}\n\t}\n}\n```\n\n### Session handling\n\nOn first connect to websocket client receive `ready` message:\n\n```\n{\n    type: \"ready\",\n    ready: {\n        sessionUUID: \"123e4567-e89b-12d3-a456-426614174000\"\n    }\n}\n```\n\n#### Redis Commands\n\n##### How the data is stored:\n\n- Key for store user session UUID: `userSession:123e4567-e89b-12d3-a456-426614174000`\n\n  - E.g `SETEX userSession.123e4567-e89b-12d3-a456-426614174000 0000-00-00T00:00:00.000000000Z 3600`\n\n- Remove user session:\n\n  - E.g `DEL userSession.123e4567-e89b-12d3-a456-426614174000`\n\n##### How the data is accessed:\n\n- Read user session created time:\n\n  - E.g `GET userSession.123e4567-e89b-12d3-a456-426614174000`\n\n#### Code example: Managing session\n\n```Go\nfunc (r *Redis) getKeyUserSession(userSessionUUID string) string {\n\treturn fmt.Sprintf(\"%s.%s\", keyUserSession, userSessionUUID)\n}\n\nfunc (r *Redis) AddConnection(userSessionUUID string) error {\n\tkey := r.getKeyUserSession(userSessionUUID)\n\treturn r.client.Set(key, time.Now().String(), time.Hour).Err()\n}\n\nfunc (r *Redis) DelConnection(userSessionUUID string) error {\n\tkey := r.getKeyUserSession(userSessionUUID)\n\treturn r.client.Del(key).Err()\n}\n```\n\n## How to run it locally?\n\nThe client utilizes **Create React App** template, to run it with the development instance of backend, specify the proxy parameter in **package.json**:\n\n```\n  \"proxy\": \"http://localhost:5555\",\n```\n\n#### Run frontend\n\n```sh\ncd client\nyarn install\nyarn start\n```\n\n#### Run backend\n\n#### Set the next environment variables (.env.example):\n\n```\nSERVER_ADDRESS=:5555\nCLIENT_LOCATION=/api/public\nREDIS_HOST=chat-redis\nREDIS_ADDRESS=:6379\nREDIS_PASSWORD=\n```\n\n```sh\ngo run\n```\n\n## Try it out\n\n#### Deploy to Heroku\n\n\u003cp\u003e\n    \u003ca href=\"https://heroku.com/deploy\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://www.herokucdn.com/deploy/button.svg\" alt=\"Deploy to Heorku\" /\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n#### Deploy to Google Cloud\n\n\u003cp\u003e\n    \u003ca href=\"https://deploy.cloud.run\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://deploy.cloud.run/button.svg\" alt=\"Run on Google Cloud\" width=\"150px\"/\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredis-developer%2Fbasic-redis-chat-demo-go","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fredis-developer%2Fbasic-redis-chat-demo-go","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredis-developer%2Fbasic-redis-chat-demo-go/lists"}