Coder Social home page Coder Social logo

boardgameatlas's Introduction

GeeksHacking Go Workshop

This is a Go workshop organised by GeeksHacking, and conducted by Chuk Munn Lee on 3 September 2022. This is also the first workshop by GeeksHacking in 2-3 years since COVID19!

Notes are taken by @lyqht.


Table of contents


Slides

Workshop slides can be found here. A PDF copy is also available in this repository.

Setup Go instructions

  1. Follow the instructions in this doc to setup Go & BoardGameAtlas API Client

  2. If you are able to download repositories but cannot see them in your src folder, you can try running

     go env -w GO111MODULE=off

    And if you are unable to run go mod init main later on, you have to switch this back to auto.

     go env -w GO111MODULE=auto

    You can refer to this documnentation to understand more about what is GO111MODULE.

Creating the Boardgame Atlas CLI app

Learn to run and build a simple Go module

  1. Create a empty folder anywhere you like e.g. bga, then cd bga.

  2. Run mod init main. You will see a new go.mod file generated in your project.

  3. Create a main.go file. In this file, enter this code.

    package main
    
    import (
      "flag"
      "fmt"
    )
    
    func main() {
      query := flag.String("query", "", "Board game name to search")
      clientId := flag.String("clientId", "", "Board Atlas Client ID")
      skip := flag.Uint("skip", 0, "Skips the number of results provided")
      limit := flag.Uint("limit", 0, "Limits the number of results provided")
    
      flag.Parse()
      fmt.Printf("query=%s, clientId=%s, limit=%d, skip=%d\n", *query, *clientId, *limit, *skip)
    }
  4. You can run this file directly with flags as defined in your program.

     go run . --limit=5 --skip=1 --query=catan --clientId=abc123
  5. To build the program as an executable, run

    go build .

    You should see a new main file generated without any file extension.

  6. Then you can start the program by running

    ./main

Add an API service call

  1. Next, create a new folder api, and create a new file search.go in it with the following code. We are using the object pattern to declare a class like, and the constructor method for it.

    package api
    
    type BoardgameAtlas struct {
      clientId string
    }
    
    // Class-like Constructor method in Go
    func New(clientId string) BoardgameAtlas {
      return BoardgameAtlas{clientId}
    }
  2. Then at the main.go file, we can add code to instantiate a BGA client in the main() function.

    import (
      //...
      "main/api"
    )
    func main() {  
      //...
      fmt.Printf("query=%s, clientId=%s, limit=%d, skip=%d\n", *query, *clientId, *limit, *skip)
    
      bga := api.New(*clientId)
    }
  3. We will add a search function to the search module. In doing so, we will add additional library imports, a constant API endpoint URL and the function that uses them.

    package api
    
    import (
      "context"
      "fmt"
      "net/http"
    )
    
    const SEARCH_URL = "https://api.boardgameatlas.com/api/search"
    
    func (b BoardgameAtlas) Search(ctx context.Context, query string, limit uint, skip uint) error {
      req, err := http.NewRequestWithContext(ctx, http.MethodGet, SEARCH_URL, nil)
      if nil != err {
        return fmt.Errorf("Cannot create HTTP Client: $v", err)
      }
    
      return nil // we are not returning the response yet
    }
  4. In Go, it is not straight forward to create a HTTP request to be made โ€” we need to create a Query instance and add query parameters to it, before encoding it and calling it with a http default client.

    func (b BoardgameAtlas) Search(ctx context.Context, query string, limit uint, skip uint) error {
      req, err := http.NewRequestWithContext(ctx, http.MethodGet, SEARCH_URL, nil)
      if nil != err {
        return fmt.Errorf("Cannot create HTTP Client: $v", err)
      }
    
      qs := req.URL.Query()
      qs.Add("name", query)
      qs.Add("limit", fmt.Sprintf("%d", limit))
      qs.Add("skip", strconv.Itoa(int(skip))) // you can use fmt.sprintf as well, either casting method works.
      qs.Add("client_id", b.clientId)
    
      // encode query params and add it back to the request
      req.URL.RawQuery = qs.Encode()
    
      fmt.Printf("URL - %s\n", req.URL.String())
    
      resp, err := http.DefaultClient.Do(req)
      if nil != err {
        return fmt.Errorf("cannot create HTTP Client: %v", err)
      }
    
      if resp.StatusCode >= 400 {
        return fmt.Errorf("error HTTP status: %s", resp.Status)
      }
    
      return nil // we are not returning the response yet
    }
  5. Then we can call the search function at main.go's main function. You should be able to see the generated URL that the http request will be made to in your console.

    bga.Search(context.Background(), *query, *limit, *skip)
  6. Now for us to see the API response, we need to deserialize the response JSON before returning it from the search function. We need to create new class likes, for it to map from the response json fields.

     type Game struct {
       Id            string `json:"id"`
       Name          string `json:"name"`
       Price         string `json:"price"`
       YearPublished uint   `json:"year_published"`
       Description   string `json:"description"`
       ImageUrl      string `json:"image_url"`
       RulesUrl      string `json:"rules_url"`
     }
    
     type SearchResult struct {
       Games []Game `json:"games"`
       Count uint   `json:"count"`
     }
    
     func (b BoardgameAtlas) Search(ctx context.Context, query string, limit uint, skip uint) (*SearchResult, error) {
       // ...
       if resp.StatusCode >= 400 {
         return nil, fmt.Errorf("error HTTP status: %s", resp.Status)
       }
    
       var result SearchResult
       if err := json.NewDecoder(resp.Body).Decode(&result); nil != err {
         return nil, fmt.Errorf("cannot deserialize JSON payload: %v", err)
       }
    
       return &result, nil
     }
  7. Then at main.go we can grab the result and display some of the fields in our console.

     result, err := bga.Search(ctx, *query, *limit, *skip)
     if nil != err {
       log.Fatalf("Cannot search foxwr boardgame: %v", err)
     }
    
     for _, game := range result.Games {
       fmt.Printf("Name: %s\n", game.Name)
       fmt.Printf("Description: %s\n", game.Description)
       fmt.Printf("RulesUrl: %s\n", game.RulesUrl)
     }
  8. Run go run --query=memoir --clientId=abc123 --limit=10 to see some results!

    Make sure to use a valid clientId.

