概览
我们准备好了解析器和 数据源,但它们还不知道如何协同工作。
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://127.0.0.1:4000 上的 Explorer。
打开 Settings 面板,并确保已将 Mock Responses 切换到关闭位置。
让我们再次尝试相同的 查询。
query FeaturedListings {featuredListings {idtitlenumOfBedscostPerNightclosedForBookings}}
现在... 我们有了 真实的数据! 🎉
练习
DataSourceContext
为我们的 codegen 配置文件提供数据?要点
- 我们可以使用 Apollo Server的
context
属性来定义 数据源我们的 解析器需要访问这些数据源。 - GraphQL代码生成器允许我们指定我们的 解析器访问的上下文类型,以便我们在整个项目中受益于类型安全性。
下一步
太棒了!我们将 REST API 响应转换为我们在模式中定义的类型,并且我们拥有无缝的 查询体验。但现在,我们的房源列表非常有限。根据我们的模型,我们需要开始探索一些其他功能——例如查询附加的房源属性(你好,便利设施!)或询问 特定房源。让我们在下一课中提升我们的 GraphQLAPI。
分享你对这一课的问题和评论
本课程目前正在
你将需要一个 GitHub 帐户才能在下面进行帖子。没有吗? 在我们的 Odyssey 论坛中发帖。