/
Developing GraphQL APIs for Totara

Developing GraphQL APIs for Totara

This page provides detailed background information on how our GraphQL APIs are organised. If you want to implement a GraphQL service, you might be more interested in the Implementing GraphQL services page.

For more background on the structure and format of GraphQL requests, see Using GraphQL APIs.

Endpoint types

We offer a number of different GraphQL APIs, each with its own separate endpoint (URL) where it can be accessed. You can find out more about the differences between these APIs on the Available APIs page.

Each endpoint has its own schema, defined via schema files, which means different services are available in different endpoints.

About schema files

Our implementation of GraphQL uses schema files written in SDL to define the API services that are available. Individual schema files (which are created within components and plugins) are combined by our schema loader into a single schema per endpoint type.

The naming of the objects within the schema must follow a specific convention, and the name that is used will determine the location in the server codebase where the specific object will be handled. See the Locations for files relating to services section below for details.

Building the schema

In order to allow plugins to specify their own schema in an extensible way, the overall schema for an endpoint is built from individual files located across the codebase. Schema building happens dynamically in code when required, but is cached as it can be a time-consuming operation to generate.

There are a few ways for a developer to access the full schema for a specific endpoint type:

Via introspection

Often it is not necessary to download the schema directly, as GraphQL clients can request details on the schema structure via an introspection query to the endpoint itself. In this case, the endpoint will build the schema if necessary and serve the result directly. Note that this is only available for the developer API and the external API when the enable_introspection admin setting is enabled.

Via a schema URL

When the development API is enabled, the full schema can be downloaded via the following URL:

https://YOUR-SITE-URL/totara/webapi/dev_graphql_schema.php

When the external API is enabled, the external schema can be downloaded by a logged-in user with access to the API reference documentation via:

https://YOUR-SITE-URL/totara/api/documentation/schema.php

Build via CLI

There is a command line (CLI) PHP script within the codebase, which can be run to generate schema for a specific endpoint:

Produces a complete GraphQL schema for the specified type by concatenating individual schema files. The specified file will be overwritten if it already exists.

php ./server/totara/api/cli/generate_external_schema.php -t=ajax -f=totara.graphqls

Options:
-h, --help            Print out this help
-t, --type            Endpoint type e.g. 'ajax', 'dev', 'mobile' or 'external' (default)
-f, --file            Writes the schema to the given file. Use '-' for stdout

Build via API reference docs build script

There is another command line (CLI) PHP script which builds the files needed to generate the API reference documentation. This will generate schema files for all endpoint types along with the required metadata.json files, via the command:

php server/totara/api/cli/prep_api_docs.php

See the Extending API documentation page for more details on how to build the reference documentation.

Locations for files relating to services

The naming of a GraphQL file uses a consistent naming convention, which determines where the code that is executed is stored in the server codebase:

Type in
GraphQL
Name in GraphQLLocation in PHP codeClass in PHP code
query{$component_name}_{$query_name}

server/{$component_path}/classes/webapi/resolver/query/{$query_name}.php

[1]

\$component_name\webapi\resolver\query\{$query_name}
mutation{$component_name}_{$mutation_name}server/{$component_path}/classes/webapi/resolver/mutation/{$mutation_name}.php\$component_name\webapi\resolver\mutation\{$mutation_name}
type{$component_name}_{$type_name}server/{$component_path}/classes/webapi/resolver/type/{$type_name}.php\$component_name\webapi\resolver\type\{$type_name}
input{$component_name}_{$type_name}server/{$component_path}/classes/webapi/resolver/type/{$type_name}.php\$component_name\webapi\resolver\type\{$type_name}
union{$component_name}_{$union_name}server/{$component_path}/classes/webapi/resolver/union/{$union_name}.php\$component_name\webapi\resolver\union\{$union_name}
persisted query or mutation{$component_name}_{$query_name}

server/{$component_path}/webapi/{$endpoint_type}/{$query_name}.graphql [2]

N/A
schema fileN/A

server/{$component_path}/webapi/{$filename}.graphqls

server/{$component_path}/webapi/{$endpoint_type}/{$filename}.graphqls

[3]

N/A

[1]  {$component_name} is the 'frankenstyle' name made up of the component type plus component name, e.g. 'mod_perform' or 'block_current_learning'. This maps to a specific {$component_path} for the plugin as determined by core_component::get_component_directory().

[2] Persiste