使用Typescript安全地开发Apollo服务器

在编写Apollo服务器时,由于大部分文档都是用JavaScript和TypeScript编写的,所以有点困扰。

由GraphQL Codegen自动生成代码

GraphQL的魅力主要体现在其可靠的模式(Schema)上。GraphQL Codegen的目的就是借鉴这种可靠性,并将其应用在TypeScript的类型定义中。

请先从npm上获得所需的模块。

npm install @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-resolvers graphql-toolkit --save-dev

如果你创建了一个用SDL编写的schema.graphql文件,那么请在根文件夹附近编写codegen.yml文件。

overwrite: true
documents: null
schema: "**/*.graphql"
generates:
  src/generated/graphql.ts:
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-resolvers"
    config:
      useIndexSignature: true
      avoidOptionals: true

在`src/generated/graphql.ts`中生成了许多类型。其中包括一些特定于Apollo的类型,同时还有一些普通类型和输入类型,可以直接用作类型,并进行有效利用。

创建入口点

几乎和教程一样,创建一个index.ts文件。

import { ApolloServer, gql } from 'apollo-server';
import fs from 'fs';
import path from 'path';

import resolvers from './resolvers'; //後で定義
import { dataSources, context } from './datasources'; //後で定義


const typeDefs = gql(fs.readFileSync(path.resolve(__dirname, '<スキーマファイルへのパス>'), 'utf8'));

const server = new ApolloServer({
  typeDefs,
  resolvers,
  dataSources,
  context
});

server.listen(4002).then(({ url }) => {
  console.log(`? Server ready at ${url}`);
});

对于 typeDefs 的定义,我感觉不够优雅…正在寻找更好的方法。

数据源,上下文的定义。

首先,让我们定义一下背景情况。

export const context = async () => ({
  /* Contextの定義 */
  ctx1: "foo"
  ctx2: "bar"
});

type PartialContext = ReturnType<typeof context> extends Promise<infer T> ? T : never;

请参考我的以往文章来了解有关Typescript的infer的信息❤️

接下来定义DataSources。

export const dataSources = () => ({
  /* Datasourceの定義 */
  ds1: new MyDataSource1<PartialContext>(),
  ds2: new MyDataSource2<PartialContext>()
});

为什么DataSource使用泛型将在后面进行说明。实际使用的上下文是将数据源添加到PartialContext中的内容。


export type TContext = PartialContext & {
  dataSources: ReturnType<typeof dataSources>
};

添加自己的数据源

我們這樣做吧。 zuò ba.)

import { DataSource, DataSourceConfig } from 'apollo-datasource';
import { InMemoryLRUCache, KeyValueCache } from 'apollo-server-caching';

export interface Context {
  ctx1: string
}

export default class MyDataSource1<T extends Context> extends DataSource<T> {

  context?: Context
  cache?: KeyValueCache

  constructor() {
    super();
  }

  initialize({ context, cache }: DataSourceConfig<Context>) {
    this.context = context;
    // this.context.ctx1にアクセスできる
    this.cache = cache || new InMemoryLRUCache();
  }
}

使用泛型的目的是为了确保DataSource所需的上下文与PartialContext保持一致。
使用泛型是为了确保DataSource所需的上下文与PartialContext保持一致。

解析程序的定义

import { TContext } from './datasources';
import { Resolvers } from './generated/graphql';

export const resolvers: Resolvers<TContext> = {
   // resolverのなかみ
};

终于看到了graphql-codegen的优点。通过使用Resolvers和TContext,可以推断出解析器函数的参数类型。还可以将解析器分解并使文件更加紧凑。

import { TContext } from './datasources';
import { Resolvers, QueryResolvers, UserResovlers } from './generated/graphql';

const queryResolvers: QueryResolvers<TContext> = {...}
const userResolvers: UserResolvers<TContext> = {...}

export const resolvers: Resolvers<TContext> = {
  Query: queryResolvers,
  User: userResolvers 
};

标量的定义。

这个scalar的规格太难懂了,感觉有点糟糕。

这个样子

import {
  GraphQLScalarType,
  Kind,
  GraphQLScalarTypeConfig,
  ValueNode
} from 'graphql';
import moment from 'moment';

const config: GraphQLScalarTypeConfig<moment.Moment, string> = {
  name: 'DateTime',
  description: 'DateTime custom scalar type, in the form of YYYY-MM-DDTHH:mm:ss',
  parseValue(value: string) {
    return moment(value);
  },
  serialize(value: moment.Moment) {
    return value.format();
  },
  parseLiteral(ast: ValueNode) {
    switch (ast.kind) {
      case Kind.STRING:
        return moment(ast.value);
      default:
        return null;
    }
  }
};

export const datetimeScalar = () => new GraphQLScalarType(config);

GraphQLScalarTypeConfig<moment.Moment, string>表示的是一个Scalar类型,它在SDL中具有string类型,并且在解析器的第二个参数中具有Moment类型。parseValue用于将SDL转换为解析器,而serialize用于将解析器转换为SDL。

对于parseLiteral,您打算用于什么目的呢?我有点搞不懂。

Directive的定义是什么?

因为我还没有使用过,所以不知道。

使用联邦

使用Apollo的原因之一可能是基于联邦机制,容易定义微服务。

首先,在codegen.yml中添加以下内容。

generates:
  src/generated/graphql.ts:
    plugins:
      ...
    config:
      avoidOptionals: true
      useIndexSignature: true
      federation: true #追加

只需要按照教程的步骤设置模式即可。

广告
将在 10 秒后关闭
bannerAds