1. Code
  2. Coding Fundamentals

12 Indispensable Go Packages and Libraries

Go is an amazing language with a lot of momentum, and it's focused on simplicity. In this tutorial, you'll discover 12 of Go's best packages and libraries.
Scroll to top

Go is an amazing language with a lot of momentum, and it's focused on simplicity. This approach is evident in its standard library, which provides all the essentials, but not much more. 

Fortunately, Go has a vibrant community that creates and shares a lot of third-party libraries. In this tutorial, I'll introduce you to 12 of Go's best packages and libraries. Some of them have relatively narrow scope and can be added to any projects, while others are huge projects that you can incorporate into massive, large-scale distributed systems.

Awesome Go

Before diving into the libraries themselves, let me introduce you to Awesome Go, a very active and curated list of Go libraries and other resources. You should visit every now and then and check what's new.

1. Golang-Set

Go has arrays, slices and maps, but it doesn't have a set data structure. You can mimic a set with a map of bools, but it's nice to have an actual data type with the right operations and semantics. This is where golang-set comes in. Here is a basic example of creating a new set, adding items and testing for membership:

1
package main
2
3
import (
4
    "fmt"
5
	"github.com/deckarep/golang-set"
6
)
7
8
9
func main() {
10
	basicColors := mapset.NewSet()
11
	basicColors.Add("Red")
12
	basicColors.Add("Blue")
13
	basicColors.Add("Green")
14
15
	if basicColors.Contains("Green") {
16
		fmt.Println("Yay! 'Green' is a basic color")
17
	} else {
18
		fmt.Println("What a disappointment! 'Green' is not a basic color")
19
	}
20
21
22
	if basicColors.Contains("Yellow") {
23
		fmt.Println("Yay! 'Yellow' is a basic color")
24
	} else {
25
		fmt.Println("What a disappointment! 'Yellow' is not a basic color")
26
	}
27
}
28
29
Output:
30
31
Yay! 'Green' is a basic color
32
What a disappointment! 'Yellow' is not a basic color

Note that the package name is "mapset". In addition to the basics, you perform all set operations like union, intersection, and difference. You can also iterate over the set values: 

1
package main
2
3
import (
4
    "fmt"
5
	"github.com/deckarep/golang-set"
6
)
7
8
9
func main() {
10
	basicColors := mapset.NewSet()
11
	basicColors.Add("Red")
12
	basicColors.Add("Blue")
13
	basicColors.Add("Green")
14
15
	otherColors := mapset.NewSetFromSlice([]interface{}{"Orange", "Yellow", "Indigo", "Violet"})
16
	rainbowColors := basicColors.Union(otherColors)
17
18
	for color := range rainbowColors.Iterator().C {
19
		fmt.Println(color)
20
	}
21
}

2. Color

Let's continue with the color theme. When writing command-line programs, it is useful to use colors to highlight important messages or distinguish between errors, successes, and warnings. 

The color package gives an easy way to add some color to your programs (see what I did there?). It uses ANSII escape codes and supports Windows too! Here is a quick example:

1
package main
2
3
import (
4
    "github.com/fatih/color"
5
)
6
7
func main() {
8
	color.Red("Roses are red")
9
	color.Blue("Violets are blue")
10
}

The color package supports mixing colors with background colors, styles like bold or italic, and sprinkling color with non-color output.

1
package main
2
3
import (
4
    "github.com/fatih/color"
5
	"fmt"
6
)
7
8
func main() {
9
	minion := color.New(color.FgBlack).Add(color.BgYellow).Add(color.Bold)
10
	minion.Println("Minion says: banana!!!!!!")
11
12
	m := minion.PrintlnFunc()
13
	m("I want another banana!!!!!")
14
15
	slantedRed := color.New(color.FgRed, color.BgWhite, color.Italic).SprintFunc()
16
	fmt.Println("I've made a huge", slantedRed("mistake"))
17
}

The color package has other useful features. Go ahead and explore more.

3. Now

Now is a very simple package that provides a convenience wrapper for the standard time package and makes it easy to work with various date and time constructs around the current time. 

For example, you can get the beginning of the current minute or the end of the Sunday closest to the current time. Here is how to use "now":

