This is early phases of a quickstart guide for creating GraphQL APIs in Totara
Examples given is of a plugin named local_todo
which is the plugin todo
in the local
directory.
GraphQL - What is it?
Concepts
Please read our beginners guide for a quick overview of GraphQL's core concepts: /wiki/spaces/~712020ed0261f15a044ee387ddaa27aaf37f0d/pages/840728681.
Schema types
In Totara we separate our schemas based on their use-case. The queries, mutations, types, and so on we define in one schema type has rules about whether it’s available in another. At Totara we have three schema types (use cases) - these are external, internal, and mobile.
External
The external schema type is where the schema for our external APIs is written. This is a special schema type as the external schema is accessible by other schemas. In this way, it can thought of as the “root” schema.
The external schema is any schema file placed under the plugin's webapi/
directory. For example, in the local_todo
plugin, the external schema file would have a file path of server/local/todo/webapi/schema.graphqls
.
Internal
The internal schema type is where the schema for our internal client is written. The internal schema is also called ajax
in the codebase. The internal schema is not shared to another schema and is only available when using the internal schema.
The internal schema is any schema file placed under the plugin’s webapi/ajax/
directory. For example, in the local_todo
plugin, the internal schema file would have a file path of server/local/todo/webapi/ajax/schema.graphqls
.
Please see Developing for the AJAX GraphQL API for more information, or check out our quick-start guide on creating an internal query in this document.
Mobile
The mobile schema type is where the schema for our mobile API is written. The mobile schema is used by our mobile app. This guide doesn’t cover the mobile API at this time.
The mobile schema is any schema file placed under the plugin’s webapi/mobile/
directory. For example, in the local_todo
plugin, the mobile schema file would have a file path of server/local/todo/webapi/mobile/schema.graphqls
.
Quickstart - Create an Internal Query
Internal query - what’s that? Internal queries are our
Create the schema
Create a
webapi
directory in your plugin directory. For example, for mylocal_todo
plugin, this would be in/server/local/todo/
.Under the
webapi
create aajax
directoryCreate a
schema.graphqls
file under theajax
directoryIn
schema.graphqls
let’s define thetodo_item
type by adding the following:type local_todo_item { id: core_id title: String completed_at: core_date }
This adds a new type with
id
,title
andcompleted_at
which we will have access to when using this type.
Create the query
We’ll start off our APIs with creating a query to retrieve a list of the type todo_item
.
In
schema.graphqls
define the result we’ll get back from the query by defining the following type:type local_todo_items_result { items: [local_todo_item!]! }
Notice here we’ve used
!
in two places. This indicates the field is non-nullable. We’ve used this here to say that we always expect an array, by putting the exclamation mark on the outside of the array[...]!
. We also used to to say that the elements of the array cannot be null[my_type!]
.
Now we have the result defined, let’s define the query. We do this by extending the type
Query
and adding our query into it. Let’s do this:extend type Query { local_todo_items: local_todo_items_result! }
This defines that the query
local_todo_items
should return thelocal_todo_items_result
type we defined earlier
Creating the persisted query
The persisted query is a definition of the query for the frontend component to import. We name this file the query name, minus the component name of the plugin we’re working with.
Persisted queries use .graphql
instead of the .graphqls
we used previously for the schema file.
Create the persisted query file in the
webapi/ajax/items.graphql
with the following:query local_todo_items { local_todo_items { items { id title completed_at } } }
Create the backend
Now we need to create the PHP backend to handle the query.
First we’ll create the directory for the query resolver by creating the following directory
/server/local/todo/classes/webapi/resolver/query
Now let’s create the query resolver class,
items.php
in the directory we just created.<?php namespace local_todo\webapi\resolver\query; use core\webapi\execution_context; use core\webapi\query_resolver; use local_todo\entity\item; class items extends query_resolver { /** * @inheritDoc * @throws \coding_exception */ public static function resolve(array $args, execution_context $ec) { global $USER; $items = item::repository() ->where('user_id', $USER->id) ->order_by('id') ->get(); return ['items' => $items]; } }
In this file we’ve defined the query resolve and we’re using the ORM of local_todo to return a list of items.
Now we create the type resolver directory
/server/local/todo/classes/webapi/resolver/type/
and the type resolver for itemitem.php
in the directory.<?php namespace local_todo\webapi\resolver\type; use core\webapi\execution_context; use core\webapi\type_resolver; class item extends type_resolver { /** * @param string $field - The field being requested * @param $source - In the case, source will be our `item` entity class as it's what's returned from the query resolver * @param array $args * @param execution_context $ec * @return mixed|void */ public static function resolve(string $field, $source, array $args, execution_context $ec) { return $source->$field; } }
Testing Queries
Testing Mutations
TODO:
Screenshots of how to set up the dev_graphql_executor
Maybe update the main doc for this
Add how to test generally - the below is not enough, need proper guidance
Tidy up steps - they can be much cleaner
To test this - Using the developer GraphQL API