尝试做完整的 Fullstack React GraphQL TypeScript 教程#2~使用 MikroORM 和 TypeGraphQL 结合进行 PostgreSQL CRUD 实现~

首先

我是YN,一个从30代没有经验开始学习成为工程师的人。这篇文章是关于Ben Awad先生的《Fullstack React GraphQL TypeScript Tutorial》, 旨在帮助初学者进行学习。Ben先生的视频真的非常高质量,让我学到了很多知识,但像我这样的初学者却经常遇到困难,难以前进。所以,我写下这篇回顾性的笔记。

这次的事物目标

0:39:50 阿波罗服务器快速设置
0:47:32 MikroORM TypeGraphQL Crud

上次 => https://qiita.com/theFirstPenguin/private/9139c0b2f9e56c9a1e49

开始之前

复制分支

感谢你根据视频内容细分了不同的分支给我。首先将分支复制到本地,然后切换到该分支。

git pull origin 3_mikroORM-type-graphql-crud:3_mikroORM-type-graphql-crud
git checkout 3_mikroORM-type-graphql-crud

首先,要全面了解整体情况。

目前教程仍处于初期阶段,从Type-GraphQL开始,对于初学者来说会变得稍微复杂。因此,首先我整理了整体架构如下图所示。之前的内容使用了MikroORM,结合了TypeScript。

    • PSQLテーブルのSQLスキーマを定義

 

    • migrationを実行して、テーブルを作成

 

    insert などのSQLクエリを実行

我成功地做到了。

スクリーンショット 2020-11-10 20.20.47.png
スクリーンショット 2020-11-10 20.06.09.png

使用MikroORM和TypeGraphQL来实现GraphQL的查询功能。

動画和這篇文章的解說順序有些前後顛倒,但在這次的目標中,我們將解釋如何達到分支的程式碼,請理解。
首先,我們將從GraphQL的查詢(即在RestAPI中的GET方法)開始解釋。

将ApolloServer作为Express的中间件进行设置。

首先我们需要进行ApolloServer的配置。

const main = async () => {
  const orm = await MikroORM.init(microConfig); // MikroORMの初期化
  await orm.getMigrator().up(); // テーブルのスキーマを作成してテーブルを作成
  // ここまで前回の内容。

  const app = express(); // APIサーバーとしてexpressを使う

  const apolloServer = new ApolloServer({
    schema: await buildSchema({ 
    // ここでGraphQLのスキーマを定義する
    }),
  });

  apolloServer.applyMiddleware({ app }); // ApolloServerをexpressのミドルウェアとして使う。

  app.listen(4000, () => {
    console.log("server started on localhost:4000");
  });
};

main()

设置resolver的配置

正如上文所示,ApolloServer在接收到客户端请求后,需要设置解析器才能使用GraphQL API执行对PQSL表的SQL查询。

设置context属性

首先,我们设置context属性。
在context属性中,我们指定一个函数,该函数的返回值可以是一个对象。
该对象(在这种情况下为{em: orm.em})可以在所有解析器中共享。
通过这一点,我们可以在解析器中使用MicroORM的方法,实现通过TypeScript与PSQL表进行数据交互。

const main = async () => {
  const orm = await MikroORM.init(microConfig); // MikroORMの初期化
  ...

  const apolloServer = new ApolloServer({
    schema: await buildSchema({
      resolvers: [PostResolver], // resolverを定義。一つのテーブルに対して一つのresolverが対応する。
      context: () => ({ em: orm.em }), // contextプロパティを設定する。
    }),
  });

...

写一个解析器的描述

这里非常重要。我读了好几次官方文件,但是理解起来花了很长时间。

import { Resolver, Query, Ctx, Arg, Mutation } from "type-graphql";
import { Post } from "../entities/Post";
import { MyContext } from "../types";

@Resolver()
export class PostResolver {
  @Query(() => [Post])
  posts(@Ctx() { em }: MyContext): Promise<Post[]> {
    return em.find(Post, {});
  }
...
import { EntityManager, IDatabaseDriver, Connection } from "@mikro-orm/core";

export type MyContext {
  em: EntityManager<any> & EntityManager<IDatabaseDriver<Connection>>
}

在装饰器的盛大展示中,对于初学者来说相当困难。
首先,如果要梳理这里解决器的作用,

    • 1) クライアントからのリクエストおよびそれに応じるレスポンスを、GraphQLのAPIで定義

 

    2) リクエストに応じてPSQLテーブルに対してSQLクエリを実行

所以,重新查看代码后,我们可以确认上述1)和2)已经实现了。

