Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update persisted-queries.md #3616

Merged
merged 17 commits into from
Aug 30, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 232 additions & 5 deletions website/src/docs/hotchocolate/performance/persisted-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,239 @@
title: "Persisted queries"
---

Hi,
This guide will walk you through how standard persisted queries works and how you can set them up with the Hot Chocolate GraphQL server.

We're currently working on the version 11 documentation. Probably right now at this very moment. However, this is an open-source project, and we need any help we can get! You can jump in at any time and help us improve the documentation for hundreds or even thousands of developers!
# What they are

In case you might need help, check out our slack channel and get immediate help from the core contributors or the community itself.
Allows you to pre-register all required queries/mutations from your client on your GraphQl server.

Sorry for any inconvenience, and thank you for being patient!
## Why to use

The ChilliCream Team
There are two main reasons why to use persisted queries:

- Performance - Payload contains only Hash and variables. This will reduce the size of your client application since queries can be removed from the client code at the build time.

- Security - By using persisted queries, the server will accept only known queries / mutations and refuse all others that are not part of persisted "Allowed-List". Useful mainly for public APIs.
tobias-tengler marked this conversation as resolved.
Show resolved Hide resolved

## How it works
damikun marked this conversation as resolved.
Show resolved Hide resolved

This can be done by extracting the queries from your client application at build time and putting them to server query storage. Persisted client output contains queries / mutations that your client application needs to run. Each extracted query has a specific Hash (identifier) and the client uses it with Variables to query data from your server. There is no more query string in the body of your request. The server obtains only concrete identifier + variables and searches for them in AllowedList. If there is a match, the request is directly executed without the need for query parsing else the particular error message is returned to the request initiator.

## Limitations

If your API is used by multiple consumers out of your environment with custom requirements for query / mutation, then this is not for your use case and you may have a look at [automatic persisted queries](automatic-persisted-queries), which probably fits better your scenario.

## How they are stored

Persisted queries are stored close to your server either in the file system or in a Redis cache this helps to reduce request sizes.

