5 Go Libraries I Use in Nearly Every Project
I’ve noticed that these libraries always seem to be the first ones I reach for when starting a new project. They’ve become my go-to tools for getting things up and running quickly.
In Go, libraries are like a treasure trove of valuable resources that can help developers create better and faster applications.
I’ve been fortunate enough to try out a bunch of libraries throughout my career, and I can attest to the fact that picking the right ones can truly be a “game changer” to eliminate redundant code, and improve the overall quality of my services.
1. golang-module/carbon
Working with time in Go can be quite a challenge, especially when relying solely on the built-in time.Time package, for example:
How can you specify the year or month of a time.Time value in Go?
In the past, I often found myself grappling with complicated code when trying to manipulate time values:
t := time.Now() // get the current time
// change the year to 2020
newTime := time.Date(2020, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
This is precisely where Carbon comes in, providing an elegant and efficient solution that can be implemented with just a short line:
t := carbon.Now().SetYear(2020)
“That’s it?”
Carbon offers a wealth of useful utilities that make working with time in Go much more manageable, allowing you to avoid the frustration and hassle of handling time.Time objects directly.
To give you an idea of how carbon
works in practice, here are a few examples I’ve taken from their GitHub documentation that I think you’ll find helpful:
// Whether is now time
carbon.Now().IsNow() // true
// Whether is future time
carbon.Tomorrow().IsFuture() // true
// Whether is pass time
carbon.Yesterday().IsPast() // true
// Whether is January
carbon.Parse("2020-08-05 13:14:15").IsJanuary() // false
// Whether is Monday
carbon.Parse("2020-08-05 13:14:15").IsMonday() // false
carbon.Parse("2020-08-05 13:14:15").DiffForHumans() // just now
carbon.Parse("2022-08-05 13:14:15").DiffForHumans() // 2 years from now
2. samber/lo
If you find yourself frequently working with slices, arrays, or maps in your Go projects, you may want to check out the Samber/lo library.
This library is inspired by Lodash and built using the new Go 1.18+ Generics feature, providing a wide range of helpful utilities such as filter, map, reduce, count, and flatten to simplify complex tasks and make your code more efficient.
As an example, you can use the Samber/lo library to rewrite Python’s range function in a Go-style using its utilities.
result := lo.Range(4)
// [0, 1, 2, 3]
result := lo.RangeFrom(1, 5)
// [1, 2, 3, 4, 5]
As with the previous section, I’ll provide a few examples taken from their GitHub documentation so you can get a better sense of what samber/lo has to offer:
// Find unique values
names := lo.Uniq[string]([]string{"Samuel", "John", "Samuel"})
// []string{"Samuel", "John"}
// Filter even number
even := lo.Filter[int]([]int{1, 2, 3, 4}, func(x int, index int) bool {
return x%2 == 0
})
// []int{2, 4}
// Reverse the slice
reverseOrder := lo.Reverse[int]([]int{0, 1, 2, 3, 4, 5})
// []int{5, 4, 3, 2, 1, 0}
Not only does samber/lo provide useful utilities for dealing with slices, arrays, and maps, but it also includes some handy functions that I find myself using quite often.
For instance, there’s a function to generate a random string and another to check whether a struct has been initialized:
// ---- RandomString
str := lo.RandomString(5, lo.LettersCharset)
// "eIGbt"
// ---- IsNotEmpty
type test struct {
foorbar string
}
lo.IsNotEmpty[test](test{foobar: ""})
// false
lo.IsNotEmpty[test](test{foobar: "foobar"})
// true
3. zerolog
When working on a large project, it’s important to structure our logs for readability, query-ability, and speed. If you’re not familiar with structured logs, check out this post: What Are Structured Logs and How Do They Improve Performance?
“Why have you chosen not to use other structured log libraries like logrus or zap?”
Zerolog is a logging library that is both highly performant and incredibly easy to use.
If you’re overwhelmed by the many different options out there, don’t waste time, just choose one at random and start experimenting with it through trial and error.
In addition to its impressive performance, Zerolog also offers a wide range of useful utilities.
For example, you can use Zerolog to log the position where a log event was emitted, making it easy to track down bugs and issues in your code:
log.Logger = log.With().Caller().Logger()
log.Info().Msg("hello world")
// Output: {"level": "info", "message": "hello world",
// "caller": "/go/src/your_project/some_file:21"}
4. jinzhu/copier
As for copying or deep cloning, Copier is a handy tool that always comes to my mind.
It’s particularly useful when dealing with complex data structures or nested structs, where manually copying data can be a tedious and error-prone task.
copier.CopyWithOption(&to, &from, copier.Option{IgnoreEmpty: true, DeepCopy: true})
As an example, let’s say we have two models: User, which contains all the user’s information, and SimpleUser, which doesn’t include their email and password:
type User struct {
Name string
Age int
Email string
Password string
}
type SimpleUser struct {
Name string
Age int
}
Suppose I need to retrieve user information from a database, process it, and return a modified version of the user object that doesn’t contain sensitive information such as email and password to avoid any security breaches:
// --- Mockup
func getUserFromDB() User {
return User{
Name: "Aiden", Age: 30,
Email: "aidenlee@gmail.com", Password: "thisisasecret"}
}
// ---
func Sample() (s SimpleUser, err error) {
user := getUserFromDB()
// ... doing thing with user then return simpleUser
err = copier.Copy(&s, &user);
return s, err
}
When it comes to creating deep copies of structs, copier is a handy tool to have in your toolkit (it may not work with non-exported fields):
func Clone[T any](input T) (*T, error) {
var output = new(T)
err := copier.CopyWithOption(output, input, copier.Option{DeepCopy: true})
return output, err
}
Copier offers a range of features:
Copy from field to field with same name
Copy from method to field with same name
Copy from field to method with same name
Copy from slice to slice
Copy from struct to slice
Copy from map to map
Enforce copying a field with a tag
Ignore a field with a tag
Deep Copy
5. stretchr/testify
When dealing with testing in other languages like C#, Javascript with Selenium or Playwright, you may be familiar with using assertions and expectations. However, I couldn’t find anything like that in Go until I discovered the testify package.
Testify’s assert
package provides a variety of helpful tools for our tests, such as:
func TestSomething(t *testing.T) {
// assert equality
assert.Equal(t, 123, 123, "they should be equal")
// assert inequality
assert.NotEqual(t, 123, 456, "they should not be equal")
// assert for nil (good for errors)
assert.Nil(t, object)
// assert for not nil (good when you expect something)
if assert.NotNil(t, object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal(t, "Something", object.Value)
}
}
If you are new to testing, you may find my article on The Fundamentals of Testing helpful.
In testify v1 (at the time of my writing this, the Testify team was working on v2), the lib provides several features to facilitate testing in Go:
Asserts package, which we’ve already mentioned.
Mock package: this provides a simple way to create mock objects that can replace real objects during testing.
Suite package: you can construct a testing suite using a struct, define setup/teardown and testing methods on the struct, and run them with ‘go test’ like you normally would.
Great packages. But, honestly, after working in Go for the last 8 years, I only used testify and zerolog from this list :)