9. Mutations
3m

Overview

Our API isn't just about reading data, we need to make updates and manipulate data too!

In this lesson, we will:

  • Set up a connector with a POST request that accepts a body
  • Learn about how to craft a selection with nested properties

Create a listing

We're on to the last feature to implement in Airlock: creating a listing.

Mockup of creating a listing

We'll need to add a to our schema. On the REST API side, we'll be using the POST /listings endpoint. Since we can't try this out in the browser, here's what the curl request looks like:

curl request
curl --location 'https://rt-airlock-services-listing.herokuapp.com/listings' \
--header 'Content-Type: application/json' \
--data '{
"listing": {
"title": "New York GQL Summit in Space",
"numOfBeds": 4,
"costPerNight": 404
}
}'

And here's the shape of the data we receive:

Response from curl request
{
"id": "06611dcd-f21f-4676-a02c-ad4b205c6d64",
"title": "New York GQL Summit in Space",
"description": "",
"costPerNight": 404,
"numOfBeds": 4,
"hostId": "user-4",
"locationType": "CAMPSITE",
"photoThumbnail": "https://res.cloudinary.com/apollographql/image/upload/v1644353890/odyssey/federation-course2/illustrations/listings-10.png",
"isFeatured": false,
"closedForBookings": false,
"latitude": null,
"longitude": null
}

Let's get to it!

Setting up the schema

Let's add the types and we need in the schema to make this feature come to life.

Note: If you need a refresher on and input types in the , check out graphql.com/learn.

  1. Open up the listings.graphql file and paste the schema below:

    listings.graphql
    input CreateListingInput {
    title: String!
    numOfBeds: Int!
    costPerNight: Float!
    }
    type CreateListingResponse {
    listing: Listing
    }
    type Mutation {
    createListing(input: CreateListingInput!): CreateListingResponse
    }

We've added a called createListing that takes in one called input. This input is a non-nullable CreateListingInput type, consisting of three : title, numOfBeds and costPerNight, all non-nullable . Finally, the returns a CreateListingResponse type. CreateListingResponse has only one : listing of type Listing.

That's it for our schema definition!

If we save our changes now, we'll get errors that should be familiar by now:

rover dev error
error[E029]: Encountered 2 build errors while trying to build a supergraph.
Caused by:
MUTATION_FIELD_MISSING_CONNECT: The field `Mutation.createListing` has no `@connect` directive.
CONNECTORS_UNRESOLVED_FIELD: No connector resolves field `CreateListingResponse.listing`.
It must have a `@connect` directive or appear in `@connect(selection:)`.

Adding a connector

Every root needs a connector, so let's get to fixing those errors!

  1. Append the @connect to the end of the , defining the source as v1 as usual.

    listings.graphql
    createListing(input: CreateListingInput!): CreateListingResponse
    @connect(
    source: "v1"
    http: { GET: "" }
    selection: """
    """
    )
  2. We'll set up the http value, this time, let's change the GET call to a POST call to the /listings endpoint.

    listings.graphql
    http: {
    POST: "/listings"
    }
  3. This call needs a body! Let's add a body property to the http object. The value will be a mapping similar to what we've done before with selection.

    listings.graphql
    http: {
    POST: "/listings"
    body: """
    # TODO
    """
    }

Crafting the body mapping

Let's look at the shape of the curl request needed for the POST /listings endpoint:

curl request
curl --location 'https://rt-airlock-services-listing.herokuapp.com/listings' \
--header 'Content-Type: application/json' \
--data '{
"listing": {
"title": "New York GQL Summit in Space",
"numOfBeds": 4,
"costPerNight": 404
}
}'

The value for --data reflects the shape we need to give the endpoint. A listing property set to an object with three properties: title, numOfBeds and costPerNight.

These are properties that already exist in our schema, specifically under the CreateListingInput type that gets passed in as an to the .

And as we already know, we can access values using $args. Let's put it all together.

  1. Jump back to the listings.graphql file where we left off defining our body property.

    listings.graphql
    body: """
    # TODO
    """
  2. First, we need to start by defining the top-level listing property, followed by a colon (:)

    listings.graphql
    body: """
    listing:
    """

    This maps the first level of our payload:

    curl request
    --data '{
    "listing": {
    "title": "New York GQL Summit in Space",
    "numOfBeds": 4,
    "costPerNight": 404
    }
    }'
  3. Next, we need values for the three properties of the listing. Since these are all accessible from the $args.input, we'll start with defining that.

    listings.graphql
    body: """
    listing:
    $args.input
    """
  4. Then we'll access the inside input using curly brackets ({ }), defining title, numOfBeds, and costPerNight.

    listings.graphql
    body: """
    listing:
    $args.input {
    title
    numOfBeds
    costPerNight
    }
    """

