概览
我们准备好了解析器和 数据源,但它们还不知道如何协同工作。
Apollo Server是我们之前构建的所有元素(架构、解析器和 数据源)完美协调在一起的位置。
在本节中,我们将
- 将我们的 GraphQL 服务器的所有部分汇总在一起:架构、解析器和 数据源
- 更新我们的代码生成配置文件,以包含我们服务器的 数据源
更新我们的服务器
在src/index.ts中,我们现在可以告诉我们的服务器所有有关我们的 解析器和 数据源的信息。
让我们在顶部导入我们的 resolvers和 ListingAPI。
import { resolvers } from "./resolvers";import { ListingAPI } from "./datasources/listing-api";
我们将更新 ApolloServer选项,以除了 typeDefs之外,还接受 resolvers。
const server = new ApolloServer({typeDefs,resolvers,});
为了将我们的服务器与我们的 ListingAPI连接起来,我们将跳转到 startStandaloneServer函数。此函数获取第二个 参数,它是一个用于配置服务器选项的对象。
const { url } = await startStandaloneServer(server, {// TODO: configure server options});
我们将在其中定义一个 context函数,该函数返回一个所有我们的 解析器将共享的对象:contextValue(即我们之前讨论的第三位置 参数)
我们将 context属性设置为一个异步函数,该函数返回一个对象。
const { url } = await startStandaloneServer(server, {context: async () => {// this object becomes our resolver's contextValue, the third positional argumentreturn {// TODO};},});
请记住,我们想要访问 dataSources.listingAPI(及其方法),该方法来自 contextValue resolver 的参数。因此,我们返回一个允许我们执行此操作的对象!
我们将在对象内设置一个 dataSources属性,该属性设置为另一个对象。此对象将具有一个 listingAPI键(小写),该键返回 ListingAPI数据源类(我们之前导入),的一个实例。
const { url } = await startStandaloneServer(server, {context: async () => {return {dataSources: {listingAPI: new ListingAPI(),},};},});
我们的 resolver函数期望在 dataSources.listingAPI中找到 contextValue,这就是我们在这里定义了一个名为 dataSources的属性,其中包含另一个名为 listingAPI的属性的原因。属性 dataSources命名不是必需的——我们选择 dataSources作为一种约定。您可以给此属性指定您想要的任何名称,但请确保更新 resolver函数以访问同一属性。
还有一件事!为了利用 RESTDataSource的缓存功能,我们需要将服务器的缓存传递到我们的 ListingAPI实例中。
在我们返回 contextValue对象之前,我们先对 cache属性进行解构,该属性属于 server。然后,我们将一个包含该 cache属性的对象传递到 ListingAPI类。
const { url } = await startStandaloneServer(server, {context: async () => {const { cache } = server;return {dataSources: {listingAPI: new ListingAPI({ cache }),},};},});
若要了解 ApolloServer可以接收的更多选项,请 查看文档。
更新 codegen
从技术角度讲,现在我们服务器的所有部件就位了!我们在服务器上实例化了数据源,它负责将数据源传递给我们的解析器函数作为contextValue,即它们 的第三个位置参数。
只有一个小小的问题:如果我们返回resolvers.ts并悬停在我们的featuredListings 解析器的dataSources参数上,我们会看到它仍然具有隐式类型any!
(parameter) dataSources: any
让我们在types.ts中的Resolvers类型再次进行审查。在此处,我们可以看到它实际接纳了一个名为ContextType的类型参数。无论我们将ContextType定义成什么,都会被我们的resolver函数接收。
export type Resolvers<ContextType = any> = {Listing?: ListingResolvers<ContextType>;Query?: QueryResolvers<ContextType>;};
此参数表示我们在服务器的context属性中设置数据的类型。它使我们能够更准确地描述我们的resolver函数在其第三个位置参数contextValue上可以访问哪种类型的数据;我们可以确定它们可以调用哪些方法,以及这些方法将返回哪种类型的数据。
我们知道在服务器的context(ListingAPI实例)中设置的数据类型,但我们还没有告知 TypeScript,我们来为我们的类提供一些更多信息,以便它在编码时对我们有所帮助。
在src目录中,我们将创建一个名为context.ts的新文件。我们将在该文件中定义描述传递给服务器的上下文的类型。
📂 src┣ 📂 datasources┣ 📄 context.ts┣ 📄 graphql.d.ts┣ 📄 helpers.ts┣ 📄 index.ts┣ 📄 resolvers.ts┣ 📄 schema.graphql┗ 📄 types.ts
在内部,我们将定义和导出一个名为 DataSourceContext 的类型。在 DataSourceContext 中,我们将定义一个名为 dataSources 的属性,它是一个对象。
export type DataSourceContext = {dataSources: {};};
我们希望给 dataSources 对象与我们在服务器上设置的相同属性,因此我们可以添加 listingAPI 键以及 ListingAPI 类。确保也要导入此类!
import { ListingAPI } from "./datasources/listing-api";export type DataSourceContext = {dataSources: {listingAPI: ListingAPI;};};
注意:我们不需要在此处实例化 ListingAPI 类,这就足以让我们的类型定义获取有关方法和属性的信息,这些属性和方法可用于 数据源 类。
更新 codegen.ts
定义了我们的 DataSourceContext 类型后,我们可以更新我们的 codegen.ts 文件以对其进行考虑。
就在 plugins 键的下方,我们可以添加一个新的 config 属性。这是一个指定 contextType 的对象。
const config: CodegenConfig = {schema: "./src/schema.graphql",generates: {"./src/types.ts": {plugins: ["typescript", "typescript-resolvers"],config: {contextType:},},},};
作为 contextType 的值,我们将传递文件路径到我们的 context.ts 文件,该路径相对于 ./src/types.ts 文件。我们的 context.ts 文件位于同一个 src 文件夹中,因此我们的路径是 "./context"。最后,要指向我们在文件中定义的类型,我们可以将 #DataSourceContext 添加到文件路径末尾。
config: {contextType: "./context#DataSourceContext",},
注意:确保在指定的文件路径中 ./context 和 #DataSourceContext 之间没有一个正斜杠 (/)。
最后,让我们再次运行我们的 codegen 命令!
npm run generate
现在,当我们重新打开 types.ts 文件并向下滚动到我们的 Resolvers 类型时,我们将看到 ContextType 默认为 DataSourceContext!
export type Resolvers<ContextType = DataSourceContext> = {Listing?: ListingResolvers<ContextType>;Query?: QueryResolvers<ContextType>;};
回到 resolvers.ts 中,我们可以悬停在 dataSources 上,以查看我们的类型是否正确推断。它是一个带有 listingAPI 属性的对象!
(parameter) dataSources: {listingAPI: ListingAPI;}
(这意味着如果我们尝试使用我们的 解析器 没有 访问权限的方法,它将显示一个告诉我们的错误!)
查询数据
当我们的服务器仍在运行时,让我们回到 https://:4000 上的 Explorer。
打开 Settings 面板,并确保已将 Mock Responses 切换到关闭位置。
让我们再次尝试相同的 查询。
query FeaturedListings {featuredListings {idtitlenumOfBedscostPerNightclosedForBookings}}
现在... 我们有了 真实的数据! 🎉
练习
DataSourceContext 为我们的 codegen 配置文件提供数据?要点
- 我们可以使用 Apollo Server的
context属性来定义 数据源我们的 解析器需要访问这些数据源。 - GraphQL代码生成器允许我们指定我们的 解析器访问的上下文类型,以便我们在整个项目中受益于类型安全性。
下一步
太棒了!我们将 REST API 响应转换为我们在模式中定义的类型,并且我们拥有无缝的 查询体验。但现在,我们的房源列表非常有限。根据我们的模型,我们需要开始探索一些其他功能——例如查询附加的房源属性(你好,便利设施!)或询问 特定房源。让我们在下一课中提升我们的 GraphQLAPI。
分享你对这一课的问题和评论
本课程目前正在
你将需要一个 GitHub 帐户才能在下面进行帖子。没有吗? 在我们的 Odyssey 论坛中发帖。