GraphQL in Polavi

Polavi is an e-commerce platform built with GraphQL. In this section, we will learn about how does Polavi e-commerce platform implements GraphQL API and how to customize it.

What is GraphQL API?

For one who does not know about GraphQL, it is a declarative query language for APIs and a runtime that used to query data from server. You can find more information here

How does Polavi e-commerce implement GraphQL?

In Polavi application we use graphql-php. This is a completed implementation of GraphQL API in PHP.

GraphQL module.

If you look to the source code, you can find a module named "Graphql". This module provides 2 main things:

1: GraphQl Schema object If you look in to the services.php file of this module you can find this

$container[\GraphQL\Type\Schema::class] = function() use ($container) {
return new \GraphQL\Type\Schema([
'query'=> $container->get(\Polavi\Module\Graphql\Services\QueryType::class),
'mutation' => $container->get(\Polavi\Module\Graphql\Services\MutationType::class),
]);
};

Above piece of code registers a GraphQL Schema object with 2 sub objects: Query and Mutation

2: 2 API endpoints for the client to request for the data

/** @var \Polavi\Services\Routing\Router $router */
$router->addSiteRoute('graphql.api', ['GET', 'POST'], '/api/graphql', [
\Polavi\Module\Graphql\Middleware\Graphql\GraphqlQLMiddleware::class,
]);
$router->addAdminRoute('admin.graphql.api', ['GET', 'POST'], '/api/graphql', [
\Polavi\Module\Graphql\Middleware\Graphql\GraphqlQLMiddleware::class
]);

How to register a Type to Schema?

In Polavi application, Types are services. If you look to the module you can see there is a folder Services\Type. This folder is where we keep all types of module. For example this is from Catalog module

Polavi catalog graphql types

Polavi catalog graphql types

To add a Type to Schema, you use event dispatcher. This is an example how Customer module add a Customer Type to Schema:

$eventDispatcher->addListener(
'filter.query.type',
function (&$fields, Container $container) {
$fields['customer'] = [
'type' => $container->get(\Polavi\Module\Customer\Services\Type\CustomerType::class),
'description' => "Return a customer",
'args' => [
'id' => Type::nonNull(Type::int())
],
'resolve' => function($rootValue, $args, Container $container, ResolveInfo $info) {
// Authentication example
if(
$container->get(Request::class)->isAdmin() == false &&
$args['id'] != $container->get(Request::class)->getCustomer()->getData('customer_id')
)
return null;
else if(
$container->get(Request::class)->isAdmin() == false &&
$args['id'] == $container->get(Request::class)->getCustomer()->getData('customer_id')
)
return $container->get(Request::class)->getCustomer()->toArray();
else
return \Polavi\_mysql()->getTable('customer')->load($args['id']);
}
];
},
5
);

Most of the types also support to modify the list of its field by using event dispatcher the same way as above.

For example, if you want to add a field to Customer Type, you can do this

$eventDispatcher->addListener(
'filter.customer.type',
function (&$fields, Container $container) {
$fields['point'] = [
'type' => Type::int(),
'description' => "Return customer reward point",
'resolve' => function($rootValue, $args, Container $container, ResolveInfo $info) {
return 123;
}
];
},
5
);

How does GraphQL work in Polavi e-commerce platform?

In Polavi e-commerce platform we use GraphQL for 2 purposes.

Use GraphQL to load data for widgets when rendering a page.

As you know, a page in Polavi platform is all about Area and Widget. Each widget needs a piece of data. When a client requests a page, there will be a GraphQL server object initialized and every widget can ask it to load the needed data. Here is an example:

$query = create_mutable_var("filter_customer_info_query", "{customer (id: {$request->getCustomer()->getData('customer_id')}) {full_name email}}");
$this->getContainer()
->get(GraphqlExecutor::class)
->waitToExecute([
"query" => $query
])
->then(function($result) use ($request, $response) {
/**@var \GraphQL\Executor\ExecutionResult $result */
if(isset($result->data['customer'])) {
$response->addWidget(
'customer_info',
'customer_dashboard_layout',
10,
get_js_file_url("production/customer/info.js", false),
['action' => $this->getContainer()->get(Router::class)->generateUrl('customer.update', ['id'=>$request->getCustomer()->getData('customer_id')])]
);
}
})->otherwise(function($reason) use ($response) {
$response->addAlert('customer_info_load_error', 'error', 'Something wrong. Please try again');
});

Loading data from client-side using API.

As you know we offer an endpoint for the client to query data. This is an example

const api = ReactRedux.useSelector(state => _.get(state, 'appState.graphqlApi'));
const addGroup = (name) => {
Fetch(
api,
false,
'POST',
{
query: "mutation { createCustomerGroup (name: \"" + name + "\") {status group {id:customer_group_id name:group_name}}}"
},
null,
(response) => {
if(_.get(response, 'payload.data.createCustomerGroup.status') === true) {
setGroups(groups.concat(_.get(response, 'payload.data.createCustomerGroup.group')));
}
},
() => {
dispatch({'type' : ADD_ALERT, 'payload': {alerts: [{id: "create_customer_group_error", message: 'Something wrong, please try again', type: "error"}]}});
}
);
};