Making HTTP Requests

Learn how to make HTTP requests with Apollo Connectors


Preview
Apollo Connectors are currently in public preview. You need an Apollo account with a GraphOS Trial or Enterprise plan to get started. Update to router v.2.0.0-preview.4 and federation v.2.10.0-preview.4 to access the latest features.

In this guide, you'll learn about:

  • The API requirements for connector compatibility

  • How to make HTTP requests with connectors

  • Connectors error handling

tip
Just getting start with Apollo Connectors? Try out the quickstart to build your first connector.

API requirements for connector compatibility

Connectors make it easy to build a GraphQL API using your existing HTTP/JSON APIs. They're flexible and work with most APIs, especially if they conform to the following guidelines.

Content Type

Connectors expect a JSON response body. If your API endpoint doesn't default to a JSON content type, you may need to specify an Accept: application/json header using the mechanisms described below.

Some APIs may have a different mechanism for specifying the response content type, such as a query parameter or file extension. Ensure your connector is configured appropriately to request a JSON response.

note
Connectors don't require a Content-Type response header. By default, they interpret any response body as JSON.

Core JSON-over-HTTP requirements

Connectors work with APIs that follow these JSON-over-HTTP principles:

  • Your endpoints accept requests using these HTTP verbs: GET, POST, PUT, PATCH, DELETE.

  • Your endpoints use path segments and query string parameters for inputs, such as /users/123 or /users?limit=10.

  • For POST, PUT, and PATCH requests with request bodies, your endpoints accept JSON.

  • Your endpoints respond with JSON values—typically objects, but any JSON value is allowed.

  • Your endpoints always return a status code between 200 and 299 for successful requests.

  • Your endpoints provide a known set of properties. GraphQL doesn't have a Map type, so you must define the mapping between JSON properties and GraphQL fields in advance. (You can map an arbitrary map to a scalar field using a custom scalar type.)

Graph-like conventions

Connectors work best with APIs that follow these graph-like conventions for representing and retrieving entities:

  • You represent your entities across various endpoints using the same identifiers. For example, a User is consistently identified by 123 across all endpoints.

  • You provide endpoints to fetch an entity by its primary key. For example, /users/123 returns the user with ID 123.

  • Your endpoints use simple values for foreign keys. For example, a User object has a companyId field containing the ID of the company it belongs to. (Using a full URL such as {"company": "http://myapi.com/company/234"} is difficult to work with).

  • When appropriate, your endpoints embed related entities in the response. For example, a User object might include a company field that contains a Company object.

Security

To ensure secure interactions, your API should follow these security best practices.

  • Your endpoints perform their own authentication and authorization checks as necessary. You can add layers of additional security using GraphOS Router's security features.

  • Your endpoints accept authentication information in request headers. The headers can come directly from the client or be injected by the router.

  • Your endpoints don't use query parameters for sensitive information. The router emits full URLs in logs and traces.

Making HTTP requests

This section covers the essentials of making HTTP requests using connectors. It includes examples specifying the HTTP method and URL, setting headers, and defining request bodies.

HTTP Methods

You specify the HTTP method for your request in the http argument of a @connect directive. You must specify one of the following HTTP methods: GET, POST, PUT, PATCH, or DELETE, followed by the URL.

Example GET request

GraphQL
Example connector for GET HTTP method
1type Query {
2  products: [Product]
3    @connect(
4      http: { GET: "https://myapi.dev/products" }
5      selection: "id"
6    )
7}

Example POST request

GraphQL
Example connector for POST HTTP method
1type Mutation {
2  createProduct(name: String!): Product
3    @connect(
4      http: {
5        POST: "https://myapi.dev/products"
6        body: "name: $args.name"
7      }
8      selection: "id"
9    )
10}

Example PUT request

GraphQL
Example connector for PUT HTTP method
1type Mutation {
2  setProduct(id: ID!, name: String!): Product
3    @connect(
4      http: {
5        PUT: "https://myapi.dev/products/{$args.id}"
6        body: "name: $args.name"
7      }
8      selection: "id"
9    )
10}

Example PATCH request

GraphQL
Example connector for PATCH HTTP method
1type Mutation {
2  updateProduct(id: ID!, name: String!): Product
3    @connect(
4      http: { PATCH: "https://myapi.dev/products/{$args.id}?name={$args.name}" }
5      selection: "id"
6    )
7}

Example DELETE request

GraphQL
Example connector for DELETE HTTP method
1type Mutation {
2  deleteProduct(id: ID!): Product
3    @connect(
4      http: { DELETE: "https://myapi.dev/products/{$args.id}" }
5      selection: "id"
6    )
7}

Dynamic URLs

URLs can be dynamic and use values from field arguments, sibling fields, and router configuration. The dynamic parts appear between curly braces ({}). For example, you can use field arguments as path segments:

GraphQL
Example connector with dynamic path segments
1type Query {
2  product(storeId: ID!, productId: ID!): Product
3    @connect(
4      http: {
5        GET: "https://myapi.dev/store/{$args.storeId}/products/{$args.productId}"
6      }
7      selection: "id"
8    )
9}

Path segment values are URL-encoded. If the value contains a /, it is encoded as %2F.

Query parameters

You add query parameters to the URL by appending them with a ? and separating them with &. You can use field arguments to create query parameters:

GraphQL
Example connector with dynamic query parameters
1type Query {
2  products(limit: Int = 10, offset: Int): Product
3    @connect(
4      http: {
5        GET: "https://myapi.dev/products?limit={$args.limit}&offset={$args.offset}"
6      }
7      selection: "id"
8    )
9}

