Which is better, JavaScript or Go? And how?

Building a chat widget with Go and JavaScript

Building a chat widget with Go and JavaScript

In this tutorial, we will see how to build a realtime chat widget with Go, Pusher, and JavaScript.

Introduction

The process of building products for an online demographic should be thorough and follow modern-day trends. One of such trend is making it possible for the customers and support agents to have realtime discussions over some form of two-way message channel. This would ensure that customers do not click away in confusion and switch to competitors in times of frustration.

In this tutorial, we will see how to build a realtime chat widget with Go, Pusher, and JavaScript. Here’s a demo of the application:

In the above image, we built a website that sells motorcycles and integrates a chat widget. A customer is able to sign up to speak with a support agent and the agent on the other end can manage communication among a number of connected customers.

Prerequisites

To follow along with this article, you will need the following:

  • An IDE of your choice like Visual Studio Code.
  • Go (version >= 0.10.x) installed on your computer. Here’s how you can install Go.
  • Basic knowledge of the Go programming language.
  • Basic knowledge of JavaScript (ES6) and jQuery.
  • Once you have all the above requirements, we can proceed.

Skip the next section if you have already signed up with Pusher and created an application.## Setting up Pusher

The realtime feature of the chat widget is dependent on Pusher so you need to create an account here if you don’t already have one, after the signup process, you will be asked to create a new application. Let’s keep the app credentials nearby because we will need it to integrate Pusher within the cat widget.

Enabling client events

The final thing we will do is enable the Pusher application to trigger events from the client (browser) over a private channel. We need this feature because it is what will make it possible for a support agent to securely chat with a customer without having to send the message through the backend server first. Follow the steps below to activate client events from the dashboard:

  • An IDE of your choice like Visual Studio Code.
  • Go (version >= 0.10.x) installed on your computer. Here’s how you can install Go.
  • Basic knowledge of the Go programming language.
  • Basic knowledge of JavaScript (ES6) and jQuery.
  • Once you have all the above requirements, we can proceed.

  • An IDE of your choice like Visual Studio Code.
  • Go (version >= 0.10.x) installed on your computer. Here’s how you can install Go.
  • Basic knowledge of the Go programming language.
  • Basic knowledge of JavaScript (ES6) and jQuery.
  • Once you have all the above requirements, we can proceed.

That’s all we need to do here.

Setting up the codebase

Let’s begin by navigating into the src directory that is located in the $GOPATH and creating a new directory for our app. This will be the root directory for this project:

    $ cd $GOPATH/src
    $ mkdir go-pusher-chat-widget
    $ cd go-pusher-chat-widget


Let’s create the main Go file (this is the entry point of the application) here and call it chat.go. Next, we will install the Go Pusher library that we will reference within the code for the backend server. Run the following code in the terminal to pull in the package:

    $ go get github.com/pusher/pusher-http-go


Skip the next section if you have already signed up with Pusher and created an application.
Open the chat.go file in your favorite IDE and update it with the following code:

    // File: ./chat.go
    package main

    // Here, we import the required packages (including Pusher)
    import (
        "encoding/json"
        "fmt"
        "io/ioutil"
        "log"
        "net/http"
        pusher "github.com/pusher/pusher-http-go"
    )

    // Here, we register the Pusher client
    var client = pusher.Client{
        AppId:   "PUSHER_APP_ID",
        Key:     "PUSHER_APP_KEY",
        Secret:  "PUSHER_APP_SECRET",
        Cluster: "PUSHER_APP_CLUSTER",
        Secure:  true,
    }

    // Here, we define a customer as a struct
    type customer struct {
        Name  string `json:"name" xml:"name" form:"name" query:"name"`
        Email string `json:"email" xml:"email" form:"email" query:"email"`
    }

    func main() {

        // Serve the static files and templates from the public directory
        http.Handle("/", http.FileServer(http.Dir("./public")))

        // -------------------------------------------------------
        // Listen on these routes for new customer registration and User authorization,
        // thereafter, handle each request using the matching handler function.
        // -------------------------------------------------------
        http.HandleFunc("/new/customer", broadcastCustomerDetails)
        http.HandleFunc("/pusher/auth", pusherAuth)

        // Start executing the application on port 8070
        log.Fatal(http.ListenAndServe(":8070", nil))
    }

In the code above, we registered a new Pusher client with the credentials from the app we created earlier on the dashboard.

Skip the next section if you have already signed up with Pusher and created an application.
In the main function, we defined two endpoints, /new/customer and /pusher/auth. The first will be hit when a new customer signs up and the last will authorize the users so they can subscribe to private channels.

We will be serving all static files from a public directory that we will create shortly.

Skip the next section if you have already signed up with Pusher and created an application.
We also defined customer as a struct and attached extra definitions to its properties so that Go knows how to handle incoming payloads and bind their various structures with a new instance of the customer struct.

Let’s create the handler functions for the endpoints, add this code to the chat.go file just before the main function:

    // File: ./chat.go

    // [...]

    func broadcastCustomerDetails(rw http.ResponseWriter, req *http.Request) {
        body, err := ioutil.ReadAll(req.Body)
        if err != nil {
            panic(err)
        }
        var newCustomer customer
        err = json.Unmarshal(body, &newCustomer)
        if err != nil {
            panic(err)
        }
        client.Trigger("one-to-many", "new-customer", newCustomer)
        json.NewEncoder(rw).Encode(newCustomer)
    }

    func pusherAuth(res http.ResponseWriter, req *http.Request) {
        params, _ := ioutil.ReadAll(req.Body)
        response, err := client.AuthenticatePrivateChannel(params)
        if err != nil {
            panic(err)
        }
        fmt.Fprintf(res, string(response))
    }

    // [...]

Above we have two functions. broadcastCustomerDetails receives a new customer’s details and binds it to an instance of the customer struct. We then trigger the received details over to the admin dashboard in an event over the public channel. The pusherAuth authorizes users so they can subscribe to private channels.

This is all the code required for the backend server to work, let’s move on to the frontend.

Building the frontend

In this section, we will start building the frontend of the web application. We will create all the static files that are rendered when a browser is pointed to the address of our application.

Create a new folder in the project directory and call it public, this folder is the root directory for all of our frontend files. In this folder, create three folders css, js and img.

Next, create two files in the root of the public directory named index.html and support.html.

Creating the homepage

Open the index.html file and update it with the following code:

    <!-- File: ./public/index.html -->
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title>X-Cycles</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
        <link rel="stylesheet" href="./css/app.css" >
      </head>

      <body>
        <div class="site-wrapper">
          <div class="site-wrapper-inner">
            <div class="cover-container">

              <header class="masthead clearfix">
                <div class="inner">
                  <h3 class="masthead-brand">X-Cycles</h3>
                  <nav class="nav nav-masthead">
                    <a class="nav-link active" href="#">Home</a>
                    <a class="nav-link" href="#">Features</a>
                    <a class="nav-link" href="#">Contact</a>
                  </nav>
                </div>
              </header>

              <main role="main" class="inner cover">
                <h1 class="cover-heading">X-cycles</h1>
                <p class="lead">We sell the best motorcycles around.</p>
                <p class="lead">
                  <a href="#" class="btn btn-lg btn-secondary">GALLERY</a>
                </p>
              </main>

              <footer class="mastfoot">
              </footer>

            </div>
          </div>
        </div>
        <div class="chatbubble">
            <div class="unexpanded">
                <div class="title">Chat with Support</div>
            </div>
            <div class="expanded chat-window">
              <div class="login-screen container">

                <form id="loginScreenForm">
                  <div class="form-group">
                    <input type="text" class="form-control" id="fullname" placeholder="Name*" required>
                  </div>
                  <div class="form-group">
                    <input type="email" class="form-control" id="email" placeholder="Email Address*" required>
                  </div>
                  <button type="submit" class="btn btn-block btn-primary">Start Chat</button>
                </form>

              </div>
              <div class="chats">
                <div class="loader-wrapper">
                  <div class="loader">
                    <span>{</span><span>}</span>
                  </div>
                </div>
                <ul class="messages clearfix">
                </ul>
                <div class="input">
                  <form class="form-inline" id="messageSupport">
                    <div class="form-group">
                      <input type="text" autocomplete="off" class="form-control" id="newMessage" placeholder="Enter Message">
                    </div>
                    <button type="submit" class="btn btn-primary">Send</button>
                  </form>
                </div>
              </div>
            </div>
        </div>    

        <script src="https://js.pusher.com/4.0/pusher.min.js"></script>
        <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
        <script type="text/javascript" src="./js/app.js"></script>
      </body>
    </html>

