Learning Go as a Node.js Developer

In the past years, Kubernetes emerged as the go-to container scheduling and management platform. As I'd like to understand better what's happening under the hood, I decided that I'll learn the Go language.

In this article, I'll summarize my learnings from a Node.js developer's point of view. I'll pay particular attention to:

dependency management,

how asynchronous operations are handled.

Let's get to it :)

Go is an open source programming language that makes it easy to build simple, reliable, and efficient software. - golang.org

Go was created in 2009 at Google by Robert Griesemer, Rob Pike, and Ken Thompson. It is a compiled language, which comes with static types. It has a garbage collector and handles asynchronous operations in a CSP style. Go has a C-like syntax:

package main import "fmt" func main ( ) { fmt . Println ( "hello world" ) }

Installing Go

To install Go, follow the official setup guide: https://golang.org/doc/install.

Dependency management in Go

As someone who writes a considerable amount of JavaScript code, one of the first questions I had was how does Go handle dependency management? It seems two solutions have emerged:

go get ,

, dep.

go get is shipped with the Go language itself, while dep is a dependency manager for Go. In npm terms, you can think of them like this: use go get whenever you'd use npm install -g , and use dep for project specific dependencies.

To get dep , you could use go get to install it, using:

go get - u github . com / golang / dep / cmd / dep

However, there’s a drawback to using go get - it does not handle versions, it just grabs the head of the provided GitHub repository. This is why it is recommended that you install dep using these commands:

brew install dep brew upgrade dep

(you can read instructions for different OSs here: Go install guide)

Once you have dep installed, you can create projects using dep init , just like you would do it with npm init .

Before start developing with Go, take some time to set up the GOPATH environment variable correctly - you can use the official Go install guide as a reference.

dep will create similar files that npm would - Gopkg.toml to describe the project, just like package.json in the Node.js ecosystem. The analogy for package-lock.json is Gopkg.lock , while instead of using the node_modules folder, dep puts dependencies into the vendor folder.

To add dependencies, you only have to run dep ensure -add github.com/pkg/errors . After running it, it will appear both in your lock and your toml file:

[ [ constraint ] ] name = "github.com/pkg/errors" version = "0.8.0"

Handling asynchronous operations in Go

When writing asynchronous JavaScript, we are used to some libraries/language features, like:

the async library,

Promises,

or async functions .

Using them, we can easily read files from the file system:

const fs = require ( "fs" ) ; const async = require ( "async" ) ; const filesToRead = [ "file1" , "file2" ] ; async . map ( filesToRead , ( filePath , callback ) => { fs . readFile ( filePath , "utf-8" , callback ) ; } , ( err , results ) => { if ( err ) { return console . log ( err ) ; } console . log ( results ) ; } ) ;

Let's see what it looks like in Go!

package main import ( "fmt" "io/ioutil" ) func main ( ) { datFile1 , errFile1 : = ioutil . ReadFile ( "file1" ) if errFile1 != nil { panic ( errFile1 ) } datFile2 , errFile2 : = ioutil . ReadFile ( "file2" ) if errFile2 != nil { panic ( errFile2 ) } fmt . Print ( string ( datFile1 ) , string ( datFile2 ) ) }

Let's see what happened in the code snippet above line by line:

import - with import, you can require packages your applications are built upon, just like require in Node.js,

- with import, you can require packages your applications are built upon, just like in Node.js, func main - the entry point of our application,

- the entry point of our application, ioutil.ReadFile - tries to read the file, and returns two values: datFile1 if the operations were successful, errFile1 if there was some error, this is your chance to handle the error, or crash the process,

- tries to read the file, and returns two values: fmt.Print prints merely the results to the standard output.

The example above works, but reads the files one after each other - time to go asynchronous!

For handling threads, Go has a concept called goroutines. A goroutine is a lightweight thread managed by the Go runtime - it enables you to run Go functions concurrently.

To manage/synchronize goroutines, I ended up using the errgroup package. This package provides synchronization, error propagation, and Context cancellation for groups of goroutines working on subtasks of a common task.

Using errgroup , we can rewrite the file reading code snippet to run concurrently this way:

package main import ( "context" "fmt" "io/ioutil" "os" "golang.org/x/sync/errgroup" ) func readFiles ( ctx context . Context , files [ ] string ) ( [ ] string , error ) { g , ctx : = errgroup . WithContext ( ctx ) results : = make ( [ ] string , len ( files ) ) for i , file : = range files { i , file : = i , file g . Go ( func ( ) error { data , err : = ioutil . ReadFile ( file ) if err == nil { results [ i ] = string ( data ) } return err } ) } if err : = g . Wait ( ) ; err != nil { return nil , err } return results , nil } func main ( ) { var files = [ ] string { "file1" , "file2" , } results , err : = readFiles ( context . Background ( ) , files ) if err != nil { fmt . Fprintln ( os . Stderr , err ) return } for _ , result : = range results { fmt . Println ( result ) } }

Building a REST API in Go

In the Node.js space we have a ton of options when it comes to picking a framework for writing HTTP services - Go is no different. After some searching on Google, I chose Gin to start with.

Its interface is similar to Express or Koa, including middleware support, JSON validation and rendering:

package main import "github.com/gin-gonic/gin" func main ( ) { r : = gin . New ( ) r . Use ( gin . Logger ( ) ) r . Use ( gin . Recovery ( ) ) r . GET ( "/ping" , func ( c * gin . Context ) { c . JSON ( 200 , gin . H { "message" : "pong" , } ) } ) r . Run ( ":8080" ) }

That's how far I've got for now - no production experience, yet. If you feel like that this blog post was useful, and you'd like to learn more on Go, just let me know, and I'll keep publishing what I learn along my journey tackling the Go language.

Further resources

The above content would not have been possible without following articles: