10. Mutations
5m

Overview

There's more to APIs than just reading data. We need to be able to do things with our data as well, which is where come in. Let's tackle one last feature: creating a listing.

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

On the REST API side, we'll be using the POST /listings endpoint. Here's what the curl request looks like.

curl request
curl --location 'https://airlock-listings.demo-api.apollo.dev/listings' \
--header 'Content-Type: application/json' \
--header 'Api-Key: ODYSSEY' \
--data '{
"listing": {
"title": "GraphQL Summit Home Base",
"numOfBeds": 4,
"costPerNight": 404
}
}'

To send the request, you'll need to provide a value for the Api-Key header.

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

Response from curl request
{
"id": "06611dcd-f21f-4676-a02c-ad4b205c6d64",
"title": "GraphQL Summit Home Base",
"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,
"amenities": []
}

We'll need to map this response to our schema, and learn how to send a POST request with a body payload using connectors.

Setting up the schema

  1. First, let's add the types and we need in the schema to make this feature come to life.

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

    We've added a new root under the Mutation type, where we organize that handle write . createListing takes in an input : CreateListingInput. This type consists of three required : title, numOfBeds and costPerNight. Finally, the returns a CreateListingResponse type; this has one called listing, which returns the Listing type.

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

  2. If we save our changes now, we'll see that familiar error.

    rover dev error
    error[E029]: Encountered 1 build error while trying to build a supergraph.
    Caused by:
    MUTATION_FIELD_MISSING_CONNECT: [listings] The field `Mutation.createListing` has no `@connect` directive.

Adding a connector

Every root needs a connector, so let's get to it.

  1. Let's append the @connect to the createListing , defining the source as usual.

    listings.graphql
    createListing(input: CreateListingInput!): CreateListingResponse
    @connect(
    source: "listings"
    http: { GET: "" }
    selection: """
    """
    )
  2. Next up, for http, we'll need to change the GET call to a POST call and point to the /listings endpoint.

    listings.graphql
    http: {
    POST: "/listings"
    }
  3. As we saw in the curl command, we need to include a header called Api-Key. We can add that to our http object under the property headers, which contains an array.

    listings.graphql
    http: {
    POST: "/listings",
    headers: []
    }
  4. For each header object, we can specify the name property, which in this case is Api-Key.

    listings.graphql
    http: {
    POST: "/listings",
    headers: [
    {
    name: "Api-Key"
    }
    ]
    }
  5. We also need to specify the header value. We could hard-code the value we're using here (under the value property).

    listings.graphql
    http: {
    POST: "/listings",
    headers: [
    {
    name: "Api-Key",
    value: "ODYSSEY" # ⬅️ We could hard-code the value here
    }
    ]
    }
  6. Or we can propagate user input. Using the from property, we'll set the value to the name of the header we're propagating from (which will also be called Api-Key!). We'll see how this works in action later on.

    listings.graphql
    http: {
    POST: "/listings",
    headers: [
    {
    name: "Api-Key",
    from: "Api-Key" # ⬅️ Let's propagate it from user input instead!
    }
    ]
    }

This call still needs a body! This will be a mapping similar to selection, but going the other way around: mapping schema to JSON.

Crafting the body mapping

Let's look at the shape of that body payload from our curl request again (the --data value). This is the end result we want:

Body payload
{
"listing": {
"title": "GraphQL Summit Home Base",
"numOfBeds": 4,
"costPerNight": 404
}
}

We've got listing set to an object with three properties: title, numOfBeds and costPerNight.

  1. Let's reflect that back in our connector. Still within the http object, we'll add a body parameter and triple quotes.

    listings.graphql
    http: {
    POST: "/listings"
    headers: [{ name: "Api-Key", from: "Api-Key" }]
    body: """
    """
    }
  2. Inside, we'll define the name of the top-level property, which is listing, followed by a colon, with the value set to an object.

    listings.graphql
    body: """
    listing: {
    }
    """
  3. Then we'll start adding those three properties we need.

    listings.graphql
    body: """
    listing: {
    title:
    numOfBeds:
    costPerNight:
    }
    """
  4. Each of these will need a value, and it should come from the input that gets passed in from the client. Remember, we can access arguments with $args. So for the title, we'll need $args.input.title. We'll also follow the same pattern for numOfBeds and costPerNight.

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

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

