10. Querying real data
2m

Overview

We've got our and ready, but they don't know yet how to work together.

is where all the elements we've built previously (the schema, the , and the ) come together in perfect coordination.

In this lesson, we will:

  • Bring together all the pieces of our : the schema, , and
  • Update our codegen config file to include our server's

Updating our server

In src/index.ts, we can now tell our server all about our and .

Let's import our resolvers and ListingAPI at the top.

index.ts
import { resolvers } from "./resolvers";
import { ListingAPI } from "./datasources/listing-api";

We'll update the ApolloServer options to accept resolvers in addition to typeDefs.

index.ts
const server = new ApolloServer({
typeDefs,
resolvers,
});

To connect our server with our ListingAPI, we'll jump down to the startStandaloneServer function. This function takes a second , which is an object for configuring your server's options.

index.ts
const { url } = await startStandaloneServer(server, {
// TODO: configure server options
});

This is where we'll define a context function that returns an object that all our will share: contextValue (that third positional we talked about earlier!).

Let's set a context property as an async function, which returns an object.

index.ts
const { url } = await startStandaloneServer(server, {
context: async () => {
// this object becomes our resolver's contextValue, the third positional argument
return {
// TODO
};
},
});

Remember, we want to access the dataSources.listingAPI (and its methods) from the contextValue parameter of our . So let's return an object that allows us to do just that!

We'll set a dataSources property inside the object, which is set to another object. This object will have a listingAPI key (lowercase), which returns an instance of the ListingAPI class we imported earlier.

index.ts
const { url } = await startStandaloneServer(server, {
context: async () => {
return {
dataSources: {
listingAPI: new ListingAPI(),
},
};
},
});

Our functions expect to find dataSources.listingAPI on their contextValue, which is why we've defined a property called dataSources here, with another property called listingAPI. Naming the property dataSources isn't a requirement—we chose dataSources as a matter of convention. You can give this property whatever name you'd like, but be sure that you update your functions to access the same property.

One last thing! To take advantage of the RESTDataSource's caching capabilities, we need to pass in the server's cache to our instance of ListingAPI.

Just before we return the contextValue object, let's destructure the cache property from the server. Then, we'll pass in an object to the ListingAPI class, containing that cache property.

index.ts
const { url } = await startStandaloneServer(server, {
context: async () => {
const { cache } = server;
return {
dataSources: {
listingAPI: new ListingAPI({ cache }),
},
};
},
});

To learn more about the options that ApolloServer can receive, check out the documentation.

Updating codegen

Technically, all the pieces of our server are now in place! We've instantiated our on our server, which takes care of passing it to our functions as contextValue, their third positional parameter.

There's just one little problem: if we return to resolvers.ts and hover over our featuredListings 's dataSources parameter, we'll see that it still has an implicit type of any!

resolvers.ts
(parameter) dataSources: any

Let's review that Resolvers type in types.ts again. Here, we can see that it actually accepts a type parameter called ContextType. Whatever we define as this ContextType is received by our functions.

types.ts
export type Resolvers<ContextType = any> = {
Listing?: ListingResolvers<ContextType>;
Query?: QueryResolvers<ContextType>;
};

This parameter represents the type of data we set on the server's context property. It lets us describe more accurately what kind of data our functions have access to on their third positional parameter, contextValue; we can determine the methods that are available for them to call, and what kinds of data those methods will return.

We know what kind of data is set on our server's context (an instance of ListingAPI), but we haven't told TypeScript about it—let's give it a bit more information about our class so it can help us as we code.

In the src directory, we'll create a new file called context.ts. This is where we'll define the type that describes the context we pass to our server.

đź“‚ src
┣ 📂 datasources
┣ 📄 context.ts
┣ 📄 graphql.d.ts
┣ 📄 helpers.ts
┣ 📄 index.ts
┣ 📄 resolvers.ts
┣ 📄 schema.graphql
â”— đź“„ types.ts

Inside, we'll define and export a type called DataSourceContext. Inside of DataSourceContext we'll define a property called dataSources, which is an object.

context.ts
export type DataSourceContext = {
dataSources: {};
};

We want to give the dataSources object the same property that we set on our server, so we can add a listingAPI key, along with the ListingAPI class. Be sure to import this class as well!

context.ts
import { ListingAPI } from "./datasources/listing-api";
export type DataSourceContext = {
dataSources: {
listingAPI: ListingAPI;
};
};

Note: We don't need to instantiate the ListingAPI class here—this is enough to give our type definition the information it needs about the methods and properties available on the class.

Updating codegen.ts

With our DataSourceContext type defined, we can update our codegen.ts file to take it into consideration.

Just below the plugins key, we can add a new config property. This is an object that specifies a contextType.

codegen.ts
const config: CodegenConfig = {
schema: "./src/schema.graphql",
generates: {
"./src/types.ts": {
plugins: ["typescript", "typescript-resolvers"],
config: {
contextType:
},
},
},
};

As the value of contextType, we'll pass the filepath to our context.ts file, relative to the ./src/types.ts file. Our context.ts file is located in the same src folder, so our path is "./context". Finally, to point to the type we defined in the file, we can tack on #DataSourceContext to the end of the file path.

codegen.ts
config: {
contextType: "./context#DataSourceContext",
},

Note: Make sure that there is NOT a forward slash (/) separating ./context and #DataSourceContext in the file path you specify.

Finally, let's run our codegen command again!

npm run generate

Now when we reopen the types.ts file and scroll down to our Resolvers type, we'll see that the ContextType defaults to DataSourceContext!

types.ts
export type Resolvers<ContextType = DataSourceContext> = {
Listing?: ListingResolvers<ContextType>;
Query?: QueryResolvers<ContextType>;
};

And back in resolvers.ts, we can hover over dataSources to see that our type has been inferred correctly. It's an object with a listingAPI property!

resolvers.ts
(parameter) dataSources: {
listingAPI: ListingAPI;
}

(This means if we try to use a method that our don't have access to, we'll get an error telling us!)

Querying data

With our server still running, let's jump back to the Explorer at http://localhost:4000.

Open up the Settings panel and make sure that we've switched Mock Responses into the OFF position.

http://localhost:4000

The Explorer open to the Settings panel, where Mock Responses is highlighted and the toggle is set to OFF

Let's try the same again.

query FeaturedListings {
featuredListings {
id
title
numOfBeds
costPerNight
closedForBookings
}
}

And now... we've got real data! 🎉

Practice

Why do we need to provide our codegen config file with DataSourceContext?
Code Challenge!

Define a data source for resolvers to access. Pass a second argument (a config object) to the startStandaloneServer function. This object should contain an async context function that returns an object containing dataSources. Define a planetService property and instantiate a new PlanetService. Then, access the server's cache and pass it into the PlanetService constructor for a performance boost!

Loading...
Loading progress

Key takeaways

  • We can use the 's context property to define the our need to access.
  • The Code Generator allows us to specify the type of context our get access to, so we can benefit from type safety throughout our project.

Up next

Amazing! We've translated the REST API response to the types that we defined in the schema, and we have a seamless experience. But right now, our list of listings is pretty limited. Based on our mockups, we need to start exploring some additional functionality—such as querying for additional listing properties (hello, amenities!) or asking for a particular listing. Let's uplevel our API in the next lesson.

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.