尝试做完整的 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クエリを実行
我成功地做到了。
使用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模式的类型
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;
}
通过使用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