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 -dActions 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>"
}
}
}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.
-
In the Hasura Console add a
usertable with aTextcolumnnameand the frequently usedUUIDcolumn id. -
In the event trigger tab, on the
usertable, check the insert and via console trigger operations. -
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"` }
-
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.
We can make a custom GraphQL in Go using gqlgen and connect it to Hasura using a remote schema.
-
Run the gqlgen quickstart, skipping the first step.
-
In the
graph/schema.resolvers.goTodos 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 }
-
Delete
server.goand inmain.goadd the generated GraphQL handlerimport ( "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) }
-
In the Hasura Console remote schema tab, add your Go server
<Go server URL>/graphql -
In the API Explorer tab, try querying the sample todos.
query { todos { id text done } }
To query Hasura from Go we use Khan Academy's genqlient to generate a type-safe GraphQL client.
-
npx --yes graphqurl <Hasura URL>/v1/graphql --introspect > schema.graphql
-
Add your queries to
genqlient.graphqlquery GetUsers { user { id name } }
-
Create
genqlient.yamlschema: schema.graphql operations: - genqlient.graphql generated: generated/generated.go use_struct_references: true bindings: DateTime: type: time.Time uuid: type: string Int: type: int32
-
Install and run
genqlientgo get github.com/Khan/genqlient go run github.com/Khan/genqlient
-
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) }
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.
