{"id":21558738,"url":"https://github.com/medilies/messenger-clone","last_synced_at":"2026-04-09T16:48:50.421Z","repository":{"id":64802283,"uuid":"555033221","full_name":"medilies/messenger-clone","owner":"medilies","description":"Trying Laravel broadcasting (Pusher SDK, Laravel Websockets) and VueJs (Vue3, Laravel Echo, AGgrid, Modular file structure ...)","archived":false,"fork":false,"pushed_at":"2023-08-10T16:15:38.000Z","size":490,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-18T03:43:57.579Z","etag":null,"topics":["chat","composition-api","laravel","laravel-echo","laravel-self-testing","pusher","self-hosted","vue","websocket"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/medilies.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2022-10-20T20:43:56.000Z","updated_at":"2023-08-08T13:32:10.000Z","dependencies_parsed_at":"2024-11-24T10:03:14.607Z","dependency_job_id":null,"html_url":"https://github.com/medilies/messenger-clone","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/medilies/messenger-clone","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/medilies%2Fmessenger-clone","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/medilies%2Fmessenger-clone/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/medilies%2Fmessenger-clone/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/medilies%2Fmessenger-clone/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/medilies","download_url":"https://codeload.github.com/medilies/messenger-clone/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/medilies%2Fmessenger-clone/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281731419,"owners_count":26551804,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-30T02:00:06.501Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chat","composition-api","laravel","laravel-echo","laravel-self-testing","pusher","self-hosted","vue","websocket"],"created_at":"2024-11-24T08:15:41.255Z","updated_at":"2025-10-30T02:08:59.396Z","avatar_url":"https://github.com/medilies.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Messenger clone\n\n![image](https://github.com/medilies/messenger-clone/assets/35309918/0e492231-064d-4c9d-80bf-ee20958701f5)\n\n## Features\n\n### System features\n\n- [x] Listing contacts.\n- [x] Searching for a contact.\n- [x] Direct messages.\n- [ ] Group messages.\n- [ ] Replying to messages.\n- [ ] Replying to messages in threads.\n- [ ] Sending media.\n- [ ]  Sending vocal messages.\n- [ ] Indicating the connection status of a contact.\n- [ ] Indicating when a contact was last seen if not connected.\n- [ ] Signaling if user is typing.\n- [ ] Reporting the message state sending - sent - delivered - seen.\n- [ ] Searching for a message in a conversation.\n- [ ] Editing a message, and wether to keep the history of changes or not.\n- [ ] Deleting a message.\n\n### Front end only features\n\n- [x] Auto-scrolling down when entering a conversation.\n- [ ] Showing a notification when a user isn't in the conversation.\n- [ ] Showing a count of not seen messages.\n- [x] Getting stored messages from the backend.\n- [ ] Caching messages.\n- [x] Show a separator when there is a considerable time interval between messages.\n\n## Architecture\n\nLike most of cloud apps. This app will need a:\n\n-   **Database** for storing users data and messages.\n-   Backend services for handling API calls, **authentication**, **session management**, **authorization** and **file management**.\n\nIn a classical way, **Laravel** with **MySQL** can handle all the above. But now the special part is enabling realtime messaging, if you are a lazy engineer and do not care about your client/employer bills its ok to pick **Firebase** to handle the messaging part. But if you have enough experience you already know that realtime data needs to be mobilized using realtime protocols such as `websocket`, and you are asking your self which websocket server implementation to couple with the chosen backend.\n\n**Laravel** comes bundled with tools that support, **events**, **notifications**, **broadcasting**, and **queues**. But doesn't support any realtime protocol out of the box but instead of letting us stray alone looking for websocket servers to couple with **Laravel**. It eases and recommends the integration of [Pusher](https://pusher.com/) or [Ably](https://ably.com/). Even better, the community maintains packages that can be used as alternative, compatible, self hosted APIs for **Pusher**.\n\n1. [Laravel Websockets](https://beyondco.de/docs/laravel-websockets/getting-started/introduction) is a PHP package built on top of [Ratchet](http://socketo.me/), [read more...](https://freek.dev/1228-introducing-laravel-websockets-an-easy-to-use-websocket-server-implemented-in-php).\n2. [Soketi](https://soketi.app/) is a NodeJS package built on top of µWebSockets.js.\n\nMy preference is `laravel-websockets` for the simple fact of being able to maintain it in one repository with the **Laravel** backend.\n\nFor the client side `pusher-js` library is the go to, and `laravel-echo` makes it even easier to use publish and subscribe from the browsers.\n\nTo be more exhaustive, this demo will be using `sanctum` for authenticating API calls. And `axios` as a front end HTTP client.\n\n### Scaling\n\n\u003chttps://beyondco.de/docs/laravel-websockets/faq/scaling\u003e\n\n## Database\n\n### Direct messaging only\n\nThis a basic approach. To identify conversations of user ID `1` with user ID `2` i\n\n```sql\nselect *\nfrom `direct_messages`\nwhere\n    (`user_id` = 1 and `target_user_id` = 2) or\n    (`user_id` = 2 and `target_user_id` = 1)\n```\n\n[![db](./direct_messaging_db.png)](https://dbdiagram.io/d/639cc38899cb1f3b55a1eb48)\n\n### Group messaging support\n\n[![db](./group_messaging_db.png)](https://dbdiagram.io/d/5f2bff1608c7880b65c55446)\n\n## Scaffolding\n\n```shell\nlaravel new --git messenger-clone\n```\n\n```shell\ncomposer require laravel/sanctum\n```\n\n```shell\ncomposer require pusher/pusher-php-server:7.0.2\n```\n\n\u003e Version compatibilty issue \u003chttps://github.com/beyondcode/laravel-websockets/issues/1041\u003e\n\n```shell\ncomposer require beyondcode/laravel-websockets:1.13.1\nphp artisan vendor:publish --provider=\"BeyondCode\\LaravelWebSockets\\WebSocketsServiceProvider\" --tag=\"config\"\n```\n\n```shell\nnpm install --save-dev laravel-echo pusher-js\n```\n\nSetup a database and the `env` configs.\n\n## Config\n\nThe following config makes it easy to switch between **Pusher** and **Laravel Websockets**.\n\n```env\n# .env\nAPP_URL=http://127.0.0.1:8000\n\nBROADCAST_DRIVER=pusher\n\nPUSHER_APP_ID=\nPUSHER_APP_KEY=\nPUSHER_APP_SECRET=\nPUSHER_HOST=#api-CLUSTER.pusher.com or 127.0.0.1\nPUSHER_PORT=443# or any other port (recommend 6001 for Laravel Websockets)\nPUSHER_SCHEME=https# or http\nPUSHER_APP_CLUSTER=\n\nLARAVEL_WEBSOCKETS_ENABLE_CLIENT_MESSAGES=true\n\nVITE_PUSHER_APP_KEY=\"${PUSHER_APP_KEY}\"\nVITE_PUSHER_HOST=\"${PUSHER_HOST}\"\nVITE_PUSHER_PORT=\"${PUSHER_PORT}\"\nVITE_PUSHER_SCHEME=\"${PUSHER_SCHEME}\"\nVITE_PUSHER_APP_CLUSTER=\"${PUSHER_APP_CLUSTER}\"\n```\n\nAdd Sanctum's middleware `\\Laravel\\Sanctum\\Http\\Middleware\\EnsureFrontendRequestsAreStateful::class` to api middleware group within `app/Http/Kernel.php`.\n\nRegister `App\\Providers\\BroadcastServiceProvider::class` in `config/app.php`.\n\n```php\n// config\\broadcasting.php\nreturn [\n    // ...\n    'connections' =\u003e [\n\n        'pusher' =\u003e [\n            'driver' =\u003e 'pusher',\n            'key' =\u003e env('PUSHER_APP_KEY'),\n            'secret' =\u003e env('PUSHER_APP_SECRET'),\n            'app_id' =\u003e env('PUSHER_APP_ID'),\n            'options' =\u003e [\n                'host' =\u003e env('PUSHER_HOST', 'api-'.env('PUSHER_APP_CLUSTER', 'eu').'.pusher.com') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'eu').'.pusher.com',\n                'port' =\u003e env('PUSHER_PORT', 443),\n                'scheme' =\u003e env('PUSHER_SCHEME', 'https'),\n                'encrypted' =\u003e true,\n                'useTLS' =\u003e env('PUSHER_SCHEME', 'https') === 'https',\n                // Added\n                'cluster' =\u003e env('PUSHER_APP_CLUSTER', 'eu'),\n                'base_path' =\u003e env('PUSHER_BASE_PATH', '/apps/'.env('PUSHER_APP_ID')),\n            ],\n            'client_options' =\u003e [\n                // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html\n            ],\n        ],\n        // ...\n    ]\n    // ...\n];\n```\n\n```php\n// config\\websockets.php\nreturn [\n    'dashboard' =\u003e [\n        'port' =\u003e env('PUSHER_PORT', 6001),\n    ],\n\n    'apps' =\u003e [\n        [\n            'name' =\u003e env('APP_NAME'),\n            'id' =\u003e env('PUSHER_APP_ID'),\n            'key' =\u003e env('PUSHER_APP_KEY'),\n            'secret' =\u003e env('PUSHER_APP_SECRET'),\n            'capacity' =\u003e null,\n            'enable_client_messages' =\u003e env('LARAVEL_WEBSOCKETS_ENABLE_CLIENT_MESSAGES', false),\n            'enable_statistics' =\u003e true,\n        ],\n    ],\n    // ...\n];\n```\n\n```js\n// resources\\js\\bootstrap.js\nimport _ from \"lodash\";\nwindow._ = _;\n\nimport axios from \"axios\";\nwindow.axios = axios;\n\nimport Echo from \"laravel-echo\";\nimport Pusher from \"pusher-js\";\n\nwindow.axios.defaults.headers.common[\"X-Requested-With\"] = \"XMLHttpRequest\";\n\naxios.defaults.baseURL = import.meta.env.APP_URL;\n\nwindow.axios.defaults.withCredentials = true;\n\nwindow.Pusher = Pusher;\n\nwindow.Echo = new Echo({\n    broadcaster: import.meta.env.VITE_BROADCAST_DRIVER,\n    key: import.meta.env.VITE_PUSHER_APP_KEY,\n    wsHost:\n        import.meta.env.VITE_PUSHER_HOST ??\n        `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,\n    wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,\n    wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,\n    forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? \"https\") === \"https\",\n    enabledTransports: [\"ws\", \"wss\"],\n    cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, // Options mentioned by pusher\n    disableStats: true, // Options mentioned by beyond code\n    // authEndpoint: \"/custom/endpoint/auth\",\n    // namespace: 'App.Other.Namespace'\n    authorizer: (channel, options) =\u003e {\n        return {\n            authorize: (socketId, callback) =\u003e {\n                axios\n                    .post(\n                        \"/broadcasting/auth\",\n                        {\n                            socket_id: socketId,\n                            channel_name: channel.name,\n                        },\n                        {\n                            headers: {\n                                Accept: \"application/json\",\n                                Authorization:\n                                    \"Bearer \" +\n                                    localStorage.getItem(\"bearerToken\"),\n                            },\n                        }\n                    )\n                    .then((response) =\u003e {\n                        callback(false, response.data);\n                    })\n                    .catch((error) =\u003e {\n                        callback(true, error);\n                    });\n            },\n        };\n    },\n});\n```\n\n\u003e It is essential to provide an `authorizer` callback to the config to add the `Authorization` HTTP headers (`Bearer`, cookies, ...).\n\n```php\n// routes/api.php\nuse Illuminate\\Support\\Facades\\Route;\n\nRoute::post('/sanctum/token', LoginController::class);\n\nRoute::middleware('auth:sanctum')-\u003egroup(function () {\n    // ...\n});\n```\n\n```php\n// app\\Providers\\BroadcastServiceProvider.php\n    public function boot()\n    {\n        Broadcast::routes(['middleware' =\u003e ['auth:sanctum']]);\n\n        require base_path('routes/channels.php');\n    }\n```\n\n```php\n// routes/web.php\nuse Illuminate\\Support\\Facades\\Route;\n\nRoute::fallback(fn() =\u003e view('app'));\n```\n\n### SSL\n\n\u003chttps://beyondco.de/docs/laravel-websockets/basic-usage/ssl\u003e\n\n## Laravel broadcasting\n\n### Channels\n\n-   Events are broadcast over \"channels\", which may be specified as **public** or **private**.\n-   Any visitor may subscribe to a **public channel** without any **authentication** or **authorization**.\n-   In order to subscribe to a **private channel**, a user must be **authenticated** and **authorized** to listen on that channel.\n\nInstances of `Channel` represent **public channels** that any user may subscribe to, while `PrivateChannels` and `PresenceChannels` represent **private channels** that require **authorization**.\n\n### Events\n\n#### ShouldBroadcast\n\nBroadcastable events must implement the `Illuminate\\Contracts\\Broadcasting\\ShouldBroadcast` interface, This will instruct Laravel to broadcast the event when it is fired.\n\n#### broadcastOn\n\nThe `ShouldBroadcast` interface requires the event to define a `broadcastOn` method. The method return a channel or array of channels that the event should broadcast on.\n\n```php\nuse Illuminate\\Broadcasting\\Channel;\nuse Illuminate\\Broadcasting\\PrivateChannel;\nuse Illuminate\\Broadcasting\\PresenceChannel;\n\npublic function broadcastOn(): Channel|array\n{\n    return [\n        new Channel(\"name.{$this-\u003eprop}\"),\n        new PrivateChannel(\"name.{$this-\u003eprop}\"),\n        new PresenceChannel(\"name.{$this-\u003eprop}\"),\n    ];\n}\n```\n\n#### broadcastWith\n\nWhen an event is broadcast, all of its `public` properties are automatically serialized and broadcast as the event's payload.\n\n```php\npublic function broadcastWith()\n{\n    // Get the data to broadcast.\n    return [\n        'id' =\u003e $this-\u003emessage-\u003eid,\n        'content' =\u003e $this-\u003emessage-\u003econtent,\n        'created_at' =\u003e $this-\u003emessage-\u003ecreated_at,\n        'user_id' =\u003e $this-\u003emessage-\u003euser_id,\n        'target_user_id' =\u003e $this-\u003emessage-\u003etarget_user_id,\n        'user' =\u003e [\n            'id' =\u003e $this-\u003emessage-\u003euser-\u003eid,\n            'name' =\u003e $this-\u003emessage-\u003euser-\u003ename,\n        ],\n    ];\n}\n```\n\n#### broadcastAs\n\n```php\npublic function broadcastAs()\n{\n    // By default: App\\Events\\EventFullyQualifiedClassName\n    return 'CustomEventName';\n}\n```\n\n#### broadcastWhen\n\n```php\npublic function broadcastWhen()\n{\n    // Determine if this event should broadcast.\n    return true;\n}\n```\n\n#### Broadcast queue\n\nBy default, each broadcast event is placed on the default queue for the default queue connection specified in `config\\queue.php` file [see ...](https://laravel.com/docs/9.x/broadcasting#broadcast-queue)\n\n#### afterCommit\n\nDispatch event after all open database transactions have been committed.\n\n```php\npublic $afterCommit = true;\n```\n\n### Authorization\n\nWhen using `Echo`, the HTTP request to **authorize subscriptions** to **private channels** will be made automatically; however, you do need to define the proper routes to respond to these requests.\n\nIn `app\\Providers\\BroadcastServiceProvider.php`, the `Broadcast::routes` method registers the `/broadcasting/auth` route to handle authorization requests. The method will automatically place its routes within the `web` **middleware group**; however, you may pass an array of route attributes to the method if you would like to customize the assigned attributes.\n\nRegister channel authorization callbacks in `routes/channels.php`.\n\n```php\nuse App\\Models\\User;\nuse Illuminate\\Support\\Facades\\Broadcast;\n\n// Private\nBroadcast::channel(\n    'name.{param}',\n    fn (User $user, bool|int|string|float $param): bool =\u003e auth()-\u003echeck(), // true = authorized\n    ['guards' =\u003e ['web', 'admin']] // Optional custom guards\n);\n\n// Presence\nBroadcast::channel(\n    'chat.{roomId}',\n    function ($user, $roomId) {\n        if ($user-\u003ecanJoinRoom($roomId)) {\n            // Return user data = authorized\n            return ['id' =\u003e $user-\u003eid, 'name' =\u003e $user-\u003ename];\n        }\n    }\n);\n```\n\nAll authorization callbacks receive the currently authenticated user as their first argument and any additional wildcard parameters as their subsequent arguments.\n\nYou can move the authorization logic from the callback to a class.\n\n```shell\nphp artisan make:channel ChannelNameChannel\n```\n\n```php\nuse App\\Broadcasting\\OrderChannel;\nuse App\\Channels\\ChannelNameChannel;\n\nBroadcast::channel('name.{param}', ChannelNameChannel::class);\n```\n\n### Model broadcasting\n\nThe `$event` argument can be `created`, `updated`, `deleted`, `trashed`, or `restored`.\n\n```php\npublic function broadcastOn($event)\n{\n    return match ($event) {\n        'created' =\u003e [new PrivateChannel(\"direct-messages.{$this-\u003euser-\u003eid}\")],\n        default =\u003e [$this, new PrivateChannel($this-\u003euser)],\n    };\n}\n```\n\n```php\npublic function broadcastAs($event)\n{\n    return match ($event) {\n        'created' =\u003e 'new.message',\n        default =\u003e null, // default convention: DirectMessageUpdated, DirectMessageDeleted ...\n    };\n}\n```\n\n```php\npublic function broadcastWith($event)\n{\n    return match ($event) {\n        'created' =\u003e [/* Payload */],\n        default =\u003e $this-\u003etoArray(),\n    };\n}\n```\n\n```php\nprotected function newBroadcastableEvent($event)\n{\n    return (new BroadcastableModelEventOccurred(\n        $this, $event\n    ))-\u003edontBroadcastToCurrentUser();\n}\n```\n\n```php\n// Model broadcastChannel conventional name\n\nApp\\Models\\User::find(1)-\u003ebroadcastChannel();\n// \"App.Models.User.1\"\n\nApp\\Models\\DirectMessage::find(2)-\u003ebroadcastChannel();\n// \"App.Models.DirectMessage.2\"\n```\n\n### Notifications\n\n### Dispatching\n\n```php\nuse App\\Events\\SomeEvent;\n\nSomeEvent::dispatch($data);\n\nbroadcast(new SomeEvent($data))-\u003etoOthers();\n```\n\nAn event must use the `Illuminate\\Broadcasting\\InteractsWithSockets` trait in order to call the `toOthers` method.\n\nWhen you initialize a `Echo`, a `socket ID` is assigned to the connection. If you are using a **global Axios instance** to make HTTP requests from your JavaScript application, `socket ID` will automatically be attached to every outgoing HTTP request as a `X-Socket-ID` header. Then, when you call `toOthers`, **Laravel** will extract `socket ID` from the header and instruct the **broadcaster** to not broadcast to any connections with that `socket ID`.\n\n### Echo\n\n```js\n// Subscribe and register event handlers\n\n// Public\nEcho.channel(`public.${param}`)\n    .listen(\"SomeEvent\", (e) =\u003e {\n        console.log(e);\n    })\n    .listen(/* ... */)\n    .listen(/* ... */)\n    .listen(/* ... */);\n\n// Private\nEcho.private(`private.${param}`)\n    .listen(\"SomeEvent\", (e) =\u003e {\n        console.log(e);\n    })\n    .listen(/* ... */)\n    .listen(/* ... */)\n    .listen(/* ... */);\n```\n\n```js\n// Unconventional base event namespace\nEcho.channel(\"someChannel\").listen(\n    \".NotAppEventsNamespace\\\\Event\\\\Class\", // Start with '.'\n    (e) =\u003e {\n        /* ... */\n    }\n);\n```\n\n```js\n// Stop Listening For Events\nEcho.channel(`public.${param}`).stopListening(\"SomeEvent\");\nEcho.private(`private.${param}`).stopListening(\"SomeEvent\");\n```\n\n```js\n// Presence channel\nEcho.join(`chat.${roomId}`)\n    .here((users) =\u003e {\n        // executed immediately once the channel is joined successfully\n    })\n    .joining((user) =\u003e {\n        // executed when a new user joins\n        console.log(user.name);\n    })\n    .leaving((user) =\u003e {\n        // executed when a user leaves the channel\n        console.log(user.name);\n    })\n    .error((error) =\u003e {\n        // executed when the authentication endpoint returns a HTTP status code other than 200\n        // or if there is a problem parsing the returned JSON\n        console.error(error);\n    })\n    .listen(/* ... */)\n    .listen(/* ... */)\n    .listen(/* ... */);\n```\n\n```js\nEcho.private(`App.Models.User.${userId}`).listen(\".PostUpdated\", (e) =\u003e {\n    console.log(e.model);\n});\n```\n\n```js\n// client events\n\n// Send\nEcho.private(`chat.${roomId}`).whisper(\"typing\", {\n    name: this.user.name,\n});\n\n// Listen\nEcho.private(`chat.${roomId}`).listenForWhisper(\"typing\", (e) =\u003e {\n    console.log(e.name);\n});\n```\n\n```js\n// Notifications\nEcho.private(`App.Models.User.${userId}`).notification((notification) =\u003e {\n    console.log(notification.type);\n});\n```\n\n#### Socket ID\n\nIf you are not using a **global Axios instance**, you will need to manually configure your JavaScript application to send the `X-Socket-ID` header with all outgoing requests. You may retrieve the `socket ID` using:\n\n```js\nlet socketId = Echo.socketId();\n```\n\n## References\n\n### Laravel docs refs\n\n-   \u003chttps://laravel.com/docs/9.x/queues\u003e\n-   \u003chttps://laravel.com/docs/9.x/events\u003e\n-   \u003chttps://laravel.com/docs/9.x/notifications\u003e\n-   \u003chttps://laravel.com/docs/9.x/broadcasting\u003e\n-   \u003chttps://laravel.com/docs/9.x/sanctum\u003e\n\n### Database Design refs\n\n-   \u003chttps://www.youtube.com/watch?v=xL_tYrEcP9M\u003e\n-   \u003chttps://towardsdatascience.com/ace-the-system-interview-design-a-chat-application-3f34fd5b85d0\u003e\n-   \u003chttps://stackoverflow.com/questions/46484989/database-schema-for-chat-private-and-group\u003e\n-   \u003chttps://stackoverflow.com/questions/39810106/storing-messages-of-different-chats-in-a-single-database-table\u003e\n\n### Architecture refs\n\n-   \u003chttps://devtechnosys.com/messaging-app-development.php\u003e\n-   \u003chttps://www.youtube.com/watch?v=vvhC64hQZMk\u003e\n\n### Laravel Webcockets refs\n\n-   \u003chttps://beyondco.de/docs/laravel-websockets/getting-started/introduction\u003e\n-   \u003chttps://www.youtube.com/watch?v=xKru8j9LXjA\u003e\n-   \u003chttps://www.youtube.com/watch?v=pd8LmGxt5Og\u0026list=PLSfH3ojgWsQosqpQUc28yP9jJZXrEylJY\u0026index=47\u003e \\[45-52\\]\n-   \u003chttps://www.youtube.com/watch?v=UwB5z6u7vt8\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmedilies%2Fmessenger-clone","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmedilies%2Fmessenger-clone","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmedilies%2Fmessenger-clone/lists"}