Skip to content

Commit

Permalink
Update persisted query documentation (#5889)
Browse files Browse the repository at this point in the history
  • Loading branch information
tobias-tengler committed Feb 27, 2023
1 parent 5740150 commit f4cd5a0
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 15 deletions.
4 changes: 3 additions & 1 deletion cSpell.json
Expand Up @@ -36,7 +36,9 @@
"cachable",
"fricking",
"runtimes",
"NATS"
"NATS",
"cypher",
"sublicensable"
],
"ignoreWords": [
"Specwise",
Expand Down
47 changes: 44 additions & 3 deletions website/src/docs/hotchocolate/v13/performance/persisted-queries.md
Expand Up @@ -22,17 +22,17 @@ Extracting queries is supported by client libraries like [Relay](https://relay.d
# Benefits

<!-- There are two main benefits to using persisted queries: -->
There are two main benefits to using persisted queries:

**Performance**

- Only a hash and optionally variables need to be sent to the server, reducing network traffic.
- Queries no longer need to be embedded into the client code, reducing the bundle size in the case of websites.
- Hot Chocolate can optimize the execution of persisted queries, as they will always be the same.

<!-- **Security**
**Security**

The server can be tweaked to [only accept persisted queries](#blocking-regular-queries) and refuse queries created by a client at runtime. This is useful mainly for public APIs. -->
The server can be tweaked to [only execute persisted queries](#blocking-regular-queries) and refuse any other queries provided by a client. This gets rid of a whole suite of potential attack vectors, since malicious actors can no longer craft and execute harmful queries against your GraphQL server.

# Usage

Expand Down Expand Up @@ -131,6 +131,47 @@ AddSha256DocumentHashProvider(HashFormat.Base64)

> Note: [Relay](https://relay.dev) uses the MD5 hashing algorithm - no additional Hot Chocolate configuration is required.
## Blocking regular queries

If you want to disallow any dynamic queries, you can enable `OnlyAllowPersistedQueries`:

```csharp
builder.Services
.AddGraphQLServer()
// Omitted for brevity
.ModifyRequestOptions(o => o.OnlyAllowPersistedQueries = true);
```

This will block any dynamic queries that do not contain the `id` of a persisted query.

You might still want to allow the execution of dynamic queries in certain circumstances. You can override the `OnlyAllowPersistedQueries` rule on a per-request basis, using the `AllowNonPersistedQuery` method on the `IQueryRequestBuilder`. Simply implement a custom [IHttpRequestInterceptor](/docs/hotchocolate/v13/server/interceptors#ihttprequestinterceptor) and call `AllowNonPersistedQuery` if a certain condition is met:

```csharp
builder.Services
.AddGraphQLServer()
// Omitted for brevity
.AddHttpRequestInterceptor<CustomHttpRequestInterceptor>()
.ModifyRequestOptions(o => o.OnlyAllowPersistedQueries = true);

public class CustomHttpRequestInterceptor : DefaultHttpRequestInterceptor
{
public override ValueTask OnCreateAsync(HttpContext context,
IRequestExecutor requestExecutor, IQueryRequestBuilder requestBuilder,
CancellationToken cancellationToken)
{
if (context.Request.Headers.ContainsKey("X-Developer"))
{
requestBuilder.AllowNonPersistedQuery();
}

return base.OnCreateAsync(context, requestExecutor, requestBuilder,
cancellationToken);
}
}
```

In the above example we would allow requests containing the `X-Developer` header to execute dynamic queries. This isn't particularly secure, but in your production application you could replace this check with an authorization policy, an API key or whatever fits your requirement.

# Client expectations

A client is expected to send an `id` field containing the query hash instead of a `query` field.
Expand Down
Expand Up @@ -2,13 +2,9 @@
title: "Persisted Queries"
---

This guide will walk you through how persisted queries work and how you can set them up with Strawberry Shake.
Persisted queries allow you to improve the performance of your GraphQL requests and the security of your GraphQL server.

# How it works

Persisted queries is a feature that Facebook uses internally for a long time to improve the performance of Facebook with their relay client.

During development you can write and edit the queries in your application. When you start and debug your application it will use these queries to interact with the GraphQL server.
Normally, when working with GraphQL, your client sends the **full** query to your GraphQL server:

```mermaid
sequenceDiagram
Expand All @@ -18,9 +14,9 @@ sequenceDiagram
GraphQL Server->>Generated Client: Response: { "data": { "foo": { ... } } }
```

Once you package your client application however the GraphQL queries are compiled, removed from the client code and exported into a query directory.
These queries can get quite big and it doesn't make much sense to always send the full query document to your server. If your client is developed in close collaboration with your GraphQL server and your GraphQL endpoint isn't public, it also doesn't make sense to allow clients to send _any_ GraphQL query they desire.

The query directory can then be uploaded to your GraphQL server. Whenever the client wants to send a GraphQL request to the server it will insert into that request the hash of the extracted GraphQL query instead of the GraphQL query itself.
With persisted queries you extract the GraphQL operations out of your client and export them to your server. During that process each operation is assigned a unique Id. Your client can now simply send such an Id to your server to request a specific operation. You no longer need to send the full query document:

```mermaid
sequenceDiagram
Expand All @@ -30,8 +26,71 @@ sequenceDiagram
GraphQL Server->>Generated Client: Response: { "data": { "foo": { ... } } }
```

# Setup
You can learn more about the benefits of persisted queries and how you can setup them up in your Hot Chocolate GraphQL server [here](/docs/hotchocolate/v13/performance/persisted-queries/#benefits).

# Usage

To enable persisted queries, specify a `GraphQLPersistedQueryFormat` property in a MSBuild `PropertyGroup` in the `.csproj` of your Strawberry Shake application:

```xml
<PropertyGroup>
<GraphQLPersistedQueryFormat>./persisted-queries</GraphQLPersistedQueryFormat>
</PropertyGroup>
```

The value in `GraphQLPersistedQueryFormat` should be the path to a directory relative to the project root directory. An empty value disables the feature and is also the default.

If you now re-build your application, the directory specified by `GraphQLPersistedQueryFormat` should be created and contain all of your persisted queries.

In the following tutorial, we will walk you through creating a Strawberry Shake GraphQL client and configuring it to support persisted queries.
You can now make these persisted queries available to your GraphQL server, by for example uploading the directory.

During development you likely want the freedom to work with dynamic queries, so you can also [conditionally export](#conditional-export) your persisted queries.

## Output formats

Per default we create a file in the format of `<hash>.graphql` in the directory specified by the `GraphQLPersistedQueryFormat` property, for each GraphQL operation in your project.

Where the content of the file is the actual GraphQL operation and the `<hash>` is the computed hash of that operation, based on the selected [hashing algorithm](#hashing-algorithms).

Alternatively we offer the `relay` output format, where we create a single `queries.json` file in the output directory. It contains a JSON object, mapping a hash value to a GraphQL operation.

```json
{
"<hash1>": "query GetAssets { ... }",
"<hash2>": "query GetPrices { ... }"
}
```

The hash (the key) is again calculated from the GraphQL operation (the value), based on the selected [hashing algorithm](#hashing-algorithms).

You can specify the format you'd like using the `GraphQLPersistedQueryFormat` property:

```xml
<PropertyGroup>
<GraphQLPersistedQueryFormat>relay</GraphQLPersistedQueryFormat>
</PropertyGroup>
```

Possible values are `default` and `relay`.

## Hashing algorithms

Per default query hashes are calculated using the MD5 hashing algorithm, but you can also use other hashing formats:

```xml
<PropertyGroup>
<GraphQLRequestHash>sha256</GraphQLRequestHash>
</PropertyGroup>
```

Possible values are `md5`, `sha1` and `sha256`.

## Conditional export

Since `GraphQLPersistedQueryFormat` and the other settings are MSBuild properties, you can easily apply conditions to them:

```xml
<GraphQLPersistedQueryFormat Condition="'$(Configuration)' == 'Release' ">
```

## Step 1: Create a console project
In the above example, persisted queries would only be exported if you are building your application in the `Release` configuration.

0 comments on commit f4cd5a0

Please sign in to comment.