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, })
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
The schema accepts two root data types: Query
- immutable data, Mutation
- mutable data
var schema, _ = graphql.NewSchema( graphql.SchemaConfig{ Query: queryType, Mutation: mutationType, }, )
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.
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
.
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.
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.
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.
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.
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 IDhttp://localhost:8080/product?query={product(id:1){name,info,price}}
Getting a list of productshttp://localhost:8080/product?query={list{id,name,info,price}}
Adding a new producthttp://localhost:8080/product?query=mutation+_{create(name:"Tequila",info:"Strong alcoholic beverage",price:999){id,name,info,price}}
Product editinghttp://localhost:8080/product?query=mutation+_{update(id:1,price:195){id,name,info,price}}
Product removal by idhttp://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