In the css directory, create an app.css file and update it with the following code:

    /* File: ./public/css/app.css */
    a,
    a:focus,
    a:hover {
      color: #fff;
    }
    .btn-secondary,
    .btn-secondary:hover,
    .btn-secondary:focus {
      color: #333;
      text-shadow: none;
      background-color: #fff;
      border: .05rem solid #fff;
    }
    html,
    body {
      height: 100%;
      background-color: #333;
    }
    body {
      color: #fff;
      text-align: center;
      text-shadow: 0 .05rem .1rem rgba(0,0,0,.5);
    }
    .site-wrapper {
      display: table;
      width: 100%;
      height: 100%; /* For at least Firefox */
      min-height: 100%;
      box-shadow: inset 0 0 5rem rgba(0,0,0,.5);
      background: url(../img/bg.jpg);
      background-size: cover;
      background-repeat: no-repeat;
      background-position: center;
    }
    .site-wrapper-inner {
      display: table-cell;
      vertical-align: top;
    }
    .cover-container {
      margin-right: auto;
      margin-left: auto;
    }
    .inner {
      padding: 2rem;
    }
    .masthead {
      margin-bottom: 2rem;
    }
    .masthead-brand {
      margin-bottom: 0;
    }
    .nav-masthead .nav-link {
      padding: .25rem 0;
      font-weight: 700;
      color: rgba(255,255,255,.5);
      background-color: transparent;
      border-bottom: .25rem solid transparent;
    }
    .nav-masthead .nav-link:hover,
    .nav-masthead .nav-link:focus {
      border-bottom-color: rgba(255,255,255,.25);
    }
    .nav-masthead .nav-link + .nav-link {
      margin-left: 1rem;
    }
    .nav-masthead .active {
      color: #fff;
      border-bottom-color: #fff;
    }
    @media (min-width: 48em) {
      .masthead-brand {
        float: left;
      }

      .nav-masthead {
        float: right;
      }

    }
    /*
     * Cover
     */

    .cover {
      padding: 0 1.5rem;
    }
    .cover .btn-lg {
      padding: .75rem 1.25rem;
      font-weight: 700;
    }
    .mastfoot {
      color: rgba(255,255,255,.5);
    }
    @media (min-width: 40em) {
      .masthead {
        position: fixed;
        top: 0;
      }

      .mastfoot {
        position: fixed;
        bottom: 0;
      }
      .site-wrapper-inner {
        vertical-align: middle;
      }

      /* Handle the widths */
      .masthead,
      .mastfoot,
      .cover-container {
        width: 100%;
      }

    }
    @media (min-width: 62em) {
      .masthead,
      .mastfoot,
      .cover-container {
        width: 42rem;
      }

    }
    .chatbubble {
        position: fixed;
        bottom: 0;
        right: 30px;
        transform: translateY(300px);
        transition: transform .3s ease-in-out;
    }
    .chatbubble.opened {
        transform: translateY(0)
    }
    .chatbubble .unexpanded {
        display: block;
        background-color: #e23e3e;
        padding: 10px 15px 10px;
        position: relative;
        cursor: pointer;
        width: 350px;
        border-radius: 10px 10px 0 0;
    }
    .chatbubble .expanded {
        height: 300px;
        width: 350px;
        background-color: #fff;
        text-align: left;
        padding: 10px;
        color: #333;
        text-shadow: none;
        font-size: 14px;
    }
    .chatbubble .chat-window {
      overflow: auto;
    }
    .chatbubble .loader-wrapper {
        margin-top: 50px;
        text-align: center;
    }
    .chatbubble .messages {
        display: none;
        list-style: none;
        margin: 0 0 50px;
        padding: 0;
    }
    .chatbubble .messages li {
        width: 85%;
        float: left;
        padding: 10px;
        border-radius: 5px 5px 5px 0;
        font-size: 14px;
        background: #c9f1e6;
        margin-bottom: 10px;
    }
    .chatbubble .messages li .sender {
        font-weight: 600;
    }
    .chatbubble .messages li.support {
        float: right;
        text-align: right;
        color: #fff;
        background-color: #e33d3d;
        border-radius: 5px 5px 0 5px;
    }
    .chatbubble .chats .input {
        position: absolute;
        bottom: 0;
        padding: 10px;
        left: 0;
        width: 100%;
        background: #f0f0f0;
        display: none;
    }
    .chatbubble .chats .input .form-group {
        width: 80%;
    }
    .chatbubble .chats .input input {
        width: 100%;
    }
    .chatbubble .chats .input button {
        width: 20%;
    }
    .chatbubble .chats {
      display: none;
    }
    .chatbubble .login-screen {
      margin-top: 20px;
      display: none;
    }
    .chatbubble .chats.active,
    .chatbubble .login-screen.active {
      display: block;
    }
    /* Loader Credit: https://codepen.io/ashmind/pen/zqaqpB */
    .chatbubble .loader {
      color: #e23e3e;
      font-family: Consolas, Menlo, Monaco, monospace;
      font-weight: bold;
      font-size: 10vh;
      opacity: 0.8;
    }
    .chatbubble .loader span {
      display: inline-block;
      -webkit-animation: pulse 0.4s alternate infinite ease-in-out;
              animation: pulse 0.4s alternate infinite ease-in-out;
    }
    .chatbubble .loader span:nth-child(odd) {
      -webkit-animation-delay: 0.4s;
              animation-delay: 0.4s;
    }
    @-webkit-keyframes pulse {
      to {
        -webkit-transform: scale(0.8);
                transform: scale(0.8);
        opacity: 0.5;
      }

    }
    @keyframes pulse {
      to {
        -webkit-transform: scale(0.8);
                transform: scale(0.8);
        opacity: 0.5;
      }

    }

