📜 ⬆️ ⬇️

GraphQL API (CRUD) on Go

image


Hello! About GraphQL many articles on Habré, but running over them found that they all bypass such a wonderful language as Go. Today I will try to correct this misunderstanding. To do this, write an API on Go using GraphQL.


In short: GraphQL is a query language for building an API that describes how to request and return data (for more information, see the official graphql.imtqy.com resource and on the habr ).


Argue about whether GraphQL or REST is better here.


We will have a classic API: CRUD (Create, Read, Update, Delete) adding, receiving, editing and deleting products in the online store.
On the server side, we will use the ready-made graphql-go implementation of GraphQL


First you need to download graphql-go, this can be done with the command


go get github.com/graphql-go/graphql 

Next, we describe the structure of the goods (in simplified form)


 type Product struct { ID int64 `json:"id"` Name string `json:"name"` Info string `json:"info,omitempty"` Price float64 `json:"price"` } 

ID - unique identifier, Name - name, Info - product information, Price - price


The first thing that needs to be done is to call the Do method, which takes the data schema and the request parameters as input parameters. And will return the resulting data to us (for further transmission to the client)


 result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) 

Full code
 func executeQuery(query string, schema graphql.Schema) *graphql.Result { result := graphql.Do(graphql.Params{ Schema: schema, RequestString: query, }) if len(result.Errors) > 0 { fmt.Printf("errors: %v", result.Errors) } return result } func main() { http.HandleFunc("/product", func(w http.ResponseWriter, r *http.Request) { result := executeQuery(r.URL.Query().Get("query"), schema) json.NewEncoder(w).Encode(result) }) http.ListenAndServe(":8080", nil) } 

Schema is the data schema, RequestString is the value of the query string parameter, in our case the value of query


Schema


The schema accepts two root data types: Query - immutable data, Mutation - mutable data


 var schema, _ = graphql.NewSchema( graphql.SchemaConfig{ Query: queryType, Mutation: mutationType, }, ) 

Query (Queries)


Query is used to read (and only read) data. Using Query we specify what data the server should return.
Let's write the implementation of the Query data type, in our case it will contain fields with information about a single product (product) and a list of products (list)


 var queryType = graphql.NewObject( graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ /*    ID http://localhost:8080/product?query={product(id:1){name,info,price}} */ "product": &graphql.Field{ Type: productType, Description: "Get product by id", //   ,    Args: graphql.FieldConfigArgument{ //       id "id": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { id, ok := p.Args["id"].(int) if ok { //    ID for _, product := range products { if int(product.ID) == id { return product, nil } } } return nil, nil }, }, /*    http://localhost:8080/product?query={list{id,name,info,price}} */ "list": &graphql.Field{ Type: graphql.NewList(productType), Description: "Get product list", Resolve: func(params graphql.ResolveParams) (interface{}, error) { return products, nil }, }, }, }) 

The queryType type contains the required Name and Fields fields, as well as an optional Description (used for documentation)
In turn, the Fields field also contains the required Type field and the optional Args , Resolve and Description fields.


Args


Arguments - the list of parameters transmitted from the client to the server and affecting the result of the returned data. Arguments are tied to a specific field. And the arguments can be passed in Query as well as in Mutation .


 ?query={product(id:1){name,info,price}} 

In this case, the id argument for the product field with a value of 1, says that it is necessary to return the product with the specified identifier.
For list arguments are omitted, but in a real application, this may be, for example: limit and offset .


Resolve (Recognizers)


All the logic of working with data (for example, requests to the database, processing and filtering) is in the recognizers, they return the data that will be transmitted to the client as a response to the request.


Type (Type System)


GraphQL uses its type system to describe data. You can use both the basic types of String , Int , Float , Boolean , and your own (custom). For our example, we need a custom Product type that will describe all the properties of the product.


 var productType = graphql.NewObject( graphql.ObjectConfig{ Name: "Product", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.Int, }, "name": &graphql.Field{ Type: graphql.String, }, "info": &graphql.Field{ Type: graphql.String, }, "price": &graphql.Field{ Type: graphql.Float, }, }, }, ) 