1
package main
2
3
import (
4
    "github.com/jinzhu/now"
5
	"fmt"
6
)
7
8
func main() {
9
10
	fmt.Println("All the beginnings...")
11
	fmt.Println(now.BeginningOfMinute())
12
	fmt.Println(now.BeginningOfHour())
13
	fmt.Println(now.BeginningOfDay())
14
	fmt.Println(now.BeginningOfWeek())
15
	fmt.Println(now.BeginningOfMonth())
16
	fmt.Println(now.BeginningOfQuarter())
17
	fmt.Println(now.BeginningOfYear())
18
19
}
20
21
Output:
22
23
All the beginnings...
24
2017-06-04 16:59:00 -0700 PDT
25
2017-06-04 16:00:00 -0700 PDT
26
2017-06-04 00:00:00 -0700 PDT
27
2017-06-04 00:00:00 -0700 PDT
28
2017-06-01 00:00:00 -0700 PDT
29
2017-04-01 00:00:00 -0700 PDT
30
2016-12-31 23:00:00 -0800 PST

You can also parse times and even add your own formats (which will require updating the known formats). The Now type embeds time.Time, so you can use all of the time.Time methods directly on Now objects.

4. Gen

The gen tool generates code for you—in particular, type-aware code that tries to alleviate the gap of not having templates or generics in Go.

You annotate your types with a special comment, and gen generates source files that you include in your project. No runtime magic. Let's see an example. Here is an annotated type.

1
// +gen slice:"Where,Count,GroupBy[int]"
2
type Person struct {
3
    Name string
4
	Age int
5
}