Setting Terminal color

We can import the color library by following the instructions of the repo.

Then at main.go, we just need to import the color library and change the printing of the game details to something like the code below which turns the game field labels to green.

import (
  //...
  "github.com/fatih/color"
)

func main() {
  //...
  boldGreen := color.New(color.Bold).Add(color.FgHiGreen).SprintFunc()

  for _, game := range result.Games {
    fmt.Printf("%s: %s\n", boldGreen("Name"), game.Name)
    fmt.Printf("%s: %s\n", boldGreen("Description"), game.Description)
    fmt.Printf("%s: %s\n", boldGreen("RulesUrl"), game.RulesUrl)
  }
}

preview of green colored labels

Resources on Go Concepts

Here are some concepts mentioned in the presentation, supplemented with resources. Go's design & syntax is pretty similar to a mixture of C + Typescript.

Assigning & getting values

name:= "fred" // assign content to variable
&name // a pointer/ memory address of variable
*&name // content of memory address

Custom Types

Golang Custom Type Declarations: The Complete Guide

Class Like

Structs instead of classes

Goroutines

Goroutine definition and examples

Creating Go Modules

How go mod init works

Random nuggets

  • Uppercase variables are public, lowercase is private.
  • Declaring function inputs and outputs is similar to Typescript, but without :.
  • In JavaScript, you have to install formatters like prettier or eslint to automatically remove unused imports or variables. But in Go, these will be removed by default if you have the Go extension installed in your Visual Studio Code.
  • nil in Go === null in JS
  • Go doesn't throw exceptions, need check manually if there is error and the error code directly.
  • GoLang cannot understand JSON directly
  • Why we return a pointer instead of the object directly
    • In JS/Typescript, Java, objects and arrays are references.
    • In Go, if we set the return type to be SearchResult instead of *SearchResult, then we cannot return nil if there is an error. We would need to create an empty SearchResult object. This is wasted memory.

boardgameatlas's People

Contributors

lyqht avatar chukmunnlee avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.