Skip the next section if you have already signed up with Pusher and created an application.
Now let’s include some JavaScript. In the js directory, create an app.js file and paste the following code:

    // File: ./public/js/app.js
    (function() {
        'use strict';

        var pusher = new Pusher('PUSHER_APP_KEY', {
            authEndpoint: '/pusher/auth',
            cluster: 'PUSHER_APP_CLUSTER',
            encrypted: true
        });

        let chat = {
            name:  undefined,
            email: undefined,
            myChannel: undefined,
        }

        const chatPage   = $(document)
        const chatWindow = $('.chatbubble')
        const chatHeader = chatWindow.find('.unexpanded')
        const chatBody   = chatWindow.find('.chat-window')

        let helpers = {
            ToggleChatWindow: function () {
                chatWindow.toggleClass('opened')
                chatHeader.find('.title').text(
                    chatWindow.hasClass('opened') ? 'Minimize Chat Window' : 'Chat with Support'
                )
            },

            ShowAppropriateChatDisplay: function () {
                (chat.name) ? helpers.ShowChatRoomDisplay() : helpers.ShowChatInitiationDisplay()
            },

            ShowChatInitiationDisplay: function () {
                chatBody.find('.chats').removeClass('active')
                chatBody.find('.login-screen').addClass('active')
            },

            ShowChatRoomDisplay: function () {
                chatBody.find('.chats').addClass('active')
                chatBody.find('.login-screen').removeClass('active')
                setTimeout(function(){
                    chatBody.find('.loader-wrapper').hide()
                    chatBody.find('.input, .messages').show()
                }, 2000)
            },

            NewChatMessage: function (message) {
                if (message !== undefined) {
                    const messageClass = message.sender !== chat.email ? 'support' : 'user'
                    chatBody.find('ul.messages').append(
                        `<li class="clearfix message ${messageClass}">
                            <div class="sender">${message.name}</div>
                            <div class="message">${message.text}</div>
                        </li>`
                    )
                    chatBody.scrollTop(chatBody[0].scrollHeight)
                }
            },

            SendMessageToSupport: function (evt) {
                evt.preventDefault()
                let createdAt = new Date()
                createdAt = createdAt.toLocaleString()
                const message = $('#newMessage').val().trim()

                chat.myChannel.trigger('client-guest-new-message', {
                    'sender': chat.name,
                    'email': chat.email,
                    'text': message,
                    'createdAt': createdAt 
                });

                helpers.NewChatMessage({
                    'text': message,
                    'name': chat.name,
                    'sender': chat.email
                })

                $('#newMessage').val('')
            },

            LogIntoChatSession: function (evt) {
                const name  = $('#fullname').val().trim()
                const email = $('#email').val().trim().toLowerCase()

                chatBody.find('#loginScreenForm input, #loginScreenForm button').attr('disabled', true)

                if ((name !== '' && name.length >= 3) && (email !== '' && email.length >= 5)) {
                    axios.post('/new/customer', {"name":name, "email":email}).then(response => {
                        chat.name = name
                        chat.email = email
                        console.log(response.data.email)
                        chat.myChannel = pusher.subscribe('private-' + response.data.email);
                        helpers.ShowAppropriateChatDisplay()
                    })
                } else {
                    alert('Enter a valid name and email.')
                }

                evt.preventDefault()
            }
        }

        pusher.bind('client-support-new-message', function(data){
            helpers.NewChatMessage(data)
        })

        chatPage.ready(helpers.ShowAppropriateChatDisplay)
        chatHeader.on('click', helpers.ToggleChatWindow)

        chatBody.find('#loginScreenForm').on('submit', helpers.LogIntoChatSession)
        chatBody.find('#messageSupport').on('submit', helpers.SendMessageToSupport)
    }());

Above, we instantiated a Pusher object instance and then we created a helpers object. In this object lies the meat of the script. In the helpers object we have a few methods that do specific tasks:

  • An IDE of your choice like Visual Studio Code.
  • Go (version >= 0.10.x) installed on your computer. Here’s how you can install Go.
  • Basic knowledge of the Go programming language.
  • Basic knowledge of JavaScript (ES6) and jQuery.
  • Once you have all the above requirements, we can proceed.

Skip the next section if you have already signed up with Pusher and created an application.### Creating the support dashboard

Open the support.html file and update it with the following code:

    <!-- File: ./public/support.html -->
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title>X-Cycles | Support </title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
        <link rel="stylesheet" href="./css/support.css" >
      </head>

      <body>
        <header>
            <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
                <a class="navbar-brand" href="#">Dashboard</a>
            </nav>
        </header>

        <div class="container-fluid">
            <div class="row" id="mainrow">
                <nav class="col-sm-3 col-md-2 d-none d-sm-block bg-light sidebar">
                    <ul class="nav nav-pills flex-column" id="rooms">
                    </ul>
                </nav>
                <main role="main" class="col-sm-9 ml-sm-auto col-md-10 pt-3" id="main">
                    <h1>Chats</h1>
                    <p>👈 Select a chat to load the messages</p>
                    <p>&nbsp;</p>
                    <div class="chat" style="margin-bottom:150px">
                        <h5 id="room-title"></h5>
                        <p>&nbsp;</p>
                        <div class="response">
                            <form id="replyMessage">
                                <div class="form-group">
                                    <input type="text" placeholder="Enter Message" class="form-control" name="message" />
                                </div>
                            </form>
                        </div>
                        <div class="table-responsive">
                          <table class="table table-striped">
                            <tbody id="chat-msgs">
                            </tbody>
                        </table>
                    </div>
                </main>
            </div>
        </div>

        <script src="https://js.pusher.com/4.0/pusher.min.js"></script>
        <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
        <script type="text/javascript" src="./js/support.js"></script>
      </body>
    </html>

Let’s write the style for the support page. In the css directory, create a support.css file and paste the following code:

    /* File: ./public/css/support.css */
    body {
        padding-top: 3.5rem;
    }
    h1 {
        padding-bottom: 9px;
        margin-bottom: 20px;
        border-bottom: 1px solid #eee;
    }
    .sidebar {
        position: fixed;
        top: 51px;
        bottom: 0;
        left: 0;
        z-index: 1000;
        padding: 20px 0;
        overflow-x: hidden;
        overflow-y: auto;
        border-right: 1px solid #eee;
    }
    .sidebar .nav {
        margin-bottom: 20px;
    }
    .sidebar .nav-item {
        width: 100%;
    }
    .sidebar .nav-item + .nav-item {
        margin-left: 0;
    }
    .sidebar .nav-link {
        border-radius: 0;
    }
    .placeholders {
        padding-bottom: 3rem;
    }
    .placeholder img {
        padding-top: 1.5rem;
        padding-bottom: 1.5rem;
    }
    tr .sender {
        font-size: 12px;
        font-weight: 600;
    }
    tr .sender span {
        color: #676767;
    }
    .response {
        display: none;
    }

Now let’s add the JavaScript for the page. In the js directory, create a support.js file and update it with the following code:

    // File: ./public/js/support.js
    (function () {
        var pusher = new Pusher('PUSHER_APP_KEY', {
            authEndpoint: '/pusher/auth',
            cluster: 'PUSHER_APP_CLUSTER',
            encrypted: true
        });

        let chat = {
            messages: [],
            currentRoom: '',
            currentChannel: '',
            subscribedChannels: [],
            subscribedUsers: []
        }

        var generalChannel = pusher.subscribe('one-to-many');

        const chatBody = $(document)
        const chatRoomsList = $('#rooms')
        const chatReplyMessage = $('#replyMessage')

        const helpers = {

            clearChatMessages: () => $('#chat-msgs').html(''),

            displayChatMessage: (message) => {
                if (message.email === chat.currentRoom) {
                    $('#chat-msgs').prepend(
                        `<tr>
                            <td>
                                <div class="sender">${message.sender} @ <span class="date">${message.createdAt}</span></div>
                                <div class="message">${message.text}</div>
                            </td>
                        </tr>`
                    )
                }
            },

            loadChatRoom: evt => {
                chat.currentRoom = evt.target.dataset.roomId
                chat.currentChannel = evt.target.dataset.channelId
                if (chat.currentRoom !== undefined) {
                    $('.response').show()
                    $('#room-title').text(evt.target.dataset.roomId)
                }
                evt.preventDefault()
                helpers.clearChatMessages()
            },

            replyMessage: evt => {
                evt.preventDefault()
                let createdAt = new Date()
                createdAt = createdAt.toLocaleString()
                const message = $('#replyMessage input').val().trim()
                chat.subscribedChannels[chat.currentChannel].trigger('client-support-new-message', {
                    'name': 'Admin',
                    'email': chat.currentRoom,
                    'text': message, 
                    'createdAt': createdAt 
                });

                helpers.displayChatMessage({
                    'email': chat.currentRoom,
                    'sender': 'Support',
                    'text': message, 
                    'createdAt': createdAt
                })

                $('#replyMessage input').val('')
            },
        }

        generalChannel.bind('new-customer', function(data) {
            chat.subscribedChannels.push(pusher.subscribe('private-' + data.email));
            chat.subscribedUsers.push(data);
            // render the new list of subscribed users and clear the former
            $('#rooms').html("");
            chat.subscribedUsers.forEach(function (user, index) {
                    $('#rooms').append(
                        `<li class="nav-item"><a data-room-id="${user.email}" data-channel-id="${index}" class="nav-link" href="#">${user.name}</a></li>`
                    )
            })
        })

        pusher.bind('client-guest-new-message', function(data){
            helpers.displayChatMessage(data)
        })

        chatReplyMessage.on('submit', helpers.replyMessage)
        chatRoomsList.on('click', 'li', helpers.loadChatRoom)
    }())