@Resolver() // ①
export class PostResolver {
  @Query(() => [Post]) // ②
  posts(@Ctx() { em }: MyContext): Promise<Post[]> { // ③
    return em.find(Post, {});
  }
...

① 最初的@Resolver是一个类装饰器,它表明这个类是一个resolver。
② @Query(() => [Post])是一个方法装饰器,它指示GraphQL模式中返回值的类型。
在GraphQL中,请求有Query(Get)和Mutaiton(Get以外)两种类型,所以@Query表示这是一个(Get请求)。另外,GraphQL需要严格定义请求的返回值类型,在这里我们指示返回值是一个帖子类型的数组。(在这里,我们需要在GraphQL模式中单独定义帖子类型,另行说明)
③ 我们定义了一个名为posts的方法。当在PostResolver中指定posts查询时,该方法会返回em.find(Post,{}),也就是返回Post表中的所有元素。该方法的返回值由TypeScript规定为Promise<Post[]>,看起来与②重复,但是②表示GraphQL模式,③表示TypeScript类型,两者都需要进行定义。另外,@Ctx()是一个属性装饰器,它可以引用前面提到的context属性并使用它,从而可以使用MikroORM的em.find方法。同时,我们明确了返回值类型为MyContext。

你觉得怎么样?这是不是相当困难?一旦熟悉了就会很简单吗?
特别是,在Post中使用了”TypeScript的类型”和”GraphQL的架构类型”这两个意思,容易引起混淆,我想接下来解释一下这个问题。

在实体(表)中添加GraphQL模式的类型

スクリーンショット 2020-11-11 14.27.55.png
import { Entity, PrimaryKey, Property } from "@mikro-orm/core";

@Entity()
export class Post {
  @PrimaryKey()
  id!: number;

  @Property({ type: "date" })
  createdAt = new Date();

  @Property({ type: "date", onUpdate: () => new Date() })
  updatedAt = new Date();

  @Property({ type: "text" })
  title!: string;
}

因此,在entities/Post.ts文件中,我们可以使用从type-graphql导入的装饰器,并进行以下追加:

import { Entity, PrimaryKey, Property } from "@mikro-orm/core";
import { ObjectType, Field } from "type-graphql";

@ObjectType() // GraphQLのスキーマの型として定義する。これでPostクラスは、テーブルのスキーマ・TypScriptの型・GraphQLの型を兼ねる。
@Entity()
export class Post {
  @Field() // APIレスポンスとして使用するためには、GraphQLのフィールドであることを明示する。
  @PrimaryKey()
  id!: number;

  @Field(() => String) // GraphQLにDateという型はないので、Stringであることを明示する
  @Property({ type: "date" })
  createdAt = new Date();

  @Field(() => String)
  @Property({ type: "date", onUpdate: () => new Date() })
  updatedAt = new Date();

  @Field()
  @Property({ type: "text" })
  title!: string;
}
スクリーンショット 2020-11-11 14.28.03.png

通过使用MikroORM和TypeGraphQL来实现GraphQL Mutation。

接下来我将介绍GraphQL的Mutation(即Get以外的RestAPI)。然而,与前面的Query没有太大的区别。
与之前一样,我们通过使用名为Mutation()的方法装饰器来定义Mutation。
由于这是一个Mutation,所以请求中需要输入值,我们可以使用@Arg()属性装饰器来定义它。

import { Resolver, Query, Ctx, Arg, Mutation } from "type-graphql";
import { Post } from "../entities/Post";
import { MyContext } from "../types";

@Resolver()
export class PostResolver {
...
  @Mutation(() => Post, { nullable: true }) // ①
  async updatePost(
    @Arg("id") id: number, 
    @Arg("title", () => String, { nullable: true }) title: string, // ②
    @Ctx() { em }: MyContext // ③
  ): Promise<Post | null> {
    const post = await em.findOne(Post, { id }); // ③
    if (!post) {
      return null;
    }
    if (typeof title !== "undefined") {
      post.title = title;
      await em.persistAndFlush(post);
    }
    return post;
  }
...

① @Mutation(() => Post, { nullable: true })是一个方法装饰器,用于指示GraphQL模式中返回值的类型。在GraphQL中,有查询(Get)和变更(Get以外)两种请求,所以@Mutation表示它是一个不同于获取请求的变更请求(相当于RestAPI中的Get请求以外的请求)。此外,GraphQL需要严格规定请求的返回值类型,在这里我们指定返回值为Post类型。type-graphql的模式默认不允许为空,所以如果可能返回空值的话需要指定{ nullable: true }。

② 使用@Arg(“title”, () => String, { nullable: true })属性装饰器,用于规定GraphQL的Mutaion(或Query)的输入值。TypeScript的类型和GraphQL的类型不一定相匹配,如果不匹配的话需要进行明确说明,例如() => String。(但在这里两者都是String类型,所以不需要这样做。)然而,默认设置不允许为空,所以如果可能为空值的话,需要指定{ nullable: true }。另外,@Arg(“title”中的title是用于GraphQL模式中的输入值的名称,可以与TypeScript中的参数名称区分开来。

③ 使用@Ctx() { em }: MyContext属性装饰器,可以引用并使用在src/index.ts中定义的context属性,通过这样的方式可以使用em.findOne这个MikroORM的方法。同时,明确指定了返回值的类型为MyContext。

最後, 终究

这次我们把MikroORM TypeGraphQL Crud写完了。
如果能够实现上述所描述的Query和Mutation,就可以实现CRUD。

在14小时的教程中,我已经学习了差不多一个小时。
前方还有很长的路要走…

下一次 => https://qiita.com/theFirstPenguin/items/6dde647c01dad4e96e22

广告
将在 10 秒后关闭
bannerAds