Troubleshooting Connectors
Fixes and workarounds for general issues and specific errors
Return debug info in GraphQL responses
To diagnose issues with your connectors, you can return debugging information as part of each GraphQL response, including the details of each HTTP request and response from your connectors.
First, run your router in development mode. This happens automatically when using rover dev
. If you're running a router manually, you can pass it the --dev
CLI argument.
Apollo Sandbox presents request, response, and mapping information in a panel after you execute an operation.
Satisfiability errors
If you see a composition error about "satisfiability", you've run into one of the most important concepts when composing various REST endpoints into a coherent graph.
Consider a schema with two connectors that hit different endpoints and get differing representations of a type. The goal is to merge the representations into a unified type so that clients can access all the fields without caring about the underlying data sources.
1type Query {
2 posts: [Post]
3 @connect(
4 http: { GET: "https://api.example.com/v1/posts" }
5 selection: "id title createdAt"
6 )
7
8 post(id: ID!): Post
9 @connect(
10 http: { GET: "https://api.example.com/v2/posts/{$args.id}" }
11 selection: "id title body"
12 )
13}
14
15type Post {
16 id: ID!
17 title: String
18 body: String
19 createdAt: String
20}
These connectors don't form a coherent graph. We can't reach body
from the Query.posts
field, and we can't reach createdAt
from the Query.post(id:)
field. The composition error looks like this:
1SATISFIABILITY_ERROR: The following supergraph API query:
2{
3 posts {
4 body
5 }
6}
7cannot be satisfied by the subgraphs because:
8- from subgraph "posts":
9 - cannot find field "Post.body".
10 - cannot move to subgraph "posts", which has field "Post.body", because type "Post" has no @key defined in subgraph "posts".
11SATISFIABILITY_ERROR: The following supergraph API query:
12{
13 post(id: "<any id>") {
14 createdAt
15 }
16}
17cannot be satisfied by the subgraphs because:
18- from subgraph "posts":
19 - cannot find field "Post.createdAt".
20 - cannot move to subgraph "posts", which has field "Post.createdAt", because type "Post" has no @key defined in subgraph "posts".
@key
directive and an entity resolver for the type.For a connector, adding entity: true
is the equivalent of defining a @key
and an entity resolver.The first issue (body
) is easily solved with entity connectors. By adding entity: true
to the connector on Query.post(id:)
, the query planner can make subsequent fetches to fetch that field.
1type Query {
2 post(id: ID!): Post
3 @connect(
4 http: { GET: "https://api.example.com/v2/posts/{$args.id}" }
5 selection: "id title body"
6 entity: true
7 )
8}
The second issue (createdAt
) doesn't have a straightforward solution. We need an endpoint that takes the ID of a Post
and returns the createdAt
field. The simplest solution is to add a second connector to the Query.post(id:)
field:
1type Query {
2 post(id: ID!): Post
3 @connect(
4 http: { GET: "https://api.example.com/v2/posts/{$args.id}" }
5 selection: "id title body"
6 entity: true
7 )
8 @connect(
9 http: { GET: "https://api.example.com/v1/posts/{$args.id}" }
10 selection: "id createdAt"
11 entity: true
12 )
13}
Now there is always a way to resolve all fields, regardless of which Query root field the client uses.
entity: true
.Circular references
If you get a composition error about circular references, you've run into a connectors preview limitation. Direct circular references, such as User.friends: [User]
, are not supported. Indirect circular references, such as User.company: Business
and Business.employees: [User]
, are supported using certain patterns.
More details
selection
of a type can't refer to itself. For example, this is invalid:1type Query {
2 user(id: ID!): User
3 @connect(
4 http: { GET: "https://api.example.com/users/{$args.id}" }
5 selection: """
6 id
7 name
8 friends { # Circular reference to User
9 id
10 }
11 """
12 )
13}
14
15type User {
16 id: ID!
17 name: String
18 friends: [User]
19}
1type Query {
2 user(id: ID!): User
3 @connect(
4 http: { GET: "https://api.example.com/users/{$args.id}" }
5 selection: """
6 id
7 name
8 favoriteBooks { # First reference to Book
9 id
10 author {
11 id
12 books { # Circular reference to Book
13 id
14 }
15 }
16 }
17 """
18 )
19}
20
21type User {
22 id: ID!
23 name: String
24 favoriteBooks: [Book]
25}
26
27type Book {
28 id: ID!
29 author: Author
30}
31
32type Author {
33 id: ID!
34 books: [Book]
35}
1type Query {
2 user(id: ID!): User
3 @connect(
4 http: { GET: "https://api.example.com/users/{$args.id}" }
5 selection: """
6 id
7 name
8 favoriteBooks {
9 id
10 title
11 author {
12 id
13 # No reference to books here
14 }
15 }
16 """
17 )
18}
19
20type User {
21 id: ID!
22 name: String
23 favoriteBooks: [Book]
24}
25
26type Book {
27 id: ID!
28 title: String!
29 author: Author
30 @connect(
31 http: { GET: "https://api.example.com/books/{$this.id}/author" }
32 selection: """
33 id
34 name
35 # No reference to books
36 """
37 )
38}
39
40type Author {
41 id: ID!
42 books: [Book]
43 @connect(
44 http: { GET: "https://api.example.com/authors/{$this.id}/books" }
45 selection: """
46 id
47 title
48 # No reference to author
49 """
50 )
51}