前几天看了一篇文章 GraphQL 搭配 Koa 最佳入门实践,很是详细地讲述了koa集成graphql的实战, 可是是js的版本,并且由于是两年前的文章了,有的包如今也已经更新了使用方式。
因此心血来潮,基于原做者的版本更新了一个ts版本的。javascript
代码戳这里 https://github.com/sunxiuguo/Koa-GraphQL-Template,喜欢的话动动小手star一波❤~java
考虑到后续type-graphql的使用, 咱们把实体的定义单独拿出来,新建一个entities文件夹。
node
src/entities/info.ts
export class Info { // 数组类型 @arrayProp({ items: String }) public hobby!: string[]; @prop() public height!: string; @prop() public weight!: number; // 其余文件定义的Meta类型 @prop({ _id: false }) public meta!: Meta; // 不生成_id }
export const InfoModal = getModelForClass(Info); // 是否是很简单!
原来的mongoose hooks,好比schema.pre('save', function(){}),改成了装饰器的写法git
@pre<Info>('save', function() { // 直接this.meta.xxx赋值会不生效 this.meta = this.meta || {}; if (this.isNew) { this.meta.createdAt = this.meta.updatedAt = Date.now(); } else { this.meta.updatedAt = Date.now(); } }) export class Info { @arrayProp({ items: String }) public hobby!: string[]; @prop() public height!: string; @prop() public weight!: number; @prop({ _id: false }) public meta!: Meta; // 不生成_id } export const InfoModal = getModelForClass(Info);
src/entities/student.ts
import { prop, getModelForClass, Ref, pre } from '@typegoose/typegoose'; import { Info } from './info'; import { Meta } from './meta'; @pre<Student>('save', function() { // 直接this.meta.xxx赋值会不生效 this.meta = this.meta || {}; if (this.isNew) { this.meta.createdAt = this.meta.updatedAt = Date.now(); } else { this.meta.updatedAt = Date.now(); } }) export class Student { @prop() public name!: string; @prop() public sex!: string; @prop() public age!: number; // info字段是和Info实体的id关联的,在typegoose中,咱们经过Ref来定义 @prop({ ref: Info }) public info!: Ref<Info>; @prop({ _id: false }) public meta!: Meta; } export const StudentModel = getModelForClass(Student);
咱们上面已经定义了一遍student和info实体的类型,若是用type-graphql再定义一遍,岂不是麻烦到爆炸💥!
所幸type-graphql提供的也是class定义的方式,使用装饰器来定义各类类型,咱们能够直接在student.ts和info.ts里加几行type-graphql的定义便可。github
src/entities/student.ts
import { Field, ObjectType } from 'type-graphql'; @ObjectType() export class Student { @Field({ description: 'id' }) public _id?: string; @Field({ description: '姓名' }) @prop() public name!: string; @Field({ description: '性别' }) @prop() public sex!: string; @Field({ description: '年龄' }) @prop() public age!: number; @Field(() => Info, { description: 'infoid' }) @prop({ ref: Info }) public info!: Ref<Info>; @Field(() => Meta, { description: '时间' }) @prop({ _id: false }) public meta!: Meta; }
这里分别定义了带参的查询,关联查询以及新增的三个方法。数组
src/graphql/resolvers/student.ts
import { Resolver, Query, Mutation, Arg, InputType, Field, Args, ArgsType } from 'type-graphql'; import { StudentModel, Student } from '../../entities/student'; @Resolver(Student) export class StudentResolver { @Query(() => [Student], { nullable: true, description: '查询学生列表' }) async students(@Args() args: StudentArgs) { // TODO args have to be required? return await StudentModel.find(args); } @Query(() => [Student], { nullable: true, description: '查询学生列表(带Info)' }) async studentsWithInfo() { return await StudentModel.find({}) .populate('info', 'hobby height weight') .exec(); } @Mutation(() => Student) async saveStudent(@Arg('data') newStudent: StudentInput) { const student = new StudentModel(newStudent); const res = await student.save(); return res; } }
type-graphql里,对于query参数和mutation的参数须要分别用ArgsType()和InputType()定义。这里由于个人两种参数有区别,因此定义了两份。app
@InputType() class StudentInput { @Field({ description: '姓名', nullable: false }) public name!: string; @Field({ description: '性别', nullable: false }) public sex!: string; // 考虑用enum @Field({ description: '年龄', nullable: false }) public age!: number; @Field({ description: 'infoid', nullable: false }) public info!: string; } @ArgsType() class StudentArgs { @Field({ description: '姓名', nullable: true }) public name?: string; @Field({ description: '性别', nullable: true }) public sex?: string; @Field({ description: '年龄', nullable: true }) public age?: number; @Field({ description: 'infoid', nullable: true }) public info?: string; }
src/graphql/index.ts
原文中的graphql-server-koa包已经更新为apollo-server-koa,本文使用的是v2版本。
koa
如今咱们已经定义好了schema,接下来要初始化apolloServer。
由于初始化时须要传入schema参数,因此咱们先来获取schema。async
import { buildSchema } from 'type-graphql'; import path from 'path'; // 获取匹配全部resolver的路径 function getResolvers() { return [path.resolve(__dirname + '/resolvers/*.ts')]; } // 经过buildSchema方法来生成graphql schema async function getSchema() { return buildSchema({ resolvers: getResolvers() }); }
由于我们是针对已有的koa服务,集成graphql,因此咱们写一个单独的函数来作这个。mongoose
export async function integrateGraphql(app: Koa<Koa.DefaultState, Koa.DefaultContext>) { const server = new ApolloServer({ schema: await getSchema(), playground: { settings: { 'request.credentials': 'include' } } as any, introspection: true, context: ({ ctx }) => ctx }); server.applyMiddleware({ app }); return server; }
在初始化Koa时,就能够直接调用这个函数支持graphql了
const app = new Koa(); integrateGraphql(app).then(server => { // 使用 bodyParser 和 KoaStatic 中间件 app.use(bodyParser()); app.use(KoaStatic(__dirname + '/public')); app.use(router.routes()).use(router.allowedMethods()); app.listen(4000, () => { console.log(`server running success at ${server.graphqlPath}`); }); });
这个很简单,咱们安装好nodemon后,直接添加一条命令就好.
nodemon --watch src -e ts --exec ts-node src/start.ts
这条命令表明的是,监听src文件下的全部ts文件,而且执行src/start.ts文件。
yarn serve
如今咱们就能够访问graphql的查询页面,开始查查查,改改改了!
http://localhost:4000/graphql
我们先来插入一条info
mutation { saveInfo(data: { hobby:["唱","跳","rap","篮球"], height:"165", weight: 100}){ hobby height weight } }
而后再来查询一波
# Write your query or mutation here query { # students(age:22){ # sex # name # age # } # studentsWithInfo { # sex # name # age # } infos { _id height weight hobby } }
妥了!!!
代码戳这里 https://github.com/sunxiuguo/Koa-GraphQL-Template,喜欢的话动动小手star一波❤~
转载请注明原文连接和做者。