Overview
Our GraphQL 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.
We'll need to add a mutation 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 --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:
{"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 fields we need in the schema to make this feature come to life.
Note: If you need a refresher on mutations and input types in the GraphQL schema, check out graphql.com/learn.
Open up the
listings.graphql
file and paste the schema below:listings.graphqlinput CreateListingInput {title: String!numOfBeds: Int!costPerNight: Float!}type CreateListingResponse {listing: Listing}type Mutation {createListing(input: CreateListingInput!): CreateListingResponse}
We've added a mutation called createListing
that takes in one argument called input
. This input
is a non-nullable CreateListingInput
type, consisting of three fields: title
, numOfBeds
and costPerNight
, all non-nullable fields. Finally, the mutation returns a CreateListingResponse
type. CreateListingResponse
has only one field: 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:
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 field needs a connector, so let's get to fixing those errors!
Append the
@connect
directive to the end of the field, defining thesource
asv1
as usual.listings.graphqlcreateListing(input: CreateListingInput!): CreateListingResponse@connect(source: "v1"http: { GET: "" }selection: """""")We'll set up the
http
value, this time, let's change theGET
call to aPOST
call to the/listings
endpoint.listings.graphqlhttp: {POST: "/listings"}This call needs a
body
! Let's add abody
property to thehttp
object. The value will be a mapping similar to what we've done before withselection
.listings.graphqlhttp: {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 --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 argument to the mutation.
And as we already know, we can access GraphQL argument values using $args
. Let's put it all together.
Jump back to the
listings.graphql
file where we left off defining ourbody
property.listings.graphqlbody: """# TODO"""First, we need to start by defining the top-level
listing
property, followed by a colon (:
)listings.graphqlbody: """listing:"""This maps the first level of our payload:
curl request--data '{"listing": {"title": "New York GQL Summit in Space","numOfBeds": 4,"costPerNight": 404}}'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.graphqlbody: """listing:$args.input"""Then we'll access the fields inside
input
using curly brackets ({ }
), definingtitle
,numOfBeds
, andcostPerNight
.listings.graphqlbody: """listing:$args.input {titlenumOfBedscostPerNight}"""
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:
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!
Jumping over to the
selection
property in our connector:listings.graphqlcreateListing(input: CreateListingInput!): CreateListingResponse@connect(source: "v1"http: {POST: "/listings"body: """listing:$args.input {titlenumOfBedscostPerNight}"""}selection: """# TODO""")This mutation needs to return a
CreateListingResponse
type, which contains alisting
field that returns aListing
type. Let's first define the top-levellisting:
property, which will return an object.listings.graphqlselection: """listing: {# TODO}"""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 fields in our
Listing
type!Let's add all of those fields inside the curly brackets, making sure to rename
closedForBookings
toclosed
to match our schema.listings.graphqlselection: """listing: {idtitlenumOfBedscostPerNightclosed: closedForBookings}"""
Running the mutation
Moment of truth! Let's check out http://localhost:4000 where the local router is running.
Let's build the mutation to create a listing.
mutation CreateListing($input: CreateListingInput!) {createListing(input: $input) {listing {idtitlecostPerNightnumOfBeds}}}
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!
{"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 fields just as we do forQuery
type fields. - When submitting data to a
POST
endpoint, we'll most likely need to include abody
property in our connector'shttp
definition. - To access the mutation field's
input
from inside the connector, we can use$args.input
and define a selection of the properties to include. - To build out our mutation field'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 directives to add connectors to our schema: @source
, which we used to define a new data source for our connectors to use, and @connect
, which let us define the specific instructions for "connecting" REST API data to a field.
We're now equipped with the tools needed for our developer workflow: using Rover for local supergraph development and Explorer to debug connector network calls and examine query plans. 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 resolver code!
So, where can you go from here?
- Publish your schema to GraphOS and take advantage of checks, observability, and proposals to bring your graph to production.
- Expand your graph to use federation and include other subgraphs.
- Go further with connectors, diving into more complex use cases such as using entities, federation directives, value transformations and more. Stay tuned for the next Odyssey 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!
Share your questions and comments about this lesson
This course is currently in
You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.