资讯动态

Nhost JS SDK GraphQL 客户端实战:从基础查询到类型安全与错误处理

发布时间:2026/9/16 20:54:27 来源:尧图企业网站定制
Nhost JS SDK GraphQL 客户端实战从基础查询到类型安全与错误处理【免费下载链接】nhostThe Open Source Firebase Alternative with GraphQL.项目地址: https://gitcode.com/GitHub_Trending/nh/nhostNhost 是一个开源的 Firebase 替代方案The Open Source Firebase Alternative with GraphQL其 JavaScript SDKnhost/nhost-js内置了完整的 GraphQL 客户端模块用于与 Nhost 的 Hasura GraphQL 服务交互。本文以 graphql.md 为核心骨架系统讲解如何通过nhost.graphql.request执行查询与变更、如何利用 TypeScript 泛型与 GraphQL Document Node 实现端到端类型安全以及 SDK 的错误处理模型同时结合仓库源码graphql/client.ts、fetch/fetch.ts与测试用例深入剖析其底层实现原理。读完本文你将掌握 Nhost GraphQL 客户端的全部核心用法并能直接在自己的项目中落地实践。模块概览GraphQL 客户端在 Nhost SDK 中的位置nhost/nhost-js是 Nhost 的官方 JavaScript SDK负责与 Nhost 后端的各项服务通信。从 nhost.ts 可以看出SDK 内部将服务拆分为四个独立的客户端auth认证服务注册、登录、会话管理storage文件存储服务graphqlHasura GraphQL 服务本文主角functionsServerless 函数服务这四者统一由createClient客户端场景或createServerClient服务端场景组装进一个NhostClient实例其中 GraphQL 客户端以nhost.graphql形式暴露。graphql模块是整个 SDK 中“与业务数据打交道”的核心你的表数据、视图、权限校验都通过它走 GraphQL 完成。该模块的入口文件 graphql/index.ts 中明确说明“这是与 Nhost GraphQL 服务交互的主模块。通常通过主 Nhost 客户端createClient使用该模块但如果你有特定场景也可以直接使用它。”直接使用 GraphQL 客户端独立导入模块通过 package.json 中的子路径导出exports字段暴露因此除了通过nhost.graphql使用外还可以单独引入import { createClient } from nhost/nhost-js/graphql;如果你需要绕过完整的 Nhost 客户端例如只需对任意 GraphQL 端点发请求而不需要认证、存储等功能可以直接调用模块导出的工厂函数createAPIClient(url, chainFunctions)创建独立客户端详见后文“深入源码”一节。基本用法从 Nhost 客户端发起首次 GraphQL 请求最常见的用法是创建完整的 Nhost 客户端然后通过nhost.graphql.request发送查询。request方法提供了完整的 TypeScript 类型支持可配合泛型标注响应类型也可使用 GraphQL Document Node 与第三方工具链如 Apollo Client、GraphQL Code Generator集成。最简单的查询如下import { createClient } from nhost/nhost-js; const nhost createClient({ subdomain, region, }); const resp await nhost.graphql.request({ query: query GetMovies { movies { id title director genre } }, });subdomain与region用于构造 GraphQL 服务的基础 URL。查看 nhost.ts 的实现可知SDK 内部通过generateServiceUrl(graphql, subdomain, region, graphqlUrl)拼接出形如https://subdomain.graphql.region.nhost.run/v1/graphql的端点如果你有自定义端点也可以直接在createClient中传入graphqlUrl完整 URL会覆盖 subdomain/region 的组合。request返回的响应对象结构为{ body, status, headers }对应 fetch/fetch.ts 中定义的FetchResponseT接口其中body是标准的GraphQLResponse包含可选的data与可选的errors。因此查询结果通过resp.body.data?.movies访问。查询与变更Query / Mutationrequest方法同时支持查询和变更操作。查询用于获取数据、不应修改服务端数据变更mutation则用于增删改。两者共用同一个request调用只需把 GraphQL 操作字符串换成 mutation 即可例如仓库测试 graphql.test.ts 中的变更示例const resp await nhost.graphql.requestUpdateUsersDisplayNameResponse({ query: mutation UpdateUsersDisplayName($id: uuid!, $displayName: String!) { updateUser(pk_columns: {id: $id}, _set: {displayName: $displayName}) { id displayName } }, variables: { id: userID, displayName: My New Display Name, }, operationName: UpdateUsersDisplayName, });注意这里同时使用了三个字段query操作字符串、variables参数、operationName可选的操作名当请求字符串中包含多个操作时用于指定执行哪一个。使用变量Variables你可以在查询和变更中通过variables选项传递动态参数使操作更灵活、可复用import { createClient } from nhost/nhost-js; const nhost createClient({ subdomain, region, }); const resp await nhost.graphql.request({ query: query GetMovies($genre: String!) { movies(where: {genre: {_eq: $genre}}) { id title director genre } }, variables: { genre: Sci-Fi, }, }); console.log(resp.body.data?.movies); // [ // { // id: 3d67a6d0-bfb5-444a-9152-aea543ebd171, // title: The Matrix, // director: Lana Wachowski, Lilly Wachowski, // genre: Sci-Fi // }, // { // id: 90f374db-16c1-4db5-ba55-643bf38953d3, // title: Inception, // director: Christopher Nolan, // genre: Sci-Fi // }, // ]变量的类型默认是GraphQLVariables即Recordstring, unknown键值对形式。借助变量你可以把用户输入、筛选条件等动态值安全地注入到查询中而无需拼接字符串。使用字符串查询 TypeScript 泛型类型安全的第一步你可以通过 TypeScript 泛型为响应数据声明类型让resp.body.data获得完整的类型推导import { createClient } from nhost/nhost-js; const nhost createClient({ subdomain, region, }); // 这是可选的但能让你获得类型化的响应 // Apollo Client 或 The Guild 的 GraphQL Code Generator 等工具 // 可以为你生成这些类型与文档节点。 interface Movies { movies: { id: string; title: string; director: string; genre: string; }[]; } const resp await nhost.graphql.requestMovies({ query: query GetMovies { movies { id title director genre } }, });requestTResponseData, TVariables的第一个泛型参数TResponseData默认unknown控制GraphQLResponseTResponseData[data]的类型第二个泛型参数TVariables默认GraphQLVariables控制variables的类型。这样查询结果与变量都处于类型系统的保护之下。使用 GraphQL Document Node第三方工具链集成为了与 Apollo Client、The Guild 的 GraphQL Code Generator 等第三方库更好地集成你可以使用gql模板标签创建 GraphQL Document Node然后直接传给requestimport { createClient } from nhost/nhost-js; import gql from graphql-tag; const nhost createClient({ subdomain, region, }); interface Movies { movies: { id: string; title: string; director: string; genre: string; }[]; } const getMoviesQuery gql query GetMovies($genre: String!) { movies(where: { genre: { _eq: $genre } }) { id title director genre } } ; const resp await nhost.graphql.requestMovies(getMoviesQuery, { genre: Sci-Fi, }); console.log(resp.body.data?.movies);注意此处的调用签名与字符串形式不同第二个参数直接是variables而非RequestInit第三个可选参数才是额外的 fetch 选项。仓库测试 graphql.test.ts 验证了这种调用方式含变量与不含变量两种形态。使用 Document Node 可以带来以下收益更好的 IDE 支持语法高亮与校验代码生成工具集成GraphQL Code Generator 可自动生成类型与文档节点与 Apollo Client 等 GraphQL 库的兼容性文档节点可直接在这些生态中复用从源码看request的重载实现位于 graphql/client.ts它会判断传入的对象是否带有kind属性Document Node 的标志若是则通过extractQueryFromDocument提取查询字符串、从第一个定义中取出操作名作为operationName再走与字符串形式相同的executeOperation执行路径。底层实现extractQueryFromDocument 与片段去重当使用 Document Node 时SDK 并非直接序列化 AST而是借助loc偏移量从原始源码切片重建查询字符串见 graphql/client.ts。其行为如下若文档没有loc返回空字符串若定义节点缺少loc则回退返回原始源码文本若定义节点均含loc偏移典型如 Codegen 输出则按定义逐段切片并拼接同时对重复的 Fragment 定义按名称去重best-effort。这一点在接口文档中也有说明“当文档的定义节点包含loc偏移时重复的片段定义会以尽力而为的方式去重。”对应的专项测试 graphql-fragment-dedup.test.ts 覆盖了多重嵌套片段去重如三个片段共同引用同一个Picture片段时只保留一份、AST 中本身存在重复片段、以及无loc时回退等边界场景。这意味着你可以放心地把 Codegen 生成的大型查询文档交给request不必担心重复片段导致 Hasura 校验失败。错误处理Error HandlingSDK 的行为是当 GraphQL 操作返回的响应带有长度大于 0 的errors属性时抛出异常。异常类型为FetchErrorGraphQLResponse其中携带包含错误的响应体。捕获 FetchError 并检查错误体import { createClient } from nhost/nhost-js; import { FetchError } from nhost/nhost-js/fetch; import type { GraphQLResponse } from nhost/nhost-js/graphql; const nhost createClient({ subdomain, region, }); try { await nhost.graphql.request({ query: query GetRestrictedObject { restrictedObject { restrictedField } } , }); expect(true).toBe(false); // 不应执行到这里 } catch (error) { if (!(error instanceof FetchError)) { throw error; // 不是 FetchError 则重新抛出 } const resp error as FetchErrorGraphQLResponse; console.log(Error:, JSON.stringify(resp.body, null, 2)); // Error: { // body: { // errors: [ // { // message: field restrictedObject not found in type: query_root, // extensions: { // path: $.selectionSet.restrictedObject, // code: validation-failed // } // } // ] // }, // status: 200, // headers: {} // } // error handling... }直接记录错误消息FetchError继承自标准Error类型因此如果你只想记录错误消息可以直接使用error.messageimport { createClient } from nhost/nhost-js; import { FetchError } from nhost/nhost-js/fetch; import type { GraphQLResponse } from nhost/nhost-js/graphql; const nhost createClient({ subdomain, region, }); try { await nhost.graphql.request({ query: query GetRestrictedObject { restrictedObject { restrictedField } } , }); expect(true).toBe(false); // 不应执行到这里 } catch (error) { if (!(error instanceof Error)) { throw error; // 重新抛出非 Error 类型 } console.log(Error:, error.message); // Error: field restrictedObject not found in type: query_root }错误模型源码解析FetchError定义在 fetch/fetch.ts它扩展了原生Error额外携带body原始响应体、statusHTTP 状态码、headers响应头三个属性。构造函数通过extractMessage(body)自动从常见错误格式中提取人类可读的消息——包括纯字符串、{ message }、{ error }、{ error: { message } }以及{ errors: [{ message }] }数组见 fetch/fetch.ts因此error.message会直接呈现第一条 GraphQL 错误信息。抛出时机在 graphql/client.ts 的executeOperation中解析响应 JSON 后只要data.errors存在就抛出FetchError。注意 GraphQL 错误通常伴随 HTTP 200 返回如上例中status: 200因此不能只依赖 HTTP 状态码判断成功与否必须捕获异常或检查errors字段。仓库测试 graphql.test.ts 对该行为有完整验证无效查询、权限不足/字段不存在两类场景。接口与类型参考Interfaces TypesClient 接口GraphQL 客户端接口提供执行查询与变更的方法属性url: string—— GraphQL 端点 URL。方法pushChainFunction(chainFunction: ChainFunction): void—— 向 fetch 链添加一个中间件函数参数chainFunction类型为ChainFunction详见 fetch 模块。request()—— 执行 GraphQL 操作有两种调用签名签名一字符串请求对象requestTResponseData, TVariables( request: GraphQLRequestTVariables, options?: RequestInit, ): PromiseFetchResponseGraphQLResponseTResponseData;执行 GraphQL 查询操作查询用于获取数据不应修改服务端数据。类型参数TResponseData默认unknownTVariables默认GraphQLVariables。参数request为包含查询与可选变量的请求对象options?为额外的 fetch 选项。返回携带 GraphQL 响应与元数据的 Promise。签名二TypedDocumentNoderequestTResponseData, TVariables( document: TypedDocumentNodeTResponseData, TVariables, variables?: TVariables, options?: RequestInit, ): PromiseFetchResponseGraphQLResponseTResponseData;使用类型化文档节点执行 GraphQL 操作。当文档的定义节点包含loc偏移时重复的片段定义会以尽力而为的方式去重。参数document为携带查询与类型信息的TypedDocumentNodevariables?为操作变量options?为额外 fetch 选项。GraphQLError表示服务端返回的 GraphQL 错误属性类型说明messagestring错误消息locations?{ column: number; line: number }[]错误在 GraphQL 文档中发生的位置path?string[]错误发生的查询路径extensions?{ path: string; code: string }特定于 GraphQL 实现Hasura的附加错误信息GraphQLRequest用于查询与变更的 GraphQL 请求对象泛型TVariables默认GraphQLVariables属性类型说明querystringGraphQL 查询或变更字符串variables?TVariables参数化查询的可选变量operationName?string可选的要执行的操作名GraphQLResponse符合 GraphQL 规范的标准响应格式泛型TResponseData默认unknown属性类型说明data?TResponseData成功执行返回的数据errors?GraphQLError[]执行失败或部分失败时的错误数组GraphQLVariables 类型别名type GraphQLVariables Recordstring, unknown;GraphQL 操作的变量对象即变量名与变量值的键值对。createAPIClient() 工厂函数function createAPIClient(url: string, chainFunctions?: ChainFunction[]): Client;创建一个用于与 GraphQL 端点交互的 API 客户端。该客户端提供执行查询与变更的方法并支持通过中间件函数处理认证、错误处理等横切关注点。参数类型默认值说明urlstringundefinedGraphQL 端点的基础 URLchainFunctionsChainFunction[][]fetch 链的中间件函数数组返回Client—— 带查询与变更方法的 GraphQL 客户端。该工厂函数从 graphql/index.ts 导出是独立使用 GraphQL 模块import { createClient } from nhost/nhost-js/graphql时实际创建客户端的底层入口。深入源码request 的执行链路与中间件机制请求执行链路从 graphql/client.ts 可以看到createAPIClient的实现要点初始化用createEnhancedFetch(chainFunctions)构建增强版 fetch头部处理executeOperation中先合并传入的options.headers若未显式设置Content-Type则自动补为application/json测试 client.test.ts 验证了自定义Authorization头与 JSON Content-Type 可共存发送请求以POST方法、JSON.stringify(request)作为请求体调用增强 fetch解析响应读取响应文本并JSON.parse为GraphQLResponse封装为{ body, status, headers }错误抛出若data.errors存在抛出FetchError。Fetch 链与中间件fetch/fetch.ts 定义了中间件模型ChainFunction (next: FetchFunction) FetchFunction即每个中间件接收“链中的下一个 fetch”并返回包装后的新 fetchcreateEnhancedFetch通过reduceRight将中间件按数组顺序依次包裹在原生fetch之外因此每个中间件既能拦截请求调用next之前也能拦截响应调用next之后。Nhost 客户端的认证能力正是构建在这一机制之上。createClient会默认注入withClientSideSessionMiddleware见 nhost.ts它由多个中间件组成其中 middlewareAttachAccessToken.ts 会从会话存储中读取 access token自动为请求添加Authorization: Bearer token头若请求已带 Authorization 头则跳过。这意味着只要用户已登录nhost.graphql.request发出的请求就会自动携带认证信息Hasura 据此完成基于角色的行级/字段级权限控制——你无需手动管理 token。此外Client接口暴露的pushChainFunction允许你在运行时动态追加中间件如自定义日志、重试、请求改写追加后 SDK 会重新构建 fetch 链graphql/client.ts。服务端场景Server-side补充说明除浏览器端外SDK 还提供createServerClient见 nhost.ts适用于 Next.js/Remix 的 Server Component、API Route、中间件等场景。与客户端版本的区别在于必须显式提供storage实现SDK 无法在服务端自动检测存储禁用自动会话刷新中间件避免服务端并发请求下的竞态问题仍然会附加 Authorization 令牌并从响应更新会话存储。nhost.graphql的用法在两种客户端下完全一致区别仅在于会话如何注入。这为 SSR 应用中使用类型安全的 GraphQL 查询提供了完整支持。总结与最佳实践日常查询通过nhost.graphql.request({ query, variables, operationName })即可完成查询与变更结果在resp.body.data错误以异常形式抛出。类型安全优先使用 TypeScript 泛型requestMovies更进一步用gql或 GraphQL Code Generator 生成TypedDocumentNode后传给request可同时获得响应类型、变量类型与 IDE 校验。错误处理捕获FetchErrorGraphQLResponse从error.body.errors读取结构化错误message/path/extensions.code或直接用error.message记录日志不要用 HTTP 状态码判断 GraphQL 成功与否。认证自动注入借助默认的中间件链登录状态下的请求自动携带 Bearer Token无需手工拼接。独立使用若只需 GraphQL 能力可通过nhost/nhost-js/graphql子路径导入并用createAPIClient创建独立客户端。相关代码与测试可继续阅读graphql/client.ts、graphql/index.ts、fetch/fetch.ts、nhost.ts、graphql.test.ts、graphql-fragment-dedup.test.ts。【免费下载链接】nhostThe Open Source Firebase Alternative with GraphQL.项目地址: https://gitcode.com/GitHub_Trending/nh/nhost创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价