Above, the script looks almost similar to the app.js script. The helpers object contains the following functions:

  • An IDE of your choice like Visual Studio Code.
  • Go (version >= 0.10.x) installed on your computer. Here’s how you can install Go.
  • Basic knowledge of the Go programming language.
  • Basic knowledge of JavaScript (ES6) and jQuery.
  • Once you have all the above requirements, we can proceed.

After declaring the helpers, we bind to the Pusher channel and register our listeners.

Skip the next section if you have already signed up with Pusher and created an application.## Running the application

To test the application, we will run the application by typing this command in the terminal:

    $ go run chat.go


We can visit these addresses, http://127.0.0.1:8070 and http://127.0.0.1:8070/support.html, on a web browser using different windows to test that the application works correctly. Here’s what we should see:

Conclusion

In this tutorial, we learned how to create a basic realtime web chat widget using Go and JavaScript. The source code for this project is available here on GitHub.

Learn More

Introducing TensorFlow.js: Machine Learning in Javascript

Express.js & Node.js Course for Beginners - Full Tutorial

Full Stack Developers: Everything You Need to Know

ES5 to ESNext — here’s every feature added to JavaScript since 2015

5 Javascript (ES6+) features that you should be using in 2019

12 Concepts That Will Level Up Your JavaScript Skills

Moving from NodeJS to Go

The Complete JavaScript Course 2019: Build Real Projects!

JavaScript Bootcamp - Build Real World Applications

JavaScript: Understanding the Weird Parts

Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

The Full JavaScript & ES6 Tutorial - (including ES7 & React)

Google’s Go Essentials For Node.js / JavaScript Developers

Google’s Go Essentials For Node.js / JavaScript Developers

Keep checking this article as I will make it more comparative between Go and Node constructs. This article will be a brain dump of all I’ve learned so far about Google’s Go language.

Keep checking this article as I will make it more comparative between Go and Node constructs. This article will be a brain dump of all I’ve learned so far about Google’s Go language.

As an advanced JavaScript developer, The more I work with JavaScript the more I understand the advantages of a statically typed language.

Paradoxically, it is easier to make TypeErrors in JavaScript than in other languages I have used. When you do work on the back-end, it is unacceptable. On the front end, when you are a little bit serious about your work, you notice how so many JS developer do NOT understand the typing system in JavaScript because it is not forced on them. That’s why there is a lot of garbage JS code in the world.

JavaScript is HARD when you get serious about understanding it under the hood. So many (kind of hidden) complex concepts you have to understand in order to make sense of the errors in your code (or why it’s working but that’s not the correct way to do it…).

JavaScript is fascinating but sometimes you want to try something else and broaden your horizon.

This article will be a brain dump of all I’ve learned so far about Google’s Go language.

I assume you have installed the Go binaries on your computer.

Variables

package main

import "fmt"

func main() {
    // you MUST use a declared variable otherwise compilation error
    
    var toto int8 = 123
    fmt.Println(toto)

    var tito = 123
    fmt.Println(tito)

    // variable declaration with type inference
    toti := 123
    fmt.Println(toti)

    // variable declaration (implicitly initialized to zero-value,// for numeric types = 0)
    var tata int
    fmt.Println(tata)

    // variable assignment
    tata = 951
    fmt.Println(tata)
}

Number Types

package main

import "fmt"

func main() {
    // Go does not convert types automatically
    // need to explicitly convert them
    var (
        i int8    = 20
        f float32 = 5.6
    )
    fmt.Println(i + int8(f+1.9))

    var (
        j int32 = 456
        k int64 = 987654
    )
    fmt.Println(int64(j) + k)

    // byte is an alias for uint8
    // no need to convert uint8 to byte because same
    var (
        l byte  = 123
        m uint8 = 45
    )
    fmt.Println(l + m)

    // int is an alias for int32 or int64, // dedpending on your mqchine's integer value
    var (
        n int32 = 324
        o int   = 84529899
    )
    fmt.Println(int(n) + o)

    // uint is an alias for uint32 or int64
    var (
        p uint   = 999
        q uint64 = 9999
    )
    fmt.Println(p + uint(q))

    // float operations do not produce an exact result after n decimals// like in most languages
    myFloat := 1.000
    myFloat2 := .999
    fmt.Println(myFloat - myFloat2)

    // arithmetic operations
    fmt.Printf("%d + %d = %d \n", 25, 39, 25+39)
    fmt.Printf("%d - %d = %d \n", 25, 39, 25-39)
    fmt.Printf("%d * %d = %d \n", 25, 39, 25*39)
    fmt.Printf("%d / %d = %v \n", 25, 39, 25/39)
    fmt.Printf("%d %% %d = %v \n", 25, 39, 25%39)

    // constants
    const goldenRatio float64 = 1.6180327868852
    fmt.Printf("The golden ration approximately %f \n", goldenRatio)
    fmt.Printf("The ration truncated to the 3rd decimal is %.3f \n", goldenRatio)

    // formatted printing for numeric types
    fmt.Printf("decimal is %d \n", 99)
    fmt.Printf("binary is %b \n", 99)
    fmt.Printf("unicode reference is %c \n", 99)
    fmt.Printf("hexadecimal is %x \n", 99)
    fmt.Printf("scientific notation of goldenRatio is %e \n", goldenRatio)
}

String Types

package main

import "fmt"

func main() {
    // zero-value for strings is an empty string ""
    var str string
    fmt.Println("\"" + str + "\"")

    str = "This is a string."
    fmt.Println(str)

    str2 := "Another string."
    fmt.Println(str2)

    // raw string literalss with back ticks
    // can be written on multiple lines and no escapes
    str3 := `
        Raw string in the building.
        And another line.
    `
    fmt.Println(str3)

    str4 := "Dunya nzuri = "
    str5 := "美麗的世界"
    str6 := str4 + str5
    fmt.Println(str6)

    // In Go, strings are immutable sequences of bytes
    // you can access each byte
    str7 := "mazoezi"
    b1 := str7[0]
    b2 := str7[1]
    fmt.Println(str7, "\n\t", b1, "=", string(b1), b2, "=", string(b2))

    // substrings
    s1 := str7[0:2]
    s2 := str7[2:4]
    s3 := str7[:3]
    s4 := str7[3:]
    fmt.Printf("%s \t %s \t %s \t %s \n", s1, s2, s3, s4)

    // length of string
    fmt.Println(str7, " = ", len(str7), " characters")

    // single character = rune -> numeric type, sane as int32
    // can be converted to a string
    var r rune 
    // single qutoes
    r = '✖'         // same as r = 10006
    fmt.Println("This is a rune : ", r, " which in string = ", string(r))
}

