Creating a GO API that can handle queries and mutations from HTTP requests

Hi everyone :raised_hand: , i’m new to Dgraph and Go and i’m trying to write a Go API that can handle HTTP requests containing dgraph queries and mutations.

So far, I managed to send a curl request containing a query and handle it to return the json result from my database but now i’m trying to pass to my API a mutation in a http request and pass it back to my dgraph database.

I tried to find examples but i couldn’t find anything like what i’m trying to do…

I’d really apreciate if someone could take a look and help me find out what i’m doing wrong

Here is my code :

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"github.com/dgraph-io/dgo/v2"
	"github.com/dgraph-io/dgo/v2/protos/api"
	"github.com/golang/gddo/httputil/header"
	"google.golang.org/grpc"
)

// query example : curl -H "Content-Type: application/json" localhost:2000/api/query -XPOST -d '{"query": "{\n testQuery(func: has(name)) {\n uid\n name\n firstname \n }\n }"}'

//mutation example :  curl -H "Content-Type: application/json" localhost:2000/api/mutate -XPOST -d '{ "set":[{"name":"Test2"}]}'

func homePage(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome to the homepage")
	fmt.Println("Endpoint touched: homePage")
}

func apiPage(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Endpoint api ...")
	fmt.Println("Endpoint touched : /api ")
}

//dgraph client creation
func getDgraphClient() *dgo.Dgraph {
	conn, err := grpc.Dial("127.0.0.1:9080", grpc.WithInsecure())
	if err != nil {
		log.Fatal("While trying to dial gRPC")
	}

	dc := api.NewDgraphClient(conn)
	dg := dgo.NewDgraphClient(dc)

	return dg
}

type queryStruct struct {
	Query string
}

type mutateStruct struct {
	Mutation []byte
}

func returnMutationJSONInR(w http.ResponseWriter, r *http.Request) []byte {

	if r.Header.Get("Content-Type") != "" {
		value, _ := header.ParseValueAndParams(r.Header, "Content-Type")
		if value != "application/json" {
			msg := "Content-Type header is not application/json"
			http.Error(w, msg, http.StatusUnsupportedMediaType)

		}
	}
	var m mutateStruct
	err := json.NewDecoder(r.Body).Decode(&m)

	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)

	}
	fmt.Println("Mutation received ...  Treating following Mutation : \n")
	fmt.Println(m.Mutation)

	return m.Mutation

}

//returnJSONInR Returns query in a string

func returnJSONInR(w http.ResponseWriter, r *http.Request) string {

	if r.Header.Get("Content-Type") != "" {
		value, _ := header.ParseValueAndParams(r.Header, "Content-Type")
		if value != "application/json" {
			msg := "Content-Type header is not application/json"
			http.Error(w, msg, http.StatusUnsupportedMediaType)

		}
	}
	var q queryStruct
	err := json.NewDecoder(r.Body).Decode(&q)

	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)

	}
	fmt.Println("Query received ...  Treating following Query : \n")
	fmt.Println(q.Query)

	return q.Query

}

//returnQueryResult returns database result
func returnQueryResult(w http.ResponseWriter, r *http.Request) {
	fmt.Println("Enpoint touché: returnQueryResult \n")

	query := returnJSONInR(w, r)
	
	ctx := context.Background()

	dg := getDgraphClient()
	txn := dg.NewTxn()
	defer txn.Discard(ctx)

	res, err := txn.Query(ctx, query)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("\n*Query result %s \n", res.Json)
	fmt.Fprintf(w, "dgraph response :  %s \n", res)
}

func mutateDatabase(w http.ResponseWriter, r *http.Request) {
	fmt.Println("Enpoint touched: mutateDatabase \n")

	mutation := returnMutationJSONInR(w, r)
	println(mutation)
	dg := getDgraphClient()
	//op := &api.Operation{}

	ctx := context.Background()
	if err := dg.Alter(ctx, op); err != nil {
		log.Fatal(err)
	}

	pb, err := json.Marshal(mutation)
	if err != nil {
		log.Fatal(err)
	}
	println(pb)
	mu := &api.Mutation{
		//CommitNow: true,
	}
	mu.SetJson = pb
	println(mu)
	txn := dg.NewTxn()
	defer txn.Discard(ctx)

	req := &api.Request{CommitNow: true, Mutations: []*api.Mutation{mu}}

	res, err := txn.Do(ctx, req)
	if err != nil {
		log.Fatal(err)
	}
	

	/*res,err := txn.Mutate(ctx, mu)
	if err != nil {
		log.Fatal(err)
	}*/

	fmt.Println(res)

}

//Http Handler
func handleRequests() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", homePage)
	//ajout des routes
	mux.HandleFunc("/api", apiPage)
	mux.HandleFunc("/api/mutate", mutateDatabase)
	mux.HandleFunc("/api/query", returnQueryResult)
	log.Fatal(http.ListenAndServe(":2000", mux))
}

func main() {
	fmt.Println("Testing API on :2000  ... \n")
	handleRequests()
}

I realize I may have done a lot of things wrong but i’ts my first time developing an api :no_mouth:, if anybody got any examples or tutorial to help me i’d be very thankful!

Peace :v: !

You can use Dgraph’s HTTP API that’s accessible via Dgraph Alpha’s HTTP port (default: 8080). In your example code you’re using dgo, which is the Dgraph gRPC client for Go.

If all the API is doing is forwarding requests /mutate and /query, then you can also simply connect to Dgraph directly over HTTP.

Thank you for your answer, I know i can send curl request directly using 8080 but I am trying to acheive that using dgo .
Basically the http request is sent to my code on port 2000 and my code prints the json result from the query , I’m now trying to send an http post containing a mutation in its body and process it trough my code

Hey ArrowHeadDev,

you basically have to put correct jsons as mutations, and it will work.

So when you have a http endpoint mutation like:

{
    "set": [
        {
            "uid": "_:new",
            "dgraph.type": "Element",
            "elementName": "Test"
        }
    ]
}

You have to change it to:

SetMutation := `
	{
		"uid": "_:new",
		"dgraph.type": "Element",
		"elementName": "Test"
	}
`

And pass this to the request, and it should work.

I wanted a more streamlined approach for doing this, so I wrote a simple wrapper pkg for dgo. Still a work in progress, but hope you can get some inspiration from it. GitHub - ppp225/ndgo: ndgo is a dgraph dgo wrapper. Provides abstractions and helper functions.

Using ndgo, you could run the above using resp, err := txn.Set(SetMutation)

2 Likes

Hey ! Thank you for this response , i’m going to try and do it this way , cheers :wink: !

1 Like

hi @ppp225, I working on a similar goal with https://github.com/emicklei/dgraph-access Also WIP. Still finding the right abstractions

2 Likes