2020-10-19 23:56:17 +02:00
|
|
|
---
|
|
|
|
title: Tips & Tricks
|
|
|
|
summary: Miscellaneous useful tips for working with your GraphQL schema
|
|
|
|
---
|
|
|
|
|
|
|
|
# Tips & Tricks
|
|
|
|
|
|
|
|
[alert]
|
|
|
|
You are viewing docs for a pre-release version of silverstripe/graphql (4.x).
|
|
|
|
Help us improve it by joining #graphql on the [Community Slack](https://www.silverstripe.org/blog/community-slack-channel/),
|
|
|
|
and report any issues at [github.com/silverstripe/silverstripe-graphql](https://github.com/silverstripe/silverstripe-graphql).
|
|
|
|
Docs for the current stable version (3.x) can be found
|
|
|
|
[here](https://github.com/silverstripe/silverstripe-graphql/tree/3)
|
|
|
|
[/alert]
|
|
|
|
|
2020-12-01 09:27:52 +01:00
|
|
|
## Getting the type name for a model class
|
|
|
|
|
|
|
|
Often times, you'll need to know the name of the type given a class name. There's a bit of context to this.
|
|
|
|
|
|
|
|
### Getting the type name at build time
|
|
|
|
|
|
|
|
If you need to know the name of the type _during the build_, e.g. creating the name of an operation, field, query, etc,
|
|
|
|
you should use the `Build::requireActiveBuild()` accessor. This will get you the schema that is currently being built,
|
|
|
|
and throw if no build is active. A more tolerant method is `getActiveBuild()` which will return null if no schema
|
|
|
|
is being built.
|
|
|
|
|
|
|
|
```php
|
|
|
|
Build::requireActiveBuild()->findOrMakeModel($className)->getName();
|
|
|
|
```
|
|
|
|
|
|
|
|
### Getting the type name from within your app
|
|
|
|
|
|
|
|
If you need the type name during normal execution of your app, e.g. to display in your UI, you can rely
|
|
|
|
on the cached typenames, which are persisted alongside your generated schema code.
|
|
|
|
|
|
|
|
```php
|
|
|
|
Schema::create('default')->getTypeNameForClass($className);
|
|
|
|
```
|
|
|
|
|
|
|
|
### Why is there a difference?
|
|
|
|
|
|
|
|
It is expensive to load all of the schema config. The `getTypeNameForClass` function avoids the need to
|
|
|
|
load the config, and reads directly from the cache. To be clear, the following is functionally equivalent,
|
|
|
|
but slow:
|
|
|
|
|
|
|
|
```php
|
|
|
|
Schema::create('default')
|
|
|
|
->loadFromConfig()
|
|
|
|
->findOrMakeModel($className)
|
|
|
|
->getName();
|
|
|
|
```
|
|
|
|
|
2020-10-19 23:56:17 +02:00
|
|
|
## Persisting queries
|
|
|
|
|
|
|
|
A common pattern in GraphQL APIs is to store queries on the server by an identifier. This helps save
|
|
|
|
on bandwidth, as the client need not put a fully expressed query in the request body, but rather a
|
|
|
|
simple identifier. Also, it allows you to whitelist only specific query IDs, and block all other ad-hoc,
|
|
|
|
potentially malicious queries, which adds an extra layer of security to your API, particularly if it's public.
|
|
|
|
|
|
|
|
To implement persisted queries, you need an implementation of the
|
|
|
|
`SilverStripe\GraphQL\PersistedQuery\PersistedQueryMappingProvider` interface. By default, three are provided,
|
|
|
|
which cover most use cases:
|
|
|
|
|
|
|
|
* `FileProvider`: Store your queries in a flat JSON file on the local filesystem.
|
|
|
|
* `HTTPProvider`: Store your queries on a remote server and reference a JSON file by URL.
|
|
|
|
* `JSONStringProvider`: Store your queries as hardcoded JSON
|
|
|
|
|
|
|
|
### Configuring query mapping providers
|
|
|
|
|
|
|
|
All of these implementations can be configured through `Injector`.
|
|
|
|
|
|
|
|
[notice]
|
|
|
|
Note that each schema gets its own set of persisted queries. In these examples, we're using the `default`schema.
|
|
|
|
[/notice]
|
|
|
|
|
|
|
|
#### FileProvider
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
SilverStripe\Core\Injector\Injector:
|
|
|
|
SilverStripe\GraphQL\PersistedQuery\PersistedQueryMappingProvider:
|
|
|
|
class: SilverStripe\GraphQL\PersistedQuery\FileProvider
|
|
|
|
properties:
|
|
|
|
schemaMapping:
|
|
|
|
default: '/var/www/project/query-mapping.json'
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
A flat file in the path `/var/www/project/query-mapping.json` should contain something like:
|
|
|
|
|
|
|
|
```json
|
|
|
|
{"someUniqueID":"query{validateToken{Valid Message Code}}"}
|
|
|
|
```
|
|
|
|
|
|
|
|
[notice]
|
|
|
|
The file path must be absolute.
|
|
|
|
[/notice]
|
|
|
|
|
|
|
|
#### HTTPProvider
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
SilverStripe\Core\Injector\Injector:
|
|
|
|
SilverStripe\GraphQL\PersistedQuery\PersistedQueryMappingProvider:
|
|
|
|
class: SilverStripe\GraphQL\PersistedQuery\HTTPProvider
|
|
|
|
properties:
|
|
|
|
schemaMapping:
|
|
|
|
default: 'http://example.com/myqueries.json'
|
|
|
|
```
|
|
|
|
|
|
|
|
A flat file at the URL `http://example.com/myqueries.json` should contain something like:
|
|
|
|
|
|
|
|
```json
|
|
|
|
{"someUniqueID":"query{readMembers{Name+Email}}"}
|
|
|
|
```
|
|
|
|
|
|
|
|
#### JSONStringProvider
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
SilverStripe\Core\Injector\Injector:
|
|
|
|
SilverStripe\GraphQL\PersistedQuery\PersistedQueryMappingProvider:
|
|
|
|
class: SilverStripe\GraphQL\PersistedQuery\HTTPProvider
|
|
|
|
properties:
|
|
|
|
schemaMapping:
|
|
|
|
default: '{"myMutation":"mutation{createComment($comment:String!){Comment}}"}'
|
|
|
|
```
|
|
|
|
|
|
|
|
The queries are hardcoded into the configuration.
|
|
|
|
|
|
|
|
### Requesting queries by identifier
|
|
|
|
|
|
|
|
To access a persisted query, simply pass an `id` parameter in the request in lieu of `query`.
|
|
|
|
|
|
|
|
`GET http://example.com/graphql?id=someID`
|
|
|
|
|
|
|
|
[notice]
|
|
|
|
Note that if you pass `query` along with `id`, an exception will be thrown.
|
|
|
|
[/notice]
|
|
|
|
|
|
|
|
## Query caching (Caution: EXPERIMENTAL)
|
|
|
|
|
|
|
|
The `QueryCachingMiddleware` class is an experimental cache layer that persists the results of a GraphQL
|
|
|
|
query to limit unnecessary calls to the database. The query cache is automatically expired when any
|
|
|
|
DataObject that it relies on is modified. The entire cache will be discarded on `?flush` requests.
|
|
|
|
|
|
|
|
To implement query caching, add the middleware to your `QueryHandlerInterface`
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
SilverStripe\Core\Injector\Injector:
|
|
|
|
SilverStripe\GraphQL\QueryHandler\QueryHandlerInterface.default:
|
|
|
|
class: SilverStripe\GraphQL\QueryHandler\QueryHandler
|
|
|
|
properties:
|
|
|
|
Middlewares:
|
|
|
|
cache: '%$SilverStripe\GraphQL\Middleware\QueryCachingMiddleware'
|
|
|
|
```
|
|
|
|
|
|
|
|
And you will also need to apply an extension to all DataObjects:
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
SilverStripe\ORM\DataObject:
|
|
|
|
extensions:
|
|
|
|
- SilverStripe\GraphQL\Extensions\QueryRecorderExtension
|
|
|
|
```
|
|
|
|
|
|
|
|
[warning]
|
|
|
|
This feature is experimental, and has not been thoroughly evaluated for security. Use at your own risk.
|
|
|
|
[/warning]
|
|
|
|
|
|
|
|
|
|
|
|
## Schema introspection
|
|
|
|
|
|
|
|
Some GraphQL clients such as [Apollo](http://apollographql.com) require some level of introspection
|
|
|
|
into the schema. While introspection is [part of the GraphQL spec](http://graphql.org/learn/introspection/),
|
|
|
|
this module provides a limited API for fetching it via non-graphql endpoints. By default, the `graphql/`
|
|
|
|
controller provides a `types` action that will return the type schema (serialised as JSON) dynamically.
|
|
|
|
|
|
|
|
*GET http://example.com/graphql/types*
|
|
|
|
```js
|
|
|
|
{
|
|
|
|
"data":{
|
|
|
|
"__schema":{
|
|
|
|
"types":[
|
|
|
|
{
|
|
|
|
"kind":"OBJECT",
|
|
|
|
"name":"Query",
|
|
|
|
"possibleTypes":null
|
|
|
|
}
|
|
|
|
// etc ...
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
As your schema grows, introspecting it dynamically may have a performance hit. Alternatively,
|
|
|
|
if you have the `silverstripe/assets` module installed (as it is in the default SilverStripe installation),
|
|
|
|
GraphQL can cache your schema as a flat file in the `assets/` directory. To enable this, simply
|
|
|
|
set the `cache_types_in_filesystem` setting to `true` on `SilverStripe\GraphQL\Controller`. Once enabled,
|
|
|
|
a `types.graphql` file will be written to your `assets/` directory on `flush`.
|
|
|
|
|
|
|
|
When `cache_types_in_filesystem` is enabled, it is recommended that you remove the extension that
|
|
|
|
provides the dynamic introspection endpoint.
|
|
|
|
|
|
|
|
```php
|
|
|
|
use SilverStripe\GraphQL\Controller;
|
|
|
|
use SilverStripe\GraphQL\Extensions\IntrospectionProvider;
|
|
|
|
|
|
|
|
Controller::remove_extension(IntrospectionProvider::class);
|
|
|
|
```
|