If and For Statements

package main

import "fmt"

func main() {
    a := 27

    // no parentheses surrounding the condition
    // the body of the if statement MUST be surrounded with {} // no matter what
    // no truthy values
    if a > 25 {
        fmt.Println("a is greater than 25")
    } else {
        fmt.Println("a is less than 25")
    }

    b := 546

    // if statement have block scope
    if b == 546 {
        // c does not exist outside of the if
        c := 54
        fmt.Println(b + c)
    }

    // can declare a variable available ONLY in if and else block
    if d := 44; b < 25 {
        fmt.Println("a is greater than 25", d)
    } else {
        fmt.Println("a is less than 25", d)
    }

    // for loop, no parentheses around signature
    e := 2
    for index := 0; index < 10; index++ {
        if index+e == 2 {
            continue
        }
        if index > 8 {
            break
        }
        fmt.Println("index =", index)

    }

    // equivalent of while statement
    f := 0
    for f < 6 {
        fmt.Println("f =", f)
        // don't forget to have smth allowing to get out of the loop
        f++
    }

    // infinite loop
    g := 0
    for {
        fmt.Println("g =", g)
        g++
        // to stop it at some point
        if g > 30 {
            break
        }
    }

    // for range loop
    
    h := "this is great!"
    for k, v := range h {
        fmt.Println("offset (position) =",k,", value as rune =", v, ",value as string = ", string(v))
    }

    // logical operators
    fmt.Printf("%t && %t is %t \n", true, false, true && false)
    fmt.Printf("%t || %t is %t \n", true, false, true || false)
    fmt.Printf("!%t is %t \n", true, !true) 
}

Functions

package main

import "fmt"

func main() {
    addNumbers(353454, 99999)
    addNumbers(353, 9999)
    addNumbers(3554, 99)

    a := addInts(99, 1)
    fmt.Println("a = ", a)

    a = addInts(9, 675)
    fmt.Println("a = ", a)

    div, remainder := divAndRemainder(57, 7)
    fmt.Println(div, remainder)

    // use underscore to ignore a returned value
    div, _ = divAndRemainder(57, 7)
    fmt.Println(div)

    _, remainder = divAndRemainder(57, 7)
    fmt.Println(remainder)

    divAndRemainder(57, 7)

    // in Go, all functions calls are done by value// (exceptions, see later)
    // a copy of input argument variables is passed o the function
    y := 5
    arr := [2]int{45, 99}
    s := "olo"
    doubleFail(y, arr, s)
    fmt.Println("outside doublefail", y, arr, s)
}

// where function is placed does not matter
// no overloading of function w/ different input parameters
func addNumbers(a int, b int) {
    fmt.Println(a + b)
}

func addInts(c int, d int) int {
    return c + d
}

// multiple returns
func divAndRemainder(e int, f int) (int, int) {
    return e / f, e%f
}

func doubleFail(a int, arr [2]int, s string)  {
    a = a * 2
    for index := 0; index < len(arr); index++ {
        arr[index] *= 2
    }
    s = s + s
    fmt.Println("in doublefail", a, arr, s)
}

Pointers

// pointers are used in C to simulate arrays and strings
package main

import "fmt"

// pointer as input parameter
func setTo10(pointerToInt *int) {
    *pointerToInt = 10
}

func setTo10Fail(pointerToInt *int) {
    fmt.Println("#setTo!(Fail pointer passed as argument =", pointerToInt)
    // will not affect the original pointer because passed by value
    pointerToInt = new(int)
    fmt.Println("#setTo!(Fail reassignment =", pointerToInt)

    // set the value in memory to 10
    *pointerToInt = 10
}

func main() {
    a := 10
    // & = reference / pointer to variable "a"
    // the value of b is the location where a is stored
    b := &a
    // c is a copy of "a" at a given time, // they are independent of each other after the first assignment
    c := a
    fmt.Println(a, b, *b, c)

    a = 20
    // to see the value inside the memory location use * // (de-reference the pointer and get to the value)
    fmt.Println(a, b, *b, c)

    // dereference the pointer and assign a value in memory
    // therefore the value of "a" also changes
    *b = 30
    fmt.Println(a, b, *b, c)

    c = 40
    fmt.Println(a, b, *b, c)

    // zero-value for a pointer is nil (absence of value)
    var d *int
    fmt.Println("value of d =", d)
    // cannot read or write value of a nil pointer
    // fmt.Println(*d)   // throws a panic

    e := new(int)
    // new keyword makes a pointer for the type
    fmt.Println("pointer to e =", e)
    // new also allocates memory, here to the zero-value forint type// therefore no panic
    fmt.Println("value of e =", *e)

    f := 20
    fmt.Println("value of f =", f)
    // we pass a pointer to f into that function
    setTo10(&f)
    fmt.Println("value of f after setTo10 =", f)

    g := 30
    fmt.Println("pointer to g =", &g)
    fmt.Println("value of g =", g)
    // on Go, variable in function calls are passed by VALUE
    setTo10Fail(&g)
    fmt.Println("pointer to g after #setTo10Fail =", &g)
    // you CANNOT change the pointer of a variable passed
    fmt.Println("value of g after #setTo10Fail =", g)
}

Arrays

package main

import "fmt"

func main() {
    // zero-value of arrays is an array// of specified length of zero-values of the type inside the array
    var myArrInt [4]int
    fmt.Println(myArrInt)

    myArrInt[0] = 12
    myArrInt[1] = 23
    myArrInt[2] = 34
    myArrInt[3] = 45
    fmt.Println(myArrInt)

    // one-line array assihnement in a composite literal expression
    // the length of the array is part of its type definition
    myArrInt = [4]int{111, 222, 333, 444}
    fmt.Println(myArrInt)

    myArrStr := [4]string{"titi", "tooi", "tatu", "teti"}
    fmt.Println(myArrStr)

    // iterate over an array
    for i, val := range myArrInt {
        fmt.Printf("At index %d = %d \n", i, val)
    }

    // slice of an array
    myArrStr2 := myArrStr[:2]

    // use _ to ignore the index variable
    for _, val := range myArrStr2 {
        fmt.Println(val)
    }
    // use of arrays is limited, slices are more flexible

}

Slices and Maps

package main

import "fmt"