[HC provides various places to store this queries](https://www.nuget.org/packages?q=Hotchocolate.PersistedQueries) :
tobias-tengler marked this conversation as resolved.
Show resolved Hide resolved

Nuget namespaces:
- HotChocolate.PersistedQueries.Redis
- HotChocolate.PersistedQueries.FileSystem
- HotChocolate.PersistedQueries.InMemory

# Setup
In the following tutorial, we will walk you through creating a Hot Chocolate GraphQL server and configuring it to support standard persisted queries.

## Step 1: Create a GraphQL server project
damikun marked this conversation as resolved.
Show resolved Hide resolved

Open your preferred terminal and select a directory where you want to add the code of this tutorial.

1. Install the Hot Chocolate GraphQL server template.

```bash
dotnet new -i HotChocolate.Templates.Server
```

2. Create a new Hot Chocolate GraphQL server project.

```bash
dotnet new graphql
```

3. Add the file query storage to your project. (Example use file-storage)

```bash
dotnet add package HotChocolate.PersistedQueries.FileSystem
```

## Step 2: Configure persisted queries on server

Next, we want to configure our GraphQL server to be able to handle persisted query requests. For this, we need to register the corresponding query storage and configure the persisted query request pipeline.

1. Configure GraphQL server to use the persisted query pipeline.

```csharp
public void ConfigureServices(IServiceCollection services)
{
services
.AddRouting()
.AddGraphQLServer()
.AddQueryType<Query>()
.UsePersistedQueryPipeline();
}
```

2. Next, register the file-query storage. (You can use any other, this example use file-storage).
damikun marked this conversation as resolved.
Show resolved Hide resolved

```csharp
public void ConfigureServices(IServiceCollection services)
{
services
.AddRouting()
.AddGraphQLServer()
.AddQueryType<Query>()
.AddReadOnlyFileSystemQueryStorage("./persisted_queries");
}
```

By this HC will search for correspondent query Hash in folder `./persisted_queries`. It is important to store all exported clients persisted  queries in this folder using a specific name and format: `{Hash}.graphql`

Example: `0c95d31ca29272475bf837f944f4e513.graphql`

Now your server knows where to search for the requested query `Id` and loads it in memory in case of a successful match.


## Step 3: Configure persisted  queries on the client (Relay)

This step shows what you need to configure on the client-side or adjust on the server regarding clients. Example show Relay implementation. You can use any other GraphQL client like Apollo. The configuration is always client-related so follow offical client Docs.

[Docs Relay](https://relay.dev/docs/guides/persisted-queries/)

[Docs Apollo](https://www.apollographql.com/docs/apollo-server/performance/apq/)

### What can be different between clients

- Hashing algorithm - Hot Chocolate server is configured to use by default the MD5 hashing algorithm. HotChocolate server comes out of the box with support for MD5, SHA1, and SHA256

- Default Headers and Setup - HC expect the persisted `Id` to be named as` id` in the request header. Some docs like Relay show you to send it under `doc_id` this would not work with HC. You must adjust the client fetch function to use the correct variable.

### Hashing algorithm setup

If adjustmed of algorithm is required you can do it using following sintax:

> **ℹ️** For relay it is not needed. Relay use default MD5 Hash.

```csharp
public void ConfigureServices(IServiceCollection services)
{
services
// Global Services
.AddRouting()
.AddMemoryCache()
.AddSha256DocumentHashProvider(HashFormat.Hex)

// GraphQL server configuration
.AddGraphQLServer()
.AddQueryType<Query>()
.UsePersistedQueryPipeline()
.AddReadOnlyFileSystemQueryStorage("./persisted_queries");
}
```

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add another section here to show how to add the security aspect and limit the server to only allow persisted queries. I will post you some code examples.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you able to throw in an example @michaelstaib ? Thre's a TODO spot just lower. Then I think we can merge this. It'll be helpful for us at AutoGuru too, as this is next on our list for persisted queries now that we've got them all setup for the perf side of things.

### Relay persisted queries setup
damikun marked this conversation as resolved.
Show resolved Hide resolved

#### Enable persisted output (Frontent project part)

Make shure you have installed all packages related to Relay and [compiler](https://www.npmjs.com/package/relay-compiler). This example counts that you are allready user of Relay and will no explain deep how to Setup-it.

Some common packages while working with relay

```
"react-relay": "0.0.0-experimental-4c4107dd",
"relay-runtime": "^10.1.3"
"@types/relay-compiler": "^8.0.0",
"@types/relay-runtime": "^10.1.8",
"relay-compiler": "^11.0.0",
"relay-compiler-language-typescript": "^13.0.4",
"relay-config": "^10.1.3",
"eslint-plugin-relay": "^1.8.2",
```

Extend your relay script in `package.json` by `--persist-output ./path/to/persisted-queries.json"`. Set correct path of your output.

```json
"scripts": {
"relay": "relay-compiler --src ./src --schema ./schema.graphql --persist-output ./path/to/persisted-queries.json"
}
```

This will remove query string from generated files and use Id.

**Before:**
```json
{
"kind": "Request",
"operationKind": "query",
"name": "HotchocolateTestQuery",
"id": null,
"text": "query HotchocolateTestQuery(\n $itemID: ID!\n) {\n node(id: $itemID) {\n ...TestItem_item_2FOrhs\n }\n}\n\nfragment TestItem_item_2FOrhs on Todo {\n text\n isComplete\n}\n",
}
```

**After:**
```json
{
"kind": "Request",
"operationKind": "query",
"name": "TodoItemRefetchQuery",
"id": "3be4abb81fa595e25eb725b2c6a87508", // Our new Hash
"text": null,
}
```

### Setup fetch function
```js
return fetch('/graphql', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
id: operation.id, // Use Id variable!
variables,
// query: operation.text !This must be null (commented -out)
})
```

### Generate persisted  output

```bash
yarn run relay
// Or
npm start relay
```

The Relay output is `persisted-queries.json` All-In-One-File.

Example relay output
```json
{
"b342480227f3ce0ec9e120e6147dd4fa": "query UserProviderQuery {\n ...UserProviderMe_Fragment\n me {\n id\n systemid\n etc... }\n},
"991ed3080b704c933eb09b011ef03998": "mutation ProjectMilestonesSetStatusMutation {\n $request: SetMailstoneS etc... },
},
```

> ⚠️ **Note:** HotChocolate server **requires** all persisted queries to be stored in the configured directory as separate files named by Hash (id) and ending as `.graphql`. Example: `0c95d31ca29272475bf837f944f4e513.graphql`. To fit Relay output and HC you need to convert `persisted-queries.json`. **You can write a custom converter for that.**

[You can use one awailable under Examples.](https://github.com/ChilliCream/hotchocolate-examples/blob/master/misc/Persisted-queries/relay-persisted-converter.js)

Put it after relay generation to convert the generated output to HC format. It is `node.js` script.

```json
"scripts": {
"relay": "relay-compiler --persist-output persisted-queries.json && node relay-presisted-converter.js persisted-output/persisted-queries.json persisted-output",
}
```

Run new generation and you are ready to go with persisted-queries.

```bash
yarn run relay
// Or
npm start relay
```

The script require source path to `persisted-queries.json` and output directory where the files will be generated. For more info read the header of the script.