Skip to content

Latest commit

 

History

History

README.md

Use a Go Backend Server With Hasura

The Hasura GraphQL engine instantly generates a real-time GraphQL CRUD API on your data. For some use-cases, we may need to call a custom backend server. This article uses the programming language Go to run custom business logic, respond to event triggers, create a GraphQL remote schema, and query a GraphQL endpoint.

Run the example with Docker

docker compose up -d

Hasura Actions

Actions are a way to extend Hasura's schema with custom business logic using custom queries and mutations. Actions can be added to Hasura to handle various use cases such as data validation, data enrichment from external sources, and any other complex business logic.

In the Actions tab on the Hasura Console we will set up a custom login function

type Mutation {
  login(username: String!, password: String!): LoginResponse
}

New types definition:

type LoginResponse {
  AccessToken: String!
}

Create the action, click the Codegen tab, and select go-serve-mux.

Combine the two generated Go files into main.go then run go run main.go.

In the Hasura API explorer tab you should now be able to test it

mutation {
  login(password: "password", username: "username") {
    AccessToken
  }
}

Result:

{
  "data": {
    "login": {
      "AccessToken": "<sample value>"
    }
  }
}

Event Triggers

Hasura can be used to create event triggers on tables in the database. Event triggers reliably capture events on specified tables and invoke HTTP webhooks to carry out any custom logic.

Let's send a webhook when a new user is created and print out their name.

  1. In the Hasura Console add a user table with a Text column name and the frequently used UUID column id.

  2. In the event trigger tab, on the user table, check the insert and via console trigger operations.

  3. The event trigger payload schema can be found in the docs. We make a struct type in Go to represent this

    type EventTriggerPayload[Old interface{}, New interface{}] struct {
        Event struct {
        	SessionVariables struct {
        		XHasuraRole string `json:"x-hasura-role"`
        	} `json:"session_variables"`
        	Op   string `json:"op"`
        	Data struct {
        		Old *Old `json:"old"`
        		New *New `json:"new"`
        	} `json:"data"`
        	TraceContext struct {
        		TraceID string `json:"trace_id"`
        		SpanID  string `json:"span_id"`
        	} `json:"trace_context"`
        } `json:"event"`
        CreatedAt    time.Time `json:"created_at"`
        ID           string    `json:"id"`
        DeliveryInfo struct {
        	MaxRetries   int `json:"max_retries"`
        	CurrentRetry int `json:"current_retry"`
        } `json:"delivery_info"`
        Trigger struct {
        	Name string `json:"name"`
        } `json:"trigger"`
        Table struct {
        	Schema string `json:"schema"`
        	Name   string `json:"name"`
        } `json:"table"`
    }
  4. Now we make an HTTP handler that handles the event

    func NewUserHandler(w http.ResponseWriter, r *http.Request) {
        var u EventTriggerPayload[interface{}, struct {
            Id   string
            Name string
        }]
        err := json.NewDecoder(r.Body).Decode(&u)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        fmt.Println("Hello", u.Event.Data.New.Name)
    
        w.WriteHeader(200)
    }
    
    mux.HandleFunc("/event", event.NewUserHandler)

When you add a user in Hasura your Go server should receive the event.

Remote Schema

We can make a custom GraphQL in Go using gqlgen and connect it to Hasura using a remote schema.

  1. Run the gqlgen quickstart, skipping the first step.

  2. In the graph/schema.resolvers.go Todos resolver return a placeholder test value.

    func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
        return []*model.Todo{
            {
                ID:   "test",
                Text: "test",
                Done: false,
                User: &model.User{
                    ID:   "",
                    Name: "",
                },
            },
        }, nil
    }
  3. Delete server.go and in main.go add the generated GraphQL handler

    import (
     "log"
     "net/http"
    
     "github.com/hasura/learn-graphql/tutorials/backend/backend-stack/source-code/action"
     "github.com/hasura/learn-graphql/tutorials/backend/backend-stack/source-code/event"
    
     "github.com/99designs/gqlgen/graphql/handler"
     "github.com/hasura/learn-graphql/tutorials/backend/backend-stack/source-code/graph"
     "github.com/hasura/learn-graphql/tutorials/backend/backend-stack/source-code/graph/generated"
    )
    
    func main() {
        mux := http.NewServeMux()
    
        srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
    
        mux.HandleFunc("/graphql", srv.ServeHTTP)
    
        mux.HandleFunc("/action", action.LoginHandler)
    
        mux.HandleFunc("/event", event.NewUserHandler)
    
        err := http.ListenAndServe(":3000", mux)
        log.Fatal(err)
    }
  4. In the Hasura Console remote schema tab, add your Go server <Go server URL>/graphql

  5. In the API Explorer tab, try querying the sample todos.

    query {
      todos {
        id
        text
        done
      }
    }

Query GraphQL

To query Hasura from Go we use Khan Academy's genqlient to generate a type-safe GraphQL client.

  1. Download your Hasura schema

    npx --yes graphqurl <Hasura URL>/v1/graphql --introspect > schema.graphql
  2. Add your queries to genqlient.graphql

    query GetUsers {
      user {
        id
        name
      }
    }
  3. Create genqlient.yaml

    schema: schema.graphql
    operations:
      - genqlient.graphql
    generated: generated/generated.go
    use_struct_references: true
    bindings:
      DateTime:
        type: time.Time
      uuid:
        type: string
      Int:
        type: int32
  4. Install and run genqlient

    go get github.com/Khan/genqlient
    
    go run github.com/Khan/genqlient
  5. To test if your setup is working, add a user, then query all users in the event trigger handler we created earlier

    ctx := context.Background()
    client := graphql.NewClient("<Hasura URL>/v1/graphql", http.DefaultClient)
    resp, _ := generated.GetUsers(ctx, client)
    for _, value := range resp.GetUser() {
      fmt.Printf("%#v", value)
    }

Conclusion

Hasura autogenerates most of our API but gives us escape hatches for custom logic. We've gone over four ways you can combine the power of Go and Hasura. Enjoy!

When ready to go to production, check out Hasura Cloud for a fully managed Hasura deployment.