func main() {
    /*
      SLICE = growable sequence of values of a single specified type
      the size is not part of the type definition
    */
    // define a slice in a composite literal expression
    myFiboSlice := []int{0, 1, 2, 3, 5, 8, 13}
    fmt.Println("myFiboSlice is", myFiboSlice)

    // create a slice from another slice = slice expression
    myFiboSlice2 := myFiboSlice[1:4]
    fmt.Println("myFiboSlice2 is", myFiboSlice2)

    // carefully with subslices because// they point to the same location in memory as the original slice
    myFiboSlice[2] = 00
    fmt.Println("\n myFiboSlice is", myFiboSlice)
    // slices are reference types, behave like pointers
    fmt.Println("myFiboSlice2 after modifying myFiboSlice is", myFiboSlice2)

    // zero-value for slice is nil slice (no value in slice)
    titiSlice := []string{"titi_one", "titi_two", "titi_three"}
    var totoSlice []string
    fmt.Println("totoSlice is", totoSlice)
    fmt.Println("length of totoSlice is", len(totoSlice))

    // assigning slice to another slice makes them share same location// in memrory
    totoSlice = titiSlice
    fmt.Println("totoSlice is now", totoSlice)
    titiSlice[0] = "titi_zero"
    fmt.Println("after modifying titiSlice, totoSlice is now", totoSlice)

    // this behavior also happens in functions
    modifySlice(titiSlice)
    fmt.Println("after modifying titiSlice in a function, totoSlice is now", totoSlice)

    // define a slice filled with wero-values of the type specified
    // last argument is the capacity of the underlying array
    s1 := make([]int, 5, 20)

    // copy a slice from another slice with the copy built-in function// "copy in slice s1 the elements of slice myFiboSlice
    copy(s1, myFiboSlice) 
    fmt.Println("\ns1 is", s1)

    // append built-in function -> returns a new slice
    // increases the length of the slice
    s2 := append(s1, 21, 34, 55)
    fmt.Println("s2 is", s2)

    // append a slice to another slice
    s3 := []int{111, 222, 333}
    // notice the ... after the s3 identier to spread the elements in it
    s3 = append(s2, s3...)
    fmt.Println("s3 is", s3)

    // deleting element at offset 6 (7th) from slice (the easy way)
    s3 = append(s3[:6], s3[7:]...)
    fmt.Println("s3 is", s3)

    // deleting from slice, the more involved way (see the function below)// it was tough to find a working algorithm !
    s3 = deleteItemFromSliceAtIndex(s3, 3)
    fmt.Println("s3 is", s3)

    // slices are based on an underlying array // for which you can specify the capacity // (number of spots to allocate in memory)
    // here the slice is initialized with 5 zero-value spots // but the underlying array has 100 spots in case we append.
    // This allows to avoid to copy and create a new underlying array // every time we append more than the length of the slice
    s4 := make([]int, 5, 100)
    fmt.Println("s4 is", s4)
    fmt.Println("length of s4 is", len(s4))
    fmt.Println("capacity of s4 is", cap(s4))

    // make a slice of bytes out of a string
    hello := "李先生你好"
    myByteSlice := []byte(hello)
    fmt.Println("\nmyByteSlice is", myByteSlice)

    myRuneSlice := []rune(hello)
    fmt.Println("\nmyRuneSlice is", myRuneSlice)

    // multidimensional slice
    s5 := []int{84, 64, 44}
    s6 := []int{42, 32, 22}
    s7 := [][]int{s5, s6}
    fmt.Println("\ns7 is", s7)

    /********** MAPS ****************/

    // associate value of single data type to value of another data type// collection of key / value pairs (key not restricted to a string)
    // maps are unordered

    myMap := make(map[string]string)
    myMap["name"] = "GOTO"
    myMap["firstname"] = "Florian"
    myMap["occupation"] = "Software Engineer"
    myMap["native_language"] = "French"
    fmt.Printf("\n%v\n", myMap)

    // access value in map
    fmt.Printf("The name is %v\n", myMap["name"])

    // if no value on a key return zero-value for the type of value
    fmt.Printf("The age is %v\n", myMap["age"])

    // make sure a key is in the map = comma ok idiom
    // v = value associated w/ existing key
    // ok = boolean is key in map
    if v, ok := myMap["isBillionaire"]; ok {
        fmt.Println("isBillionaire in map =", v)
    } else {
        // ok == false
        fmt.Println("isBillionaire in map =", ok)
    }

    // map literal declaration (composite literal expression)
    worldMap := map[int]string{
        // every line must end with a comma
        1: "nimefurahi kukuona",
        2: "приємно бачити вас",
        3: "तुम्हें देखकर अच्छा लगा",
        4: "ስለተያየን ደስ ብሎኛል",
        5: "ดีใจที่ได้พบคุณ",
    }

    // iterate over a map - order of iteration is random
    for keyInMap, valueInMap := range worldMap {
        fmt.Println(keyInMap, "=", valueInMap)
    }

    // delete value from map (built-in function)
    delete(worldMap, 2)
    fmt.Println("worldMap :", worldMap)

    // same as slices, maps are passed by reference

    // nil map
    var tMap map[string]int
    // writing to zero-valued map will make the program panic
    // tMap["toto"] = 12345678
    fmt.Println("tMap :", tMap)
    fmt.Println("length of tMap :", len(tMap))

    // delete an element in map
    sport := map[string]string{"yoyo": "ok", "ping pong": "great"}
    fmt.Println("sport :", sport)
    delete(sport, "yoyo")
    fmt.Println("sport :", sport)

    // to make sure that you delete an existing pair in the map
    langs := map[string]string{
        "ES6+":         "great",
        "Go":           "cool",
        "TypeScript":   "ok",
        "Python":       "over hyped but nice",
        "Bash":         "necessary",
        "HTML5":        "necessary",
        "CSS":          "necessary",
        "Elm":          "niche",
        "Java":         "no comments...",
        "Rust":         "to assess",
        "Web Assembly": "who knows",
    }
    fmt.Println("lang :", langs)
    if _, ok := langs["assembly"]; ok {
        delete(langs, "assembly")
    }
    fmt.Println("lang :", langs)

}

func modifySlice(s []string) {
    s[1] = "one"
    fmt.Println("\n", s)
}

func deleteItemFromSliceAtIndex(s []int, index int) []int {
    temp := make(map[int]int, len(s))
    // convert slice to map
    for i, v := range s {
        if i == index {
            continue
        }
        temp[i] = v
    }

    newSlice := make([]int, len(temp))

    for i := 0; i < len(temp); i++ {
        if i == index {
            // comma dot idiom = check if key in map
            value, ok := temp[i+1]

            // check that next index exists
            if ok && i <= len(temp) {
                newSlice[i] = value
                continue
            }
        }

        if i > index {
            newSlice[i] = temp[i+1]
            continue
        }

        newSlice[i] = temp[i]
    }

    return newSlice
}

I won’t go into concurrency and the like (Goroutines, channels…) here which are more advanced topics that make Go apart from other languages.

Keep learning new things, never stops !

Moving from NodeJS to Go

Moving from NodeJS to Go

You are a seasoned NodeJS developer, and are looking to learn a new language, but you don't want to go deep, just see how things compare with your current expertise, and then make a final decision.

You are a seasoned NodeJS developer, and are looking to learn a new language, but you don't want to go deep, just see how things compare with your current expertise, and then make a final decision.

The approach of this article is this:

Table of Contents

  • Why Go
  • Hello World
  • Data Types, Variables and Constants
  • Conditional Statements
  • Loops
  • Objects
  • Http Server
  • Middleware
  • Consuming APIs
  • Database Connections
  • Frameworks
  • Conclusion

You are a seasoned NodeJS developer, and are looking to learn a new language, but you don’t want to go deep, just see how things compare with your current expertise, and then make a final decision.

Chances are, you were initially a PHP or Python developer, then you found your way into NodeJS and now you feel like you want to expand your expertise. Of course I might be wrong, but in this article we are going to look at some common patterns when working with NodeJS and how they compare in Go.

Why Go

The most common reasons to learn Go include the following:

  • Why Go
  • Hello World
  • Data Types, Variables and Constants
  • Conditional Statements
  • Loops
  • Objects
  • Http Server
  • Middleware
  • Consuming APIs
  • Database Connections
  • Frameworks
  • Conclusion