Crafting the selection mapping

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

Rover error
error[E029]: Encountered 1 build error while trying to build a supergraph.
Caused by:
INVALID_SELECTION: [listings] `@connect(selection:)` on `Mutation.createListing` is empty

We need a selection mapping to finish off this connector!

listings.graphql
createListing(input: CreateListingInput!): CreateListingResponse
@connect(
source: "listings"
http: {
POST: "/listings"
headers: [{ name: "Api-Key", from: "Api-Key" }]
body: """
listing: {
title: $args.input.title
numOfBeds: $args.input.numOfBeds
costPerNight: $args.input.costPerNight
}
"""
}
selection: """
"""
)
  1. This needs to return a CreateListingResponse type, which contains a listing that returns a Listing type. First, we'll define that top-level listing property, followed by a colon, with the value set to an object.

    listings.graphql
    selection: """
    listing: {
    }
    """
  2. Let's pull up that response object again to help us out. It looks like the response has all the properties we need to map to our schema's Listing , pretty much one-to-one. (Even if some of them are null or empty!)

    Response from curl request
    {
    "id": "06611dcd-f21f-4676-a02c-ad4b205c6d64",
    "title": "GraphQL Summit Home Base",
    "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,
    "amenities": []
    }
  3. Let's add all of those . Make sure you rename closedForBookings to closedForBooking to match our schema!

    listings.graphql
    selection: """
    listing: {
    id
    title
    numOfBeds
    costPerNight
    closedForBooking: closedForBookings
    description
    photoThumbnail
    latitude
    longitude
    amenities {
    id
    name
    category
    }
    }
    """

Checking our work

Moment of truth! Let's check out Sandbox and start to build out this , adding the we need from the sidebar.

CreateListing operation
mutation CreateListing($input: CreateListingInput!) {
createListing(input: $input) {
listing {
id
title
numOfBeds
costPerNight
closedForBooking
description
photoThumbnail
latitude
longitude
amenities {
id
category
name
}
}
}
}

We'll need to provide the input in the Variables section with the details of the listing we're creating.

Variables
{
"input": {
"title": "GraphQL Summit Home Base",
"costPerNight": 404,
"numOfBeds": 5
}
}
http://localhost:4000

Adding variables in Sandbox

Adding headers

Under the Headers section, let's add a new header, called Api-Key, passing in a value.

http://localhost:4000

Adding a headers in Sandbox

Remember, we built our connector to take the value from this header and propagate it to our REST API call.

Go ahead and run it! And we get data back with the details of our new listing, complete with an id generated from the REST API.

Response
{
"data": {
"createListing": {
"listing": {
"id": "d6c46fed-98bc-4ca6-b74f-93f9949c1db3", // you will get a different ID!
"title": "GraphQL Summit Home Base",
"numOfBeds": 5,
"costPerNight": 404,
"closedForBooking": false,
"description": "",
"photoThumbnail": "https://res.cloudinary.com/apollographql/image/upload/v1644353890/odyssey/federation-course2/illustrations/listings-10.png",
"latitude": null,
"longitude": null,
"amenities": []
}
}
}
}
Task!

Practice

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

Use the REST API body payload below to complete the code challenge.

REST API body payload
{
"officialName": "Mraza",
"mass": 6.42
}
Code Challenge!

Fill in the missing values for the createPlanet field's @connect directive. Use the POST /planets endpoint, which needs an Authorization header propagated from a user-provided header of the same name. Refer to the JSON object above for the shape of the body payload, and the input argument for the values needed.

Loading...
Loading progress

Key takeaways

  • We can apply connectors to Mutation type just as we do for Query type .
  • When submitting data to a POST endpoint, we can include a body in our connector's http definition.

Conclusion

Well done! You've successfully built an API powered by . You now have the tools and patterns to keep on exploring and building.

Let us know about what you're creating, and what you'd like to see next. Drop us a note in the community forum.

Want to keep learning? Dive deeper into the syntax with Expressions in connectors: Mapping and Transforms.

See you next time!

Previous

Share your questions and comments about this lesson

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.