For each field, a base type is specified, in this case, it is graphql.Int , graphql.String , graphql.Float .
The number of nested fields is unlimited, so you can implement a graph system of any level.


Mutation


The mutations are these mutable data, which include: add, edit, and delete. In all other respects, mutations are very similar to regular queries: they also take Args arguments and return Resolve data as a response to a query.


Let's write mutations for our products.
  var mutationType = graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{ /*    http://localhost:8080/product?query=mutation+_{create(name:"Tequila",info:"Alcohol",price:99){id,name,info,price}} */ "create": &graphql.Field{ Type: productType, Description: "Create new product", Args: graphql.FieldConfigArgument{ "name": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), //     }, "info": &graphql.ArgumentConfig{ Type: graphql.String, //    }, "price": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Float), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { rand.Seed(time.Now().UnixNano()) product := Product{ ID: int64(rand.Intn(100000)), //   ID Name: params.Args["name"].(string), Info: params.Args["info"].(string), Price: params.Args["price"].(float64), } products = append(products, product) return product, nil }, }, /*    id http://localhost:8080/product?query=mutation+_{update(id:1,price:195){id,name,info,price}} */ "update": &graphql.Field{ Type: productType, Description: "Update product by id", Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Int), }, "name": &graphql.ArgumentConfig{ Type: graphql.String, }, "info": &graphql.ArgumentConfig{ Type: graphql.String, }, "price": &graphql.ArgumentConfig{ Type: graphql.Float, }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { id, _ := params.Args["id"].(int) name, nameOk := params.Args["name"].(string) info, infoOk := params.Args["info"].(string) price, priceOk := params.Args["price"].(float64) product := Product{} for i, p := range products { //     if int64(id) == p.ID { if nameOk { products[i].Name = name } if infoOk { products[i].Info = info } if priceOk { products[i].Price = price } product = products[i] break } } return product, nil }, }, /*    id http://localhost:8080/product?query=mutation+_{delete(id:1){id,name,info,price}} */ "delete": &graphql.Field{ Type: productType, Description: "Delete product by id", Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.Int), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { id, _ := params.Args["id"].(int) product := Product{} for i, p := range products { if int64(id) == p.ID { product = products[i] //     products = append(products[:i], products[i+1:]...) } } return product, nil }, }, }, }) 

All similar to queryType . There is only one small feature: the graphql.NewNonNull(graphql.Int) type graphql.NewNonNull(graphql.Int) , which tells us that this field cannot be empty (similar to NOT NULL in MySQL)


Everything. Now we have a simple CRUD API on Go to work with products. We did not use the database for this example, but we looked at how to create a data model and manipulate them with mutations.


Examples


If you downloaded the source through


 go get github.com/graphql-go/graphql 

just go to the directory with an example


 cd examples/crud 

and run the application


 go run main.go 

You can use the following queries:
Getting product by ID
http://localhost:8080/product?query={product(id:1){name,info,price}}


Getting a list of products
http://localhost:8080/product?query={list{id,name,info,price}}


Adding a new product
http://localhost:8080/product?query=mutation+_{create(name:"Tequila",info:"Strong alcoholic beverage",price:999){id,name,info,price}}


Product editing
http://localhost:8080/product?query=mutation+_{update(id:1,price:195){id,name,info,price}}


Product removal by id
http://localhost:8080/product?query=mutation+_{delete(id:1){id,name,info,price}}


If you are using REST you should pay attention to GraphQL as a possible alternative. Yes, at first glance it seems more difficult, but it is worth starting and in a couple of days you will master this technology. At least it will be useful.


')

Source: https://habr.com/ru/post/418203/


All Articles