Here’s an example with the net/http package in the go documentation. Examples are provided, and there are even tabs for the source code files. Sweet!

Is Go Better Than **JavaScript **?. No, I don’t think so, nor do I think JavaScript is better. The idea is to use the right tool for the job, and sometimes, one tool is better than another one, or one developer is better in one tool over another. So learn what you can, and choose the right thing that you are sure will get the job done for you.

Hello World

Here’s a hello world program in NodeJS

console.log('Hello World');

Running this will produce Hello World in the terminal.

node app.js

Here’s an equivalent hello world program in Go

package main

import "fmt"

func main() {
    fmt.Println("Hello World")
}

Go follows a certain structure which involves:

  • Why Go
  • Hello World
  • Data Types, Variables and Constants
  • Conditional Statements
  • Loops
  • Objects
  • Http Server
  • Middleware
  • Consuming APIs
  • Database Connections
  • Frameworks
  • Conclusion

This code can be run by typing in the following, or see it in GoPlay Space.

go run main.go

where main.go is the name of the file.

Data Types, Variables and Constants

JavaScript is dynamically typed, which means you do not have to specify types when defining variables, and the variables can change their types as you program along.

That being said, JavaScript has the following types:

To define variables in JavaScript(NodeJS) we’d write this:

const num = 3; // declaring a constant number
let flt = 2.34; // declaring a number (float)
let a = true; // declaring a boolean
let b = false;
var name = 'Scotch'; // declaring a string 

Go however, is statically typed, which means we have to define the types before hand, or assign them and let them be inferred. Here’s a comprehensive list of Go Types.

An equivalent of the above JavaScript definitions in Go is

package main

const num = 3 // declaring a constant

func main() {
    var flt float64 = 2.34 // declaring a float 
    var name string = "Scotch" // declaring a string
    a, b := true, false,  // shorthand declaration syntax
}

Assigning initial values in Go is done by with the var keyword, a variable name, and a type. Additionally and more commonly the := shorthand syntax can be used. When declaring variables with := , they are automatically assigned the correct type. Additionally, you can assign multiple values in one line like we have done in a, b := true, false. This will assign both a and b to the right hand values respectively.

Here is an array of strings in JavaScript

const names = ['Scotch', 'IO'];

While Go has arrays, what we typically refer to arrays in JavaScript are referred to as slices in Go. Here’s an example of a slice in Go:

package main

func main() {
    names := []string{"Scotch", "IO"}
}

As an example, we’ll write a small program that returns the substring of a string at index 10. So a sentence like Luke, I'm not your father will end up being Luke, I'm

JavaScript

const sentence = '`Luke, I\'m not your Father';`
console.log(sentence.substr(0,10));

Go

package main

import "fmt"

func main() {
    sentence := "Luke, I'm not your Father"
    fmt.Println(sentence[:10])
}

You can run the app in Goplay Space

Conditional Statements

Conditional statements include if else and switch statements. Here’s an example in NodeJS.

const age = 10;

if (age > 10) {
    console.log('you are old');
} else {
    console.log('you are young');
}

const gender = 'female';

switch (gender) {
    case 'female':
        console.log('your name is tonie');
        break;
    case 'male':
        console.log('your name is tony');
        break;;
    default:
        ///
}

Here’s an equivalent in Go

package main

import "fmt"

func main() {
    age := 10
    if age > 10 {
        fmt.Println("you are old")
    } else {
        fmt.Println("you are young")
    }

    gender := "female"
    switch gender {
    case "female":
        fmt.Println("your name is tonie ")
    case "male":
        fmt.Println("your name is tony")
    default:
        ///
    }
}

You can run the app in GoPlay Space.

You’ll notice the conditionals are a bit cleaner in Golang, with less brackets.

Loops

JavaScript has 3 loops: for loop, while loop, and a do while loop. Here’s a for loop example.

// normal for loop
for i = 0; i < 10; i++ {
    console.log(i);
}

// key, value loop
for (var key in p) {
    if (p.hasOwnProperty(key)) {
        console.log(key + ' -> ' + p[key]);
    }
}

// another key value loop
Object.keys(obj).forEach(function(key) {
    console.log(key, obj[key]);
})

// there's also a `for...of`

Go has only one type of loop, and it’s the for loop. Don’t let that deceive you though as the for loop in Go is very versatile and can emulate almost any type of loop. Let’s look at a simple example:

package main

import (
    "fmt"
)

func main() {
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }

    // key value pairs
    kvs := map[string]string{
        "name":    "Scotch",
        "website": "https://scotch.io",
    }

    for key, value := range kvs {
        fmt.Println(key, value)
    }
}

You can run the app in GoPlay Space.

Objects

Objects are a big part of JavaScript and exist in almost every program. Here’s an Object in JavaScript.

// an object
const Post = {
    ID: 300
    Title: "Moving from NodeJS to Go",
    Author: "Christopher Ganga",
    Difficulty: "Beginner",
}

console.log(Post)

// access values
console.log(Post.ID)
console.log(Post.Title)
console.log(Post.Author)
// ....

// we can also define classes in javascript.

Since Go is statically typed, we need to do a little extra to define objects. There are two ways to do this, and it involves using map. A map is a key value data structure, where the keys are a set (does not contain duplicates).

package main

import (
    "fmt"
)

func main() {
    Post := map[string]interface{}{
        "ID":         300,
        "Title":      "Moving from NodeJS to Go",
        "Author":     "Christopher Ganga",
        "Difficulty": "Beginner",
    }

    fmt.Println(Post)

    // to access values
    fmt.Println(Post["ID"])
    fmt.Println(Post["Title"])
    fmt.Println(Post["Author"])
    // ....
}


You can run this example in Goplay Space

The other way to write obects in Go is by using Structs. A struct is an abstract data structure, with properties and methods. It’s a close equivalent to a Class in Javascript.

package main

import (
    "fmt"
)

type Post struct {
    ID         int
    Title      string
    Author     string
    Difficulty string
}

func main() {
    // create an instance of the Post
    p := Post{
        ID:         300,
        Title:      "Moving from NodeJS to Go",
        Author:     "Christopher Ganga",
        Difficulty: "Beginner",
    }

    fmt.Println(p)

    // to access values
    fmt.Println(p.ID)
    fmt.Println(p.Title)
    fmt.Println(p.Author)
    // ....
}

Struct defines the name of the type and its properties together with the types. We can then create an instance of the type (Post).

You can run this in Goplay Space

Http Server

Now that we know a little about the similarities and differences in language constructs, we can have a look at servers. Since we are coming from NodeJS it’s likely that we’re building a server, that returns JSON for instance.

In NodeJS, chances are while writing a server, you are using Express as the base library for your server. It’s the most common, comes with a router and is the most battle tested. Here’s a NodeJS server.

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello from Express!');
})

app.listen(3000, err => {
  if (err) {
    return console.log('something bad happened', err);
  }

  console.log('`server is listening on 3000'`);
})

The Go standard library provides everything we need to get a server up and running without any external dependencies. The net/http package provides most of this functionality. When building larger applications however, expanding on the base net/http package with third-party packages is common, and one popular package that provides greatly a lot of functionality is the Gorilla Web Toolkit.

Here’s an equivalent Server in Go .

package main

import (
    "net/http"
)

func Hello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))
}

