├── 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 | --------------------------------------------------------------------------------