前幾天看了一篇文章 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一波❤~
轉載請註明原文連接和做者。