├── README.md
├── backend
├── go.mod
├── go.sum
├── main.go
└── pkg
│ └── websocket
│ ├── client.go
│ ├── pool.go
│ └── websocket.go
└── frontend
├── .gitignore
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.css
├── App.js
├── App.test.js
├── api
│ └── index.js
├── components
│ ├── ChatHistory
│ │ ├── ChatHistory.jsx
│ │ ├── ChatHistory.scss
│ │ └── index.js
│ ├── ChatInput
│ │ ├── ChatInput.jsx
│ │ ├── ChatInput.scss
│ │ └── index.js
│ ├── Header
│ │ ├── Header.jsx
│ │ ├── Header.scss
│ │ └── index.js
│ └── Message
│ │ ├── Message.jsx
│ │ ├── Message.scss
│ │ └── index.js
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
└── setupTests.js
└── yarn.lock
/README.md:
--------------------------------------------------------------------------------
1 | # go-fullstack-chat
2 | This is go chat application with react and go
3 |
--------------------------------------------------------------------------------
/backend/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/akhil/golang-chat
2 |
3 | go 1.16
4 |
5 | require github.com/gorilla/websocket v1.5.0
6 |
--------------------------------------------------------------------------------
/backend/go.sum:
--------------------------------------------------------------------------------
1 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
2 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
3 |
--------------------------------------------------------------------------------
/backend/main.go:
--------------------------------------------------------------------------------
1 | //file name: main.go
2 | package main
3 |
4 | import (
5 | "fmt"
6 | "net/http"
7 |
8 | "github.com/akhil/golang-chat/pkg/websocket"
9 | )
10 |
11 | func serveWS(pool *websocket.Pool, w http.ResponseWriter, r *http.Request) {
12 | fmt.Println("websocket endpoint reached")
13 |
14 | conn, err := websocket.Upgrade(w, r)
15 |
16 | if err != nil {
17 | fmt.Fprintf(w, "%+v\n", err)
18 | }
19 | client := &websocket.Client{
20 | Conn: conn,
21 | Pool: pool,
22 | }
23 | pool.Register <- client
24 | client.Read()
25 | }
26 |
27 | func setupRoutes() {
28 | pool := websocket.NewPool()
29 | go pool.Start()
30 |
31 | http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
32 | serveWS(pool, w, r)
33 | })
34 | }
35 |
36 | func main() {
37 | fmt.Println("Akhil's full stack chat project")
38 | setupRoutes()
39 | http.ListenAndServe(":9000", nil)
40 | }
41 |
--------------------------------------------------------------------------------
/backend/pkg/websocket/client.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "sync"
7 |
8 | "github.com/gorilla/websocket"
9 | )
10 |
11 | type Client struct {
12 | ID string
13 | Conn *websocket.Conn
14 | Pool *Pool
15 | mu sync.Mutex
16 | }
17 |
18 | type Message struct {
19 | Type int `json:"type"`
20 | Body string `json:"body"`
21 | }
22 |
23 | func (c *Client) Read() {
24 | defer func() {
25 | c.Pool.Unregister <- c
26 | c.Conn.Close()
27 | }()
28 |
29 | for {
30 | messageType, p, err := c.Conn.ReadMessage()
31 | if err != nil {
32 | log.Println(err)
33 | return
34 | }
35 |
36 | message := Message{Type: messageType, Body: string(p)}
37 | c.Pool.Broadcast <- message
38 | fmt.Printf("Message Received: %+v\n", message)
39 |
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/backend/pkg/websocket/pool.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import "fmt"
4 |
5 | type Pool struct {
6 | Register chan *Client
7 | Unregister chan *Client
8 | Clients map[*Client]bool
9 | Broadcast chan Message
10 | }
11 |
12 | func NewPool() *Pool {
13 | return &Pool{
14 | Register: make(chan *Client),
15 | Unregister: make(chan *Client),
16 | Clients: make(map[*Client]bool),
17 | Broadcast: make(chan Message),
18 | }
19 | }
20 |
21 | func (pool *Pool) Start() {
22 | for {
23 | select {
24 | case client := <-pool.Register:
25 | pool.Clients[client] = true
26 | fmt.Println("Size of Connection Pool: ", len(pool.Clients))
27 | for client, _ := range pool.Clients {
28 | fmt.Println(client)
29 | client.Conn.WriteJSON(Message{Type: 1, Body: "New User Joined..."})
30 | }
31 | break
32 | case client := <-pool.Unregister:
33 | delete(pool.Clients, client)
34 | fmt.Println("Size of Connection Pool: ", len(pool.Clients))
35 | for client, _ := range pool.Clients {
36 | client.Conn.WriteJSON(Message{Type: 1, Body: "User Disconnected..."})
37 | }
38 | break
39 | case message := <-pool.Broadcast:
40 | fmt.Println("Sending message to all clients in Pool")
41 | for client, _ := range pool.Clients {
42 | if err := client.Conn.WriteJSON(message); err != nil {
43 | fmt.Println(err)
44 | return
45 | }
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/backend/pkg/websocket/websocket.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "log"
5 | "net/http"
6 |
7 | "github.com/gorilla/websocket"
8 | )
9 |
10 | var upgrader = websocket.Upgrader{
11 | ReadBufferSize: 1024,
12 | WriteBufferSize: 1024,
13 | }
14 |
15 | func Upgrade(w http.ResponseWriter, r *http.Request) (*websocket.Conn, error) {
16 | upgrader.CheckOrigin = func(r *http.Request) bool { return true }
17 | conn, err := upgrader.Upgrade(w, r, nil)
18 | if err != nil {
19 | log.Println(err)
20 | return nil, err
21 | }
22 |
23 | return conn, nil
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chat-frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "react": "^17.0.2",
10 | "react-dom": "^17.0.2",
11 | "react-scripts": "4.0.3",
12 | "web-vitals": "^1.0.1"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test",
18 | "eject": "react-scripts eject"
19 | },
20 | "eslintConfig": {
21 | "extends": [
22 | "react-app",
23 | "react-app/jest"
24 | ]
25 | },
26 | "browserslist": {
27 | "production": [
28 | ">0.2%",
29 | "not dead",
30 | "not op_mini all"
31 | ],
32 | "development": [
33 | "last 1 chrome version",
34 | "last 1 firefox version",
35 | "last 1 safari version"
36 | ]
37 | },
38 | "devDependencies": {
39 | "sass": "^1.35.2"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/top-comengineer/go-fullstack-chat/0082442b74a07b7eddc41e0494988290d717da52/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/top-comengineer/go-fullstack-chat/0082442b74a07b7eddc41e0494988290d717da52/frontend/public/logo192.png
--------------------------------------------------------------------------------
/frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/top-comengineer/go-fullstack-chat/0082442b74a07b7eddc41e0494988290d717da52/frontend/public/logo512.png
--------------------------------------------------------------------------------
/frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | body{
2 | background-color: #595260;
3 | margin:0;
4 | padding:0;
5 | }
--------------------------------------------------------------------------------
/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | //file name: App.js
2 | import React, { Component } from "react";
3 | import Header from "./components/Header/Header";
4 | import ChatHistory from "./components/ChatHistory/ChatHistory";
5 | import ChatInput from "./components/ChatInput/ChatInput";
6 | import "./App.css";
7 | import { connect, sendMsg } from "./api";
8 |
9 | class App extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | chatHistory: [],
14 | };
15 | }
16 |
17 | componentDidMount() {
18 | connect((msg) => {
19 | console.log("New Message");
20 | this.setState((prevState) => ({
21 | chatHistory: [...prevState.chatHistory, msg],
22 | }));
23 | console.log(this.state);
24 | });
25 | }
26 |
27 | send(event) {
28 | if (event.keyCode === 13) {
29 | sendMsg(event.target.value);
30 | event.target.value = "";
31 | }
32 | }
33 |
34 | render() {
35 | return (
36 |
37 |
38 |
39 |
40 |
41 | );
42 | }
43 | }
44 |
45 | export default App;
46 |
--------------------------------------------------------------------------------
/frontend/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/frontend/src/api/index.js:
--------------------------------------------------------------------------------
1 | //file name: api/index.js
2 | //description: total api integration.
3 | var socket = new WebSocket("ws://localhost:9000/ws");
4 |
5 | let connect = (cb) => {
6 | console.log("connecting");
7 |
8 | socket.onopen = () => {
9 | console.log("Successfully Connected");
10 | };
11 |
12 | socket.onmessage = (msg) => {
13 | console.log("Message from WebSocket: ", msg);
14 | cb(msg);
15 | };
16 |
17 | socket.onclose = (event) => {
18 | console.log("Socket Closed Connection: ", event);
19 | };
20 |
21 | socket.onerror = (error) => {
22 | console.log("Socket Error: ", error);
23 | };
24 | };
25 |
26 | let sendMsg = (msg) => {
27 | console.log("sending msg: ", msg);
28 | socket.send(msg);
29 | };
30 |
31 | export { connect, sendMsg };
32 |
--------------------------------------------------------------------------------
/frontend/src/components/ChatHistory/ChatHistory.jsx:
--------------------------------------------------------------------------------
1 | //file name: ChatHistory.jsx
2 | import React, { Component } from 'react';
3 | import './ChatHistory.scss';
4 | import Message from '../Message/Message';
5 |
6 | class ChatHistory extends Component {
7 | render() {
8 | console.log(this.props.chatHistory);
9 | const messages = this.props.chatHistory.map(msg => );
10 |
11 | return (
12 |
13 |
Chat History
14 | {messages}
15 |
16 | );
17 | };
18 |
19 | }
20 |
21 | export default ChatHistory;
22 |
--------------------------------------------------------------------------------
/frontend/src/components/ChatHistory/ChatHistory.scss:
--------------------------------------------------------------------------------
1 | .ChatHistory {
2 | background-color: #20244e;
3 | margin: 0;
4 | padding: 20px;
5 | h2 {
6 | margin: 0;
7 | padding: 0;
8 | color: white;
9 | }
10 | }
--------------------------------------------------------------------------------
/frontend/src/components/ChatHistory/index.js:
--------------------------------------------------------------------------------
1 | import ChatHistory from './ChatHistory.jsx'
2 |
3 | export default ChatHistory;
--------------------------------------------------------------------------------
/frontend/src/components/ChatInput/ChatInput.jsx:
--------------------------------------------------------------------------------
1 | //file name: ChatInput.jsx
2 | //date: 3/25/2023
3 | import React, { Component } from 'react';
4 | import './ChatInput.scss';
5 |
6 | class ChatInput extends Component {
7 |
8 | render() {
9 | return (
10 |
11 |
12 |
13 | );
14 | };
15 |
16 | }
17 |
18 | export default ChatInput;
19 |
--------------------------------------------------------------------------------
/frontend/src/components/ChatInput/ChatInput.scss:
--------------------------------------------------------------------------------
1 | //file name: ChatInput.scss
2 | .ChatInput {
3 | width: 95%;
4 | display: block;
5 | margin: auto;
6 |
7 | input {
8 | padding: 10px;
9 | margin: 0;
10 | background-color:#F4CCA4;
11 | font-size: 16px;
12 | border: none;
13 | border-radius: 5px;
14 | border: 1px solid rgba(0,0,0,0.1);
15 | width: 98%;
16 | box-shadow: 0 5px 15px -5px rgba(0,0,0,.1);
17 | }
18 | }
--------------------------------------------------------------------------------
/frontend/src/components/ChatInput/index.js:
--------------------------------------------------------------------------------
1 | import ChatInput from './ChatInput.jsx'
2 |
3 | export default ChatInput;
--------------------------------------------------------------------------------
/frontend/src/components/Header/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Header.scss';
3 |
4 | const Header = () => (
5 |
6 |
Go + React Socket Chat
7 |
8 | );
9 |
10 | export default Header;
11 |
--------------------------------------------------------------------------------
/frontend/src/components/Header/Header.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | background-color: #2940D3;
3 | color: #FFEDA3;
4 | max-width: 100%;
5 | padding: 50px;
6 | h2 {
7 | margin: 0;
8 | padding: 0;
9 | font-size: 4em;
10 | text-align : center;
11 | }
12 |
13 | }
--------------------------------------------------------------------------------
/frontend/src/components/Header/index.js:
--------------------------------------------------------------------------------
1 | import Header from './header.jsx'
2 |
3 | export default Header;
--------------------------------------------------------------------------------
/frontend/src/components/Message/Message.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './Message.scss';
3 |
4 | class Message extends Component {
5 | constructor(props) {
6 | super(props);
7 | let temp = JSON.parse(this.props.message);
8 | this.state = {
9 | message: temp
10 | }
11 | }
12 |
13 | render() {
14 | return (
15 |
16 | {this.state.message.body}
17 |
18 | );
19 | };
20 |
21 | }
22 |
23 | export default Message;
24 |
--------------------------------------------------------------------------------
/frontend/src/components/Message/Message.scss:
--------------------------------------------------------------------------------
1 | .Message {
2 | display: block;
3 | background-color: #B2B1B9;
4 | margin: 10px auto;
5 | box-shadow: 0 5px 15px -5px rgba(0,0,0,.2);
6 | padding: 10px 20px;
7 | border-radius: 5px;
8 | clear:both;
9 |
10 | &.me {
11 | color: rgb(31, 28, 70);
12 | float: right;
13 | }
14 | }
--------------------------------------------------------------------------------
/frontend/src/components/Message/index.js:
--------------------------------------------------------------------------------
1 | import Message from './Message.jsx'
2 |
3 | export default Message;
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/frontend/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/frontend/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------