Go Client Now Available

3 min read
Federico R.
Federico R.
Published April 5, 2018 Updated August 31, 2020

Go is one of the fastest growing languages around. We ourselves use it extensively inside the Stream API service. Support for Go was added almost two years ago by MrHenry and HyperWorks, which open-sourced a Go client.

We are proud to announce that starting today, we are releasing our official Go API client. You can find it on GitHub at https://github.com/GetStream/stream-go2.

The new client APIs are more simple and easy to use. Compared to the previous version (which is still available here), the new version has a much smaller code footprint, with about 57% less lines of code. Here’s a list of the key features:

  • Complete test coverage of Stream API endpoints and options
  • Smaller code footprint (less than 900 relevant lines of code)
  • Extensive use of functional options for clearer method calls that, in the future, can be extended without breaking compatibility
  • Utilities for initializing a client directly from environment variables
  • Complete endpoints support
  • Happy Go linter, meaning more consistent docs and idiomatic code
  • Code versioning via gopkg.in

The complete documentation for the client is available via GoDoc, while Stream-specific docs are available on our docs page.

Let’s see how to use it with some simple examples.

Obtaining stream-go2 is straightforward. Type the following to retrieve the latest stable version of the client:
go get gopkg.in/GetStream/stream-go2.v1

Then, we'll start by creating a new client which we’ll use in our application:
https://gist.github.com/ruggi/597d2a8b11779f91644126f8a8afcaec

We can now use the newly created client to initialize the feeds we want to interact with:

// Initialize a flat feed
flat := client.FlatFeed("user", "123")

// Initialize an aggregated feed
aggregated := client.AggregatedFeed("timeline_aggregated", "123")

// Initialize a notification feed
notification := client.NotificationFeed("notification", "123")

Flat, aggregated, and notification feeds implement the stream.Feed interface. To retrieve activities, all of them offer a GetActivities method that returns different structs based on the kind of feed:

// Get flat feed activities
flatResp, err := flat.GetActivities()
if err != nil {
    // ...
}
log.Println("got response in", flatResp.Duration)
for _, activity := range flatResp.Results {
    log.Println(activity.Actor, activity.Verb, activity.Object)
}

// Get aggregated activities
aggrResp, err := aggregated.GetActivities()
if err != nil {
    // ...
}

Feeds pagination via offset and limit can be done using the WithActivitiesOffset and WithActivitiesLimit option functions:

flat.GetActivities(
  stream.WithActivitiesOffset(2),
  stream.WithActivitiesLimit(10),
)

Pagination via activity ID can be done with the WithActivitiesID[GT,GTE,LT,LTE] functions for the feeds that support it.

Moreover, multiple filters can be combined:

flat.GetActivities(
    stream.WithActivitiesIDGTE("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"),
    stream.WithActivitiesLimit(5),
)

Here are all the supported read options.

If you have a flat feed with custom ranking you can retrieve its' activities by applying one of the ranking methods configured for that feed with the GetActivitiesWithRanking method:

resp, err := rankedFeed.GetActivitiesWithRanking("popularity") // you can also add optional read options
if err != nil {
    // ...
}
for i, activity := range resp.Results {
    log.Println(i+1, activity.ForeignID, activity.Score)
}

You can add activities to any kind of stream.Feed with the AddActivity and AddActivities methods:

activity := stream.Activity{
    Actor:  "bob",
    Verb:   "like",
    Object: "music",
}
resp, err := feed.AddActivity(activity)
if err != nil {
    // ...
}
log.Println("activity addedd succesfully in", resp.Duration)
log.Println(resp.Activity.ID)

Removing an activity can be done either by its ID or its foreign ID. The stream.Feed interface offers two methods for doing so: RemoveActivityByID and RemoveActivityByForeignID.

// Remove by ID
err := feed.RemoveActivityByID("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")
if err != nil {
    // ...
}

// Remove by foreign ID
err := feed.RemoveActivityByForeignID("fid:123")
if err != nil {
    // ...
}

Following and unfollowing a feed is done via the Follow and Unfollow methods offered by the Feed interface:

// Follow a feed
f1, f2 := stream.FlatFeed("user", "123"), stream.FlatFeed("user", "456")
err := f1.Follow(f2)
if err != nil {
    // ...
}

// Adding an activity to f2 will make it appear on f1
act := stream.Activity{Actor: "bob", Verb: "like", Object: "music"}
err = f2.AddActivity(act)
if err != nil {
    // ...
}
resp, err := f1.GetActivities()
if err != nil {

Note: many methods offered by stream.Feed and stream.Client support variadic option functions to customize the behavior of the methods themselves, and by convention, they all start with the “With…” prefix. You can find them all listed on the GoDoc page.

For example, when using a feed’s GetFollowing method to retrieve the feeds which are being followed, you can use these option functions:

// WithFollowingFilter adds the filter parameter to API calls, used when retrieving
// following feeds, allowing the check whether certain feeds are being followed.
func WithFollowingFilter(ids ...string) FollowingOption {
    return FollowingOption{makeRequestOption("filter", strings.Join(ids, ","))}
}

// WithFollowingLimit limits the number of followings in the response to the provided limit.
func WithFollowingLimit(limit int) FollowingOption {
    return FollowingOption{withLimit(limit)}
}

// WithFollowingOffset returns followings starting from the given offset.
func WithFollowingOffset(offset int) FollowingOption {
    return FollowingOption{withOffset(offset)}
}

Which results in something like this:

resp, err := feed.GetFollowing(
    stream.WithFollowingFilter("aabbccddee", "ccddeeffgg"),
    stream.WithFollowingLimit(5),
    stream.WithFollowingOffset(100),
)
if err != nil {
    // ...
}

Wrapping up

Once again, we would like to thank MrHenry and HyperWorks for their efforts in creating the original stream-go client, a great community contribution which made it possible for many Go developers to use Stream.

The new Go client exposes all Stream’s API features via a clean and easy to use interface. The client was rewritten from scratch, and this approach allowed us to follow Go’s coding best practices and test the entire code base very easily.