Learn how to configure the Kendo UI Grid Component to easily consume data through GraphQL queries and mutations.
By now you have almost certainly heard of GraphQL, the runtime query language for APIs. With rapidly growing popularity, it is becoming an increasingly adopted standard for API development. This has generated demand for frameworks and UI tools that can easily consume the data from a GraphQL API – just like the Kendo UI Components can do, as we provide seamless integration through the DataSource component.
This blog provides a comprehensive guide on how to setup the Kendo UI Grid component to perform CRUD operations through GraphQL queries and mutations. It includes a sample application, code snippets and documentation resources to get you up and running with Kendo UI and GraphQL.
Why are Kendo UI Widgets Easy To Integrate with a GraphQL API?
The backbone of every data-driven Kendo UI Component is the DataSource. It possesses the agility to adapt to any data/service scenario and fully supports CRUD data operations. It is also highly configurable and provides tons of features for fine-tuning its behavior. This is also the main reason why consuming a GraphQL API is so easy with the DataSource abstraction.
Setup a GraphQL Service
First things first – we need to create a GraphQL service that can receive queries and mutations to validate and execute:
- Start by defining a type which describes the possible data that can be queried on the service:
import {
GraphQLObjectType,
GraphQLString,
GraphQLID,
GraphQLFloat,
}
from
'graphql'
;
module.exports = new GraphQLObjectType({
name
:
'Product'
,
fields: () => ({
ProductID: { type: GraphQLID },
ProductName: { type: GraphQLString },
UnitPrice: { type: GraphQLFloat },
UnitsInStock: { type: GraphQLFloat }
})
});
- Create queries for fetching the data and mutations for modifying the data server-side:
const RootQuery = new GraphQLObjectType({
name
:
'RootQueryType'
,
fields: {
products: {
type: new GraphQLList(Product),
resolve(parent, args){
return
products;
}
}
}
});
const Mutation = new GraphQLObjectType({
name
:
'Mutation'
,
fields: {
AddProduct: {
type: Product,
args:{
ProductName: { type: new GraphQLNonNull(GraphQLString) },
UnitPrice: { type: new GraphQLNonNull(GraphQLFloat) },
UnitsInStock: { type: new GraphQLNonNull(GraphQLFloat) }
},
resolve(parent, args) {
let newProduct = {
ProductID: uuidv1(),
ProductName: args.ProductName,
UnitsInStock: args.UnitsInStock,
UnitPrice: args.UnitPrice
}
products.unshift(newProduct);
return
newProduct;
}
},
UpdateProduct: {
type: Product,
args:{
ProductID: { type: new GraphQLNonNull(GraphQLID) },
ProductName: { type: new GraphQLNonNull(GraphQLString) },
UnitPrice: { type: new GraphQLNonNull(GraphQLFloat) },
UnitsInStock: { type: new GraphQLNonNull(GraphQLFloat) }
},
resolve(parent, args) {
let
index
= products.findIndex(product => product.ProductID == args.ProductID);
let product = products[
index
];
product.ProductName = args.ProductName;
product.UnitsInStock = args.UnitsInStock;
product.UnitPrice = args.UnitPrice;
return
product;
}
},
DeleteProduct: {
type: Product,
args:{
ProductID: { type: new GraphQLNonNull(GraphQLID) }
},
resolve(parent, args) {
let
index
= products.findIndex(product => product.ProductID == args.ProductID);
products.splice(
index
, 1);
return
{ ProductID: args.ProductID };
}
}
}
});
module.exports = new GraphQLSchema({
query: RootQuery,
mutation: Mutation
});
- Serve the GraphQL service over HTTP via a single endpoint which expresses the full set of its capabilities:
import express
from
'express'
;
import cors
from
'cors'
;
import graphqlHTTP
from
'express-graphql'
;
import
schema
from
'./schema'
;
import { createServer }
from
'http'
;
const PORT = 3021;
var app = express();
app.use(cors());
app.use(
'/graphql'
, graphqlHTTP({
schema
}));
const server = createServer(app);
server.listen(PORT, () => {
console.log(`API Server
is
now running
on
http://localhost:${PORT}/graphql`)
});
For additional information on how to setup the server, required packages and the full GraphQL schema, refer to the source code of the sample application.
Configuring the Grid
To be able to use the Kendo UI Grid Component, just reference the required client-side resources of the framework:
<
link
rel
=
"stylesheet"
href
=
"https://kendo.cdn.telerik.com/2018.2.620/styles/kendo.default-v2.min.css"
/>
<
script
src
=
"http://kendo.cdn.telerik.com/2018.2.620/js/jquery.min.js"
></
script
>
<
script
src
=
"http://kendo.cdn.telerik.com/2018.2.620/js/kendo.all.min.js"
></
script
>
Once you have the right setup, adding the Grid is as simple as placing a container element on the page and then initializing the widget with JavaScript:
<div id=
"content"
>
<div id=
"grid"
></div>
</div>
$(document).ready(
function
() {
$(
"#grid"
).kendoGrid({
dataSource: dataSource,
height: 550,
groupable:
true
,
sortable:
true
,
pageable:
true
,
toolbar: [
"create"
],
editable:
"inline"
,
columns: [{
field:
"ProductID"
,
title:
"Product ID"
},
{
field:
"ProductName"
,
title:
"Product Name"
},
{
field:
"UnitPrice"
,
title:
"Unit Price"
},
{
field:
"UnitsInStock"
,
title:
"Units in stock"
},
{
command: [
"edit"
,
"destroy"
],
title:
"Options "
,
width:
"250px"
}]
});
});
</script>
Using GraphQL on the Client
The wealth of configuration options that the DataSource offers allows you to easily integrate it to work with a GraphQL API and bind the Grid Component to it.
Compose Queries and Mutations
GraphQL is all about asking for specific fields on objects. To populate the Grid initially with records, we need to issue a query against the API to return the object types:
<script>
var
READ_PRODUCTS_QUERY =
"query {"
+
"products { ProductID, ProductName, UnitPrice, UnitsInStock }"
+
"}"
;
</script>
And then create the mutations for adding, updating and deleting the object type:
<script>
var
ADD_PRODUCT_QUERY =
"mutation AddProduct($ProductName: String!, $UnitPrice: Float!, $UnitsInStock: Float!){"
+
"AddProduct(ProductName: $ProductName, UnitPrice: $UnitPrice, UnitsInStock: $UnitsInStock ){"
+
"ProductID,"
+
"ProductName,"
+
"UnitPrice,"
+
"UnitsInStock"
+
"}"
+
"}"
;
var
UPDATE_PRODUCT_QUERY =
"mutation UpdateProduct($ProductID: ID!, $ProductName: String! ,$UnitPrice: Float!, $UnitsInStock: Float!){"
+
"UpdateProduct(ProductID: $ProductID, ProductName: $ProductName, UnitPrice: $UnitPrice, UnitsInStock: $UnitsInStock){"
+
"ProductID,"
+
"ProductName,"
+
"UnitPrice,"
+
"UnitsInStock"
+
"}"
+
"}"
;
var
DELETE_PRODUCT_QUERY =
"mutation DeleteProduct($ProductID: ID!){"
+
"DeleteProduct(ProductID: $ProductID){"
+
"ProductID"
+
"}"
+
"}"
;
</script>
Consume the API
To request or modify data through a GraphQL query or mutation, all you have to do is to configure the transport.read method of the DataSource:
- Set the content-type to “application/json.”
- Set the request type to “POST.”
- Pass the composed GraphQL query/mutation as “query” and the model data as “variables” parameters along with the requests to the remote service when performing CRUD operations. This is achieved through the transport.read’s data() method.
<script>
var
dataSource =
new
kendo.data.DataSource({
pageSize: 20,
transport: {
read: {
contentType:
"application/json"
,
url:
"http://localhost:3021/graphql"
,
type:
"POST"
,
data:
function
() {
return
{ query: READ_PRODUCTS_QUERY };
}
},
update: {
contentType:
"application/json"
,
url:
"http://localhost:3021/graphql"
,
type:
"POST"
,
data:
function
(model) {
return
{
query: UPDATE_PRODUCT_QUERY,
variables: model
};
}
},
destroy: {
contentType:
"application/json"
,
url:
"http://localhost:3021/graphql"
,
type:
"POST"
,
data:
function
(model) {
return
{
query: DELETE_PRODUCT_QUERY,
variables: model
};
}
},
create: {
contentType:
"application/json"
,
url:
"http://localhost:3021/graphql"
,
type:
"POST"
,
data:
function
(model) {
return
{
query: ADD_PRODUCT_QUERY,
variables: model
};
}
},
parameterMap:
function
(options, operation) {
return
kendo.stringify(options);
}
},
schema: {
data:
function
(response) {
var
data = response.data;
if
(data.products) {
return
data.products;
}
else
if
(data.AddProduct) {
return
data.AddProduct;
}
else
if
(data.UpdateProduct) {
return
data.UpdateProduct;
}
else
if
(data.DeleteProduct){
return
data.DeleteProduct;
}
},
model: {
id:
"ProductID"
,
fields: {
ProductID: { type:
"string"
, editable:
false
},
ProductName: { type:
"string"
},
UnitPrice: { type:
"number"
},
UnitsInStock: { type:
"number"
}
}
}
}
});
</script>
Format Request Parameters and Parse the Response
Beyond configuring the transports, there are also other features of the DataSource, like the parameterMap() and the schema options, that are useful for encoding the request parameters and parsing the API response:
- When sending data to a GraphQL API, the request parameters for CRUD operations need to be encoded in JSON format. To help with this, the DataSource’s transport.parameterMap() option is used.
- On the other hand, when receiving data from the service, the DataSource’s schema.data() method comes to the rescue for parsing the received response, so that the Grid can figure out which field of the response holds the data.
Start Exploring the Grid
There you go – by simply setting up the DataSource we got the Grid up and running with a GraphQL API. From here on, you can start exploring the vast options of the Grid and also take advantage of the other 70+ ready-to-use Kendo UI components and easily bind them to a GraphQL service.