Running gen (make sure it's in your path) generates person_slice.go:

1
// Generated by: gen
2
// TypeWriter: slice
3
// Directive: +gen on Person
4
5
package main
6
7
// PersonSlice is a slice of type Person. Use it where you would use []Person.
8
type PersonSlice []Person
9
10
// Where returns a new PersonSlice whose elements return true for func. See: https://clipperhouse.github.io/gen/#Where
11
func (rcv PersonSlice) Where(fn func(Person) bool) (result PersonSlice) {
12
    for _, v := range rcv {
13
		if fn(v) {
14
			result = append(result, v)
15
		}
16
	}
17
	return result
18
}
19
20
// Count gives the number elements of PersonSlice that return true for the passed func. See: http://clipperhouse.github.io/gen/#Count
21
func (rcv PersonSlice) Count(fn func(Person) bool) (result int) {
22
	for _, v := range rcv {
23
		if fn(v) {
24
			result++
25
		}
26
	}
27
	return
28
}
29
30
// GroupByInt groups elements into a map keyed by int. See: http://clipperhouse.github.io/gen/#GroupBy
31
func (rcv PersonSlice) GroupByInt(fn func(Person) int) map[int]PersonSlice {
32
	result := make(map[int]PersonSlice)
33
	for _, v := range rcv {
34
		key := fn(v)
35
		result[key] = append(result[key], v)
36
	}
37
	return result
38
}

The code provides LINQ-like methods to operate on the PersonSlice type. It's simple to understand and nicely documented. 

Here is how you use it. In the main function, a PersonSlice is defined. The age() function selects the age field from its Person argument. The generated GroupByInt() function takes the age() function and returns the people from the slice grouped by their age (34 is just Jim, but 23 has both Jane and Kyle).

1
package main
2
3
import (
4
    "fmt"
5
)
6
7
// +gen slice:"Where,Count,GroupBy[int]"
8
type Person struct {
9
	Name string
10
	Age int
11
}
12
13
func age(p Person) int {
14
	return p.Age
15
}
16
17
func main() {
18
	people := PersonSlice {
19
		{"Jim", 34},
20
		{"Jane", 23},
21
		{"Kyle", 23},
22
	}
23
24
	groupedByAge := people.GroupByInt(age)
25
26
	fmt.Println(groupedByAge)
27
}
28
29
30
Output:
31
32
map[34:[{Jim 34}] 23:[{Jane 23} {Kyle 23}]]

5. Gorm

Go is known for its spartan nature. Database programming is no different. Most popular DB libraries for Go are pretty low-level. Gorm brings the world of object-relational mapping to Go with the following features:

  • Associations (Has One, Has Many, Belongs To, Many To Many, Polymorphism)
  • Callbacks (Before/After Create/Save/Update/Delete/Find)
  • Preloading (eager loading)
  • Transactions
  • Composite Primary Key
  • SQL Builder
  • Auto Migrations
  • Logger
  • Extendable, write Plugins based on GORM callbacks

But it doesn't cover everything. If you come from Python, don't expect SQLAlchemy magic. For more fancy stuff, you'll have to go a lower level. Here is an example of how to use Gorm with sqlite. Note the embedded gorm.Model in the Product struct.

1
package main
2
3
import (
4
    "github.com/jinzhu/gorm"
5
    _ "github.com/jinzhu/gorm/dialects/sqlite"
6
)
7
8
type Product struct {
9
  gorm.Model
10
  Code string
11
  Price uint
12
}
13
14
func main() {
15
  db, err := gorm.Open("sqlite3", "test.db")
16
  if err != nil {
17
    panic("failed to connect database")
18
  }
19
  defer db.Close()
20
21
  // Migrate the schema
22
  db.AutoMigrate(&Product{})
23
24
  // Create
25
  db.Create(&Product{Code: "L1212", Price: 1000})
26
27
  // Read
28
  var product Product
29
  db.First(&product, 1) // find product with id 1
30
  db.First(&product, "code = ?", "L1212")
31
32
  // Update - update product's price to 2000
33
  db.Model(&product).Update("Price", 2000)
34
35
  // Delete - delete product
36
  db.Delete(&product)

6. Goose

One of the most important tasks when working with relational databases is managing the schema. Modifying the DB schema is considered a "scary" change in some organizations. The goose package lets you perform schema changes and even data migrations if needed. You can goose up and goose down to go back and forth. Mind your data, though, and make sure it doesn't get lost or corrupted.

Goose works by versioning your schema and using migration files corresponding to each schema. The migration files can be SQL commands or Go commands. Here is an example of a SQL migration file that adds a new table:

1
-- +goose Up
2
CREATE TABLE person (
3
    id int NOT NULL,
4
    name text,
5
    age int,
6
    PRIMARY KEY(id)
7
);
8
9
-- +goose Down
10
DROP TABLE person;

The -- +goose up and -- +goose down comments tell goose what to do to upgrade or downgrade the schema.

7. Glide

Glide is a package manager for Go. Under a single GOPATH, you may have many programs that have conflicting dependencies. The solution is to have each program manage its own vendor directory of package dependencies. Glide helps with this task.

Here are the features of glide:

  • Support versioning packages including Semantic Versioning 2.0.0 support.
  • Support aliasing packages (e.g. for working with github forks).
  • Remove the need for munging import statements.
  • Work with all of the go tools.
  • Support all the VCS tools that Go supports (git, bzr, hg, svn).
  • Support custom local and global plugins.
  • Repository caching and data caching for improved performance.
  • Flatten dependencies, resolving version differences and avoiding the inclusion of a package multiple times.
  • Manage and install dependencies on-demand or vendored in your version control system. 

The dependencies are stored in glide.yaml, and glide provides several commands to manage dependencies:

1
     create, init       Initialize a new project, creating a 
2
                        glide.yaml file
3
     config-wizard, cw  Wizard that makes optional suggestions 
4
                        to improve config in a glide.yaml file.
5
     get                Install one or more packages into 
6
                        `vendor/` and add dependency to 
7
                        glide.yaml.
8
     remove, rm         Remove a package from the glide.yaml 
9
                        file, and regenerate the lock file.
10
     import             Import files from other dependency 
11
                        management systems.
12
     name               Print the name of this project.
13
     novendor, nv       List all non-vendor paths in a 
14
                        directory.
15
     rebuild            Rebuild ('go build') the dependencies
16
     install, i         Install a project's dependencies
17
     update, up         Update a project's dependencies
18
     tree               (Deprecated) Tree prints the 
19
                        dependencies of this project as a tree.
20
     list               List prints all dependencies that the 
21
                        present code references.
22
     info               Info prints information about this 
23
                        project
24
     cache-clear, cc    Clears the Glide cache.
25
     about              Learn about Glide
26
     mirror             Manage mirrors
27
     help, h            Shows a list of commands or help for 
28
                        one command

8. Ginkgo

Ginkgo is a BDD (Behavior Driven Development) testing framework. It lets you write your tests in a syntax that resembles English and allow less technical people to review tests (and their output) and verify that they match the business requirements. 

Some developers like this style of test specification too. It integrates with Go's built-in testing package and is often combined with Gomega. Here is an example of a Ginkgo + Gomega test:

1
actual, err := foo()
2
Ω(err).Should(BeNil())
3
Ω(actual).ShouldNot(BeNil())
4
Ω(actual.result).Should(Equal(100))

9. Etcd

Etcd is a reliable distributed Key-Value store. The server is implemented in Go, and the Go client interacts with it though gRPC.

It focuses on the following:

  • Simple: well-defined, user-facing API (gRPC).
  • Secure: automatic TLS with optional client cert authentication.
  • Fast: benchmarked 10,000 writes/sec.
  • Reliable: properly distributed using Raft.

Here is an example of connecting to the server, putting a value and getting it, including timeouts and cleanup.

1
func test_get() {
2
    cli, err := clientv3.New(clientv3.Config{
3
		Endpoints:   endpoints,
4
		DialTimeout: dialTimeout,
5
	})
6
	if err != nil {
7
		log.Fatal(err)
8
	}
9
	defer cli.Close()
10
11
	_, err = cli.Put(context.TODO(), "foo", "bar")
12
	if err != nil {
13
		log.Fatal(err)
14
	}
15
16
	ctx, cancel := context.WithTimeout(context.Background(), 
17
                                       requestTimeout)
18
	resp, err := cli.Get(ctx, "foo")
19
	cancel()
20
	if err != nil {
21
		log.Fatal(err)
22
	}
23
	for _, ev := range resp.Kvs {
24
		fmt.Printf("%s : %s\n", ev.Key, ev.Value)
25
	}
26
	// Output: foo : bar
27
}

10. NSQ

NSQ is a great distributed queue. I've used it successfully as a primary building block for large-scale distributed systems. Here are some of its features:

  • Support distributed topologies with no SPOF.
  • Horizontally scalable (no brokers, seamlessly add more nodes to the cluster).
  • Low-latency push based message delivery (performance).
  • Combination load-balanced and multicast style message routing.
  • Excel at both streaming (high-throughput) and job oriented (low-throughput) workloads.
  • Primarily in-memory (beyond a high-water mark messages are transparently kept on disk).
  • Runtime discovery service for consumers to find producers (nsqlookupd).
  • Transport layer security (TLS).
  • Data format agnostic.
  • Few dependencies (easy to deploy) and a sane, bounded, default configuration.
  • Simple TCP protocol supporting client libraries in any language.
  • HTTP interface for stats, admin actions, and producers (no client library needed to publish).
  • Integrates with statsd for real-time instrumentation.
  • Robust cluster administration interface (nsqadmin).

Here is how to publish a message to NSQ (error handling is elided):

1
package main
2
3
import (
4
  "github.com/bitly/go-nsq"
5
)
6
7
func main() {
8
  config := nsq.NewConfig()
9
  p, _ := nsq.NewProducer("127.0.0.1:4150", config)
10
11
  p.Publish("topic", []byte("message"))
12
  p.Stop()
13
}

And here is how to consume:

1
package main
2
3
import (
4
  "sync"
5
  "fmt"
6
  "github.com/bitly/go-nsq"
7
)
8
9
func main() {
10
  wg := &sync.WaitGroup{}
11
  wg.Add(1)
12
13
  config := nsq.NewConfig()
14
  q, _ := nsq.NewConsumer("topic", "channel", config)
15
  handler := nsq.HandlerFunc(func(message *nsq.Message) error {
16
      fmt.Printf("Got a message: %v", message)
17
      wg.Done()
18
      return nil
19
  })
20
  q.AddHandler(handler)
21
  q.ConnectToNSQD("127.0.0.1:4150")
22
  wg.Wait()
23
}

11. Docker

Docker is a household name now (if your family members are mostly DevOps people). You may not be aware that Docker is implemented in Go. You don't  typically use Docker in your code, but it is a significant project and deserves to be recognized as a hugely successful and popular Go project.

12. Kubernetes

Kubernetes is an open-source container orchestration platform for cloud-native applications. It is another monster distributed system implemented in Go. I recently wrote a book called Mastering Kubernetes where I go in detail over the most advanced aspects of Kubernetes. From the Go developer's point of view, Kubernetes is very flexible, and you can extend and customize it via plugins.

Conclusion

Go is a great language. Its design philosophy is to be a simple and approachable language. Its standard library is not as comprehensive as some other languages like Python. 

The Go community stepped up, and there are many high-quality libraries you can use in your programs. In this article, I introduced 12 libraries. I encourage you to look for other libraries before jumping in and implementing everything from scratch.