And we're all done with the POST request's body!

Crafting the selection mapping

If we save our changes now, we'll get an error:

rover dev error
error[E029]: Encountered 1 build error while trying to build a supergraph.
Caused by:
GRAPHQL_ERROR: `@connect(selection:)` on `Mutation.createListing` is required.

We need a selection mapping to finish it off!

  1. Jumping over to the selection property in our connector:

    listings.graphql
    createListing(input: CreateListingInput!): CreateListingResponse
    @connect(
    source: "v1"
    http: {
    POST: "/listings"
    body: """
    listing:
    $args.input {
    title
    numOfBeds
    costPerNight
    }
    """
    }
    selection: """
    # TODO
    """
    )
  2. This needs to return a CreateListingResponse type, which contains a listing that returns a Listing type. Let's first define the top-level listing: property, which will return an object.

    listings.graphql
    selection: """
    listing: {
    # TODO
    }
    """
  3. To help us craft this selection mapping, let's refer back to the response from our curl request:

    Response from curl request
    {
    "id": "06611dcd-f21f-4676-a02c-ad4b205c6d64",
    "title": "New York GQL Summit in Space",
    "description": "",
    "costPerNight": 404,
    "numOfBeds": 4,
    "hostId": "user-4",
    "locationType": "CAMPSITE",
    "photoThumbnail": "https://res.cloudinary.com/apollographql/image/upload/v1644353890/odyssey/federation-course2/illustrations/listings-10.png",
    "isFeatured": false,
    "closedForBookings": false,
    "latitude": null,
    "longitude": null
    }

    It looks like we have all the properties we need that map to all the in our Listing type!

  4. Let's add all of those inside the curly brackets, making sure to rename closedForBookings to closed to match our schema.

    listings.graphql
    selection: """
    listing: {
    id
    title
    numOfBeds
    costPerNight
    closed: closedForBookings
    }
    """

Running the mutation

Moment of truth! Let's check out http://localhost:4000 where the local is running.

Let's build the to create a listing.

mutation CreateListing($input: CreateListingInput!) {
createListing(input: $input) {
listing {
id
title
costPerNight
numOfBeds
}
}
}

We'll also need to provide the input in the Variables section:

{
"input": {
"title": "New York GQL Summit in Space",
"costPerNight": 404,
"numOfBeds": 5
}
}

Perfect, go ahead and run it! We should get data back with the details of our new listing, complete with an id generated from the REST API!

Response
{
"data": {
"createListing": {
"listing": {
"id": "157b2038-aa69-47de-a833-5a37ba89f64a", // you will get a different id!
"numOfBeds": 5,
"title": "New York GQL Summit in Space",
"costPerNight": 404
}
}
}
}

Practice

POST requests are usually accompanied by a payload. How do we send that payload to the REST API endpoint using a connector?

Key takeaways

  • We can apply connectors to Mutation type just as we do for Query type .
  • When submitting data to a POST endpoint, we'll most likely need to include a body property in our connector's http definition.
  • To access the 's input from inside the connector, we can use $args.input and define a selection of the properties to include.
  • To build out our 's response, we can use the selection property. From here we can access the response from the REST API and select the JSON properties we want to return.

Conclusion

Congratulations, you've successfully plugged an existing REST API into a graph using Apollo Connectors! 🎉

We worked with two schema to add connectors to our schema: @source, which we used to define a new for our connectors to use, and @connect, which let us define the specific instructions for "connecting" REST API data to a .

We're now equipped with the tools needed for our developer workflow: using for local development and Explorer to debug connector network calls and examine . We've implemented a few pages for our project demo, Airlock, showcasing featured listings, a listing's details, and creating a listing. And we did all of this without writing a single line of code!

So, where can you go from here?

  • Publish your schema to and take advantage of checks, observability, and proposals to bring your to production.
  • Expand your graph to use federation and include other .
  • Go further with connectors, diving into more complex use cases such as using entities, federation , value transformations and more. Stay tuned for the next course!

We'd also love to hear your feedback on Apollo Connectors. Drop us a note in the community forum!

See you in the next one!

Previous

Share your questions and comments about this lesson

This course is currently in

beta
. Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.