When a value is missing or null, the result is an empty string. In the previous example, if offset in {$args.offset} is not provided, the URI will end with &offset=. Parameter names still appear in the URI if the parameter value is missing or null.

tip
URL templates support the full mapping language in expressions as long as the expression evaluates to a simple scalar value—not an object or array.

Headers

You add headers to your HTTP requests using the http.headers argument. Like with URIs, you can define header values using a combination of fixed values and dynamic mapping expressions in curly braces ({}).

The following example uses a static value for the x-api-version header and a dynamic value with the $config variable for the Authorization header:

GraphQL
Example connector with headers
1type Query {
2  products: [Product]
3    @connect(
4      http: {
5        GET: "https://myapi.dev/products"
6        headers: [
7          { name: "x-api-version", value: "2024-01-01" }
8          { name: "Authorization", value: "Bearer {$config.token}" }
9        ]
10      }
11      selection: "id"
12    )
13}

You can also propagate headers from the incoming client request using the from argument:

GraphQL
Example connector with client header propagation
1type Query {
2  products: [Product]
3    @connect(
4      http: {
5        GET: "https://myapi.dev/products"
6        headers: [{ name: "Authorization", from: "Authorization" }]
7      }
8      selection: "id"
9    )
10}
note
The router's header propagation configuration doesn't affect connector requests.

Sharing configuration with @source

You can use the @source directive to share a partial URL and headers with multiple connectors. To use a @source:

  1. Import the @source directive from the connectors @link.

  2. Apply the @source directive to the schema.

    1. Define a baseURL.

    2. Optionally define headers, which can't contain $this or $args.

  3. Set the source: in each @connect directive that should use the shared configuration.

  4. Use a partial URL in the @connect directive containing only the path and query parameters (no scheme, host, etc.).

  5. Define headers in @connect to override headers from the @source with the same name.

The connector request below resolves to https://myapi.example.com/v1/products?client=router&first=10. It includes the X-API-Key header from the @source configuration and the X-Product-Type header from the @connect configuration.

GraphQL
Example connector with a related @source
1extend schema
2  @link(
3    url: "https://specs.apollo.dev/connect/v0.1"
4    import: ["@connect", "@source"]
5  )
6  @source(
7    name: "myapi"
8    http: {
9      baseURL: "https://myapi.example.com/v1?client=router"
10      headers: [{ name: "X-API-Key", value: "{$config.api_key}" }]
11    }
12  )
13
14type Query {
15  products(first: Int = 10): [Product]
16    @connect(
17      source: "myapi"
18      http: {
19        GET: "/products?first={$args.first}"
20        headers: [{ name: "X-Product-Type", from: "Product-Type" }]
21      }
22      selection: "id"
23    )
24}

Request body

The http.body field defines a JSON body to send with the request using the mapping language.

GraphQL
Example connector with a request body
1type Mutation {
2  createProduct(input: CreateProductInput!): Product
3    @connect(
4      http: {
5        POST: "https://myapi.dev/products"
6        body: """
7        $args.input {
8          name
9          price
10        }
11        """
12      }
13      selection: "id"
14    )
15}

Form URL encoding

By adding a Content-Type header of exactly application/x-www-form-urlencoded, GraphOS Router encodes the request body as a form URL encoded string.

GraphQL
Form URL encoding
1type Mutation {
2  createPost(input: CreatePostInput!): Post
3    @connect(
4      http: {
5        POST: "https://api.example.com/posts"
6        headers: [
7          { name: "Content-Type", value: "application/x-www-form-urlencoded" }
8        ]
9      }
10      selection: """
11      $args.input {
12        title
13        content
14      }
15      """
16    )
17}

The router first maps the request body to a JSON object:

JSON
{
  "title": "Hello, world!",
  "content": "This is a post."
}

Then, it encodes the object as a x-www-form-urlencoded string:

plaintext
title=Hello%2C+world%21&content=This+is+a+post.
Form URL encoding details
  • List values are indexed starting from 0 using the list[0]=value syntax.
  • Nested objects use the parent[child]=value syntax.
  • Spaces are encoded as +.
GraphQL
Example: form URL encoding
1type Mutation {
2  example(input: ExampleInput!): Example
3    @connect(
4      http: {
5        POST: "/example"
6        headers: [
7          { name: "content-type", value: "application/x-www-form-urlencoded" }
8        ]
9      }
10      selection: """
11      $args.input {
12        name
13        tags
14        addresses {
15          street
16          city
17          state
18          zip
19        }
20      }
21      """
22    )
23}
24
25input ExampleInput {
26  name: String!
27  tags: [String!]
28  addresses: [AddressInput!]
29}
30
31input AddressInput {
32  street: String!
33  city: String!
34  state: String!
35  zip: String!
36}
plaintext
Result (new lines added for readability)
1name=Example
2&tags[0]=tag1
3&tags[1]=tag2
4&addresses[0][street]=123+Main+St
5&addresses[0][city]=Anytown
6&addresses[0][state]=CA
7&addresses[0][zip]=12345
8&addresses[1][street]=456+Elm+St
9&addresses[1][city]=Othertown
10&addresses[1][state]=NY
11&addresses[1][zip]=54321

Error handling

Following the GraphQL specification, if a connector returns an error, the corresponding field in the response's data is null, and the error is logged in the errors array. Connectors follow this convention for all non-20x responses.

See the common errors section on the troubleshooting page for information on composition errors.

Feedback

Forums