func main() {
    http.HandleFunc("/", Hello)
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

We call http.HandleFunc and give it a route and a handler. Almost sililar to the callback we give to express routes.

You can test this by running go run main.go, assuming your file was named main.go

Now, let’s introduce a router library, because, if we don’t, we’ll have to test whether a request came as a POST , GET or the likes, and use if statements to match specific routes. Something like this:

if req.Method == "POST" {
    // do this
}

To get packages with golang, you usually use a go get <github-link>. To get Gorilla Mux from the Gorilla Web Toolkit we mentioned earlier, we would write the following in our terminal:

go get -u github.com/gorilla/mux

Then we are able to do this. I pulled this directly from Gorilla mux documentation.

package main

import (
     "fmt"
     "net/http"

     "github.com/gorilla/mux"
)
func main() {
    r := mux.NewRouter()
    r.HandleFunc("/", HomeHandler)
    r.HandleFunc("/products", ProductsHandler)
    r.HandleFunc("/articles", ArticlesHandler)

    // details
    r.HandleFunc("/products/{key}", ProductHandler)
    r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
    r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)

    http.Handle("/", r)
}

// example handler
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "Category: %v\n", vars["category"])
}

We see that the same way express accepts patterns, Gorilla mux allows us to use the same patterns. It can get a little complicated than what I’ve described, but I hope you get the idea.

Gorilla mux also comes with helper functions that npm’s body parser helps us achieve. Go purist can however claim that these function can easily be written.

Middleware

Middleware are a great part of NodeJS servers.

They are functions that sit somewhere, and are run before or after the actual request is run. In NodeJS, this is a simple snippet for a middleware that retrieves a secret from the env and uses it to authenticate a user. When reading variables from NodeJS, dotenv is commonly used.

const express = require('express');
const app = express();

// add server_name middleware
function authenticate((req, res, next) => {
    const secret = process.ENV.SECRET;
    if (secret == "") {
        return res.send("secret not found");
    }

    if (!isAuthenticated(req, secret)) {
        return res.send("invalid authentication");
    }

    return next();
})

// use middleware
app.get('/', authenticate, (req, res) => {
  res.send('Hello from Express!');
})

app.listen(3000, err => {
  if (err) {
    return console.log('something bad happened', err);
  }

  console.log('`server is listening on 3000'`);
})

Go takes a similar approach. Since all a middleware does is take in a request, do something with it and decide whether the request should proceed.

package main

import (
    "net/http"
    "os"
)

// our sample authenticate middleware
func Authenticate(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r _http.Request) {
        secret := os.Getenv('SECRET')
        if secret == "" {
            w.Write(_[_]byte("secret not found")
            return
        }

        if !isAuthenticated(r, secret) {
            w.Write(_[]byte("invalid authentication"))
            return
        }
        next.ServeHTTP(w, r)
    }
}

func Hello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))
}

func main() {
    http.HandleFunc("/", Authenticate(Hello)) // call the middeware by wrapping
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

If you are a seasoned JavaScript developer, you’ve probably noticed functions are first class citizens in Go too.

We’ve just written a function that takes in a [http.HandlerFunc](https://golang.org/pkg/net/http/#HandlerFunc "http.HandlerFunc") which is a function type, and the function structure is type HandlerFunc func(ResponseWriter, *Request), just like the Handler we wrote.

The advantage is that the http.HandlerFunc type has a function ServeHTTP which takes in the response and the request pointer we passed, and executes the handle call.

Some people call this approach wrapping functions, but this is the general idea. Again, you can easily write your own middlewares, but there are a couple of libraries out there to help you like

  • Why Go
  • Hello World
  • Data Types, Variables and Constants
  • Conditional Statements
  • Loops
  • Objects
  • Http Server
  • Middleware
  • Consuming APIs
  • Database Connections
  • Frameworks
  • Conclusion
Consuming APIs

Most of the time, our servers usually depend on an external API to get some data it needs. Let’s say for example we are getting users from Github.

This is the approach you would take in a NodeJS app.

You’ll first install a http request module, such as axios. npm install axios

const axios = require('axios');
const url = 'https://api.github.com/users';

axios.get(url).then(res => {
    // do something with the response
}).catch(err => {
    // do something with the error
})

This piece of code can either be in your service, or anywhere you like.

In Go, however, the [net/http](https://golang.org/pkg/net/http/ "net/http") package can help us with this scenario.

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

func main() {
    URL := "https://api.github.com/users"
    res, err := http.Get(URL)
    if err != nil {
        log.Println(err)
        return
    }
    defer res.Body.Close() // for garbage collection

    responseBodyBytes, err := ioutil.ReadAll(res.Body)
    if err != nil {
        log.Println(err)
        return
    }

    fmt.Println(string(responseBodyBytes))
}

We use http.Get to make requests, and check errors. The response is usually in bytes, and we have to read it first, then convert it to string with string([]bytes).

You can add this to a main.go file and run go run main.go.

This code can also easily be converted to a func, and called repeatedly when needed.

Here’e an example showing various ways to use the http package from the net/http documentation page.

resp, err := http.Get("http://example.com/")
...
resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
...
resp, err := http.PostForm("http://example.com/form",
    url.Values{"key": {"Value"}, "id": {"123"}})

Database Connections

NodeJS has various npm modules to help in database connections depending on the databases that you are using.

These libraries most of the time come with their own methods, that try to make it easier for you to work with them, and some even offer ORM like features.

Go however takes a different approach. The standard library provides an interface for working with databases called https://golang.org/pkg/database/sql/ which RDBMS package authors can use to implement drivers for various databases. Authors following the standard interface ensure greater interoperability for their packages.

Common database driver packages include:

  • Why Go
  • Hello World
  • Data Types, Variables and Constants
  • Conditional Statements
  • Loops
  • Objects
  • Http Server
  • Middleware
  • Consuming APIs
  • Database Connections
  • Frameworks
  • Conclusion
Frameworks

We all know how JavaScript has a lot of frameworks and libraries, and we use them ocassionally to avoid reinventing the wheel.

The Go community however prefers using libraries instead of frameworks, so you will rarely find a team commiting to a particular framework when building their applications.

That being said, there is one Framework I’d recommend, since it is built by combining a lot of the commonly used packages, and file structures.

  • Why Go
  • Hello World
  • Data Types, Variables and Constants
  • Conditional Statements
  • Loops
  • Objects
  • Http Server
  • Middleware
  • Consuming APIs
  • Database Connections
  • Frameworks
  • Conclusion
Conclusion

Go’s files must be written within packages, and this usually affects your file structure.

In JavaScript, you’ll see a lot of require statements at the beginning of files, while in Golang, the first line is always a package name, which will then be used as an import path in a file where it’s required import package_path/package_name

I hope you’ve gotten a gist of what it’s like to write Go, and you’d like to get into action. Golang is really praised for it’s concurrency and performance, and if you are building a large application, this would be a preferred choice.

I’ve been following this transitional journey for a while now, and would gladly answer any questions you may have. Just leave a comment.

Thanks for reading

If you liked this post, please do share/like it with all of your programming buddies!

Originally published by Chris Ganga at  scotch.io

Follow us on Facebook | Twitter

Further reading about JavaScript

The Complete JavaScript Course 2019: Build Real Projects!

Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

JavaScript Bootcamp - Build Real World Applications

The Web Developer Bootcamp

JavaScript Programming Tutorial - Full JavaScript Course for Beginners

New ES2019 Features Every JavaScript Developer Should Know

Best JavaScript Frameworks, Libraries and Tools to Use in 2019

What JavaScript Framework You Should Learn to Get a Job in 2019?

Best JavaScript Frameworks, Libraries and Tools to Use in 2019

Microfrontends — Connecting JavaScript frameworks together (React, Angular, Vue etc)

Ember.js vs Vue.js - Which is JavaScript Framework Works Better for You

Do we still need JavaScript frameworks?