Skip to content

ThreadDB & Buckets and in Go

Warning

We are shutting down our hosted Hub infrastructure. Please see this deprecation notice for details.

You have access to the full suite of Textile APIs and technologies in Go. This includes access to:

  • Hub-based persistence
  • Thread client
  • Local Thread databases
  • Bucket client
  • And more.

Below, we'll walk you through the basic flow for interacting with the Hub-backed ThreadDB.

Set up your project

Create a directory for your project and initialize a new go module.

mkdir hello-threads
cd hello-threads
go mod init github.com/example/hello-threads

Go is particular about how you install libraries above v2. To ensure you are using the latest, grab v2 now.

go get github.com/textileio/textile/v2

Next, create a main.go that you'll use to build your first thread client.

touch main.go

Connect a new thread client

Inside main.go you'll create a new client connection to the Textile Hub's thread APIs.

package main

import (
    "context"
    "crypto/rand"
    "crypto/tls"
    "fmt"
    "time"

    crypto "github.com/libp2p/go-libp2p-crypto"
    "github.com/textileio/go-threads/api/client"
    "github.com/textileio/go-threads/core/thread"
    "github.com/textileio/textile/api/common"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

func main() {

    // Create an API Client
    creds := credentials.NewTLS(&tls.Config{})
    auth := common.Credentials{}
    opts := []grpc.DialOption{grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(auth)}
    cli, err := client.NewClient("api.hub.textile.io:443", opts...)
    if err != nil {
        panic(err)
    }

    fmt.Println("Success!")
}

You can now run your example,

go run main.go

> Success!

Create a random user

This function will use the crypto and thread libraries to generate a new random private key identity.

func GetRandomUser() (thread.Identity, error) {
    privateKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
    if err != nil {
        return nil, err
    }
    myIdentity := thread.NewLibp2pIdentity(privateKey)
    return myIdentity, nil
}

Hub authentication

Authentication on the Hub is done with either your Account Keys or User Group Keys depending on what type of application you're building and which APIs you plan on using.

Below is an example of using your user key to generate an authenticated API context to use with Hub-backed buckets and threadDB APIs. You can generate a similar context using account keys for accessing a developer or organization account.

func NewUserAuthCtx(ctx context.Context, userGroupKey string, userGroupSecret string) (context.Context, error) {
    // Add our user group key to the context
    ctx = common.NewAPIKeyContext(ctx, userGroupKey)

    // Add a signature using our user group secret
    return common.CreateAPISigContext(ctx, time.Now().Add(time.Minute), userGroupSecret)
}

The above function will take your API key and secret and set up a context ready to prove to the API that the user is who they claim to be.

Request and specify a user token

A user token is generated per-user and can then be used with subsequent API calls to specify what user is making the request. They can only be created using valid API credentials and provable user identity.

func NewTokenCtx(ctx context.Context, user thread.Identity) (context.Context, error){
    // Generate a new token for the user
    token, err := cli.GetToken(ctx, user)
    if err != nil {
        return nil, err
    }
    return thread.NewTokenContext(ctx, token), nil
}

You can also store and reuse the token, but it needs to be attached the the context before future API calls.

Create a new DB

Let's put it all together and then create a new Thread database for our user.

package main

import (
    "context"
    "crypto/rand"
    "crypto/tls"
    "fmt"
    "time"

    crypto "github.com/libp2p/go-libp2p-crypto"
    "github.com/textileio/go-threads/api/client"
    "github.com/textileio/go-threads/core/thread"
    "github.com/textileio/textile/api/common"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

func GetRandomUser() (thread.Identity, error) {
    privateKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
    if err != nil {
        return nil, err
    }
    myIdentity := thread.NewLibp2pIdentity(privateKey)
    return myIdentity, nil
}


func NewUserAuthCtx(ctx context.Context, userGroupKey string, userGroupSecret string) (context.Context, error) {
    // Add our user group key to the context
    ctx = common.NewAPIKeyContext(ctx, userGroupKey)

    // Add a signature using our user group secret
    return common.CreateAPISigContext(ctx, time.Now().Add(time.Minute), userGroupSecret)
}

func NewTokenCtx(ctx context.Context, cli *client.Client, user thread.Identity) (context.Context, error){
    // Generate a new token for the user
    token, err := cli.GetToken(ctx, user)
    if err != nil {
        return nil, err
    }
    return thread.NewTokenContext(ctx, token), nil
}

func main() {

    // Create an API Client
    creds := credentials.NewTLS(&tls.Config{})
    auth := common.Credentials{}
    opts := []grpc.DialOption{grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(auth)}
    cli, err := client.NewClient("api.hub.textile.io:443", opts...)
    if err != nil {
        panic(err)
    }

    user, err := GetRandomUser()
    if err != nil {
        panic(err)
    }

    authCtx, err := NewUserAuthCtx(context.Background(), "<key>", "<secret>")
    if err != nil {
        panic(err)
    }

    tokenCtx, err := NewTokenCtx(authCtx, cli, user)
    if err != nil {
        panic(err)
    }

    // Generate a new thread ID
    threadID := thread.NewIDV1(thread.Raw, 32)

    // Create your new thread
    err = cli.NewDB(tokenCtx, threadID)
    if err != nil {
        panic(err)
    }

    fmt.Println("> Success!")
    fmt.Println(threadID)
}

Please refer to the ThreadDB and Buckets docs for more.