基于 egg.js 构建 graphql api 服务

2022-04-25 16:59:49 浏览数 (1)

基于 egg.js 构建 graphql api 服务

登录快速注册

基于 egg.js 构建 graphql api 服务

Egg.js 简介:https://eggjs.org/zh-cn/index.html

生成项目

通过骨架快速初始化,该操作会生成一个极简版的示例,完整示例参见:eggjs/examples/hackernews-async-ts

代码语言:javascript复制
$ mkdir egg && cd egg
$ npm init egg --type=ts
$ npm i

启动项目

代码语言:javascript复制
$ npm run dev
$ open http://localhost:7001

安装插件

@switchdog/egg-graphql

插件机制是egg的一大特色,由于我们基于 TypeScript ,所以选择了支持 TS 的包。

代码语言:javascript复制
npm i -s @switchdog/egg-graphql

开启插件

config/plugin.ts 下告诉 egg 开启哪些插件:

代码语言:javascript复制
graphql: {
    enable: true,
    package: '@switchdog/egg-graphql',
},

配置插件

通常插件都会有一些配置项,在/config/config.default.ts中配置即可:

代码语言:javascript复制
config.graphql = {
    router: '/graphql',
    // 是否加载到 app 上,默认开启
    app: true,
    // 是否加载到 agent 上,默认关闭
    agent: false,
    // 是否加载开发者工具 graphiql, 默认开启。路由同 router 字段。使用浏览器打开该可见。
    graphiql: true,
    apolloServerOptions: {
      tracing: true, // 设置为true时,以Apollo跟踪格式收集和公开跟踪数据
      debug: true, // 一个布尔值,如果发生执行错误,它将打印其他调试日志记录
    },
  };

在中间件中开启 graphql

代码语言:javascript复制
config.middleware = [ 'graphql' ];

配置完成之后,每个落到 /graphql的请求都会触发 GraphQL Schema 的查询。

代码结构

graphql 目录下有 4 种代码,分别是:

common 全局类型定义

query 查询代码

mutation 更新操作代码

resolver业务实现代码

代码语言:javascript复制
.
├── graphql                       | graphql 代码
│   ├── common                    | 通用类型定义
│   │   ├── resolver.js           | 合并所有全局类型定义
│   │   ├── scalars               | 自定义类型定义
│   │   │   └── date.js           | 日期类型实现
│   │   └── schema.graphql        | schema 定义
│   ├── mutation                  | 所有的更新
│   │   └── schema.graphql        | schema 定义
│   ├── query                     | 所有的查询
│   │   └── schema.graphql        | schema 定义
│   └── user                      | 用户业务
│       ├── connector.js          | 连接数据服务
│       ├── resolver.js           | 类型实现
│       └── schema.graphql        | schema 定义

CORS

CORS,常被大家称之为跨域问题,准确的叫法是跨域资源共享**(CORS,Cross-origin resource sharing)**,是W3C标准,是一种机制,它使用额外的HTTP头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。

安装 egg-cors

代码语言:javascript复制
npm i egg-cors --save

开启 /config/plugin.ts

代码语言:javascript复制
cors: {
    enable: true,
    package: 'egg-cors',
},

配置 /config/config.default.ts

代码语言:javascript复制
config.cors = {
    origin: '*', 
    allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
};

CSRF

CSRF(Cross-site request forgery)跨站请求伪造,也被称为 One Click Attack 或者 Session Riding,通常缩写为 CSRF 或者 XSRF,是一种对网站的恶意利用。

使用 graphqlrest 端点时,实际上不必担心使用 CSRF 保护。对服务的请求应该是无状态的,并且不真正依赖Cookie或会话数据。

关闭 /config/config.default.ts

代码语言:javascript复制
config.security = {
    csrf: {
      ignore: () => true,
    },
};

实现一个简单的 GraphQL API

编写 hello 业务

schema.graphql

graphql 自带一组默认标量类型,包括 IntFloatStringBooleanID。在定义字段时需要注明类型,这也是 graphql 的特点之一,是支持强类型的。如果非空,就在类型后面跟上一个!号。graphql 还包括枚举类型,列表和自定义类型。

代码语言:javascript复制
type Hello {
  id: ID!
  name: String!
}
connector

编写完 schema 之后,graphql 知道有哪些数据了,但他还需要知道 “如何去取”connector 的角色就在于此。 connector 的职责就是 “取数”, 他既可以调用 rpc 接口取数,又可以调用内置的 orm 插件去取数,还可以直接调用 egg 的 service

代码语言:javascript复制
export default class HelloConnector {
  hellos() {
    return [
      {
        id: 1,
        name: 'Jack',
      },
      {
        id: 2,
        name: 'Lucy',
      },
    ];
  }
}
resolver

resolve.js是数据类型的具体实现,依赖connector.js完成。其实 resolver 非常简单,就是针对你暴露的查询接口,调用相应的connector去取数即可,如下:

代码语言:javascript复制
export default {
  Query: {
    hellos(_root: any, {}, { connector }) {
      return connector.hello.hellos();
    },
  },
};

定义 Query

新建一个 query 目录创建 schema.graphql 文件,也可以直接在各个模块下的 schema.graphql 文件中定义

代码语言:javascript复制
type Query {
  hellos: [Hello!]
}

[Hello!] 可以理解为 [{id: 1, name: 'jack'}, {id: 2, name: 'praise'}] Hello! 可以理解为 {id: 1, name: 'jack'}

项目启动

代码语言:javascript复制
npm run dev

在浏览器中输入 http://127.0.0.1:7001/graphql 出现如下界面说明已经 graphql 服务已经成功运行。

完成一次查询

代码语言:javascript复制
# Write your query or mutation here
{
	hellos {
  	id
    name
	}
}

执行得到

代码语言:javascript复制
{
  "data": {
    "hellos": [
      {
        "id": "1",
        "name": "Jack"
      },
      {
        "id": "2",
        "name": "Lucy"
      }
    ]
  }
}

设置别名

代码语言:javascript复制
# Write your query or mutation here
{
	users: hellos {
  	id
    name
	}
}

得到

代码语言:javascript复制
{
  "data": {
    "users": [
      {
        "id": "1",
        "name": "Jack"
      },
      {
        "id": "2",
        "name": "Lucy"
      }
    ]
  }
}

请求流程

通过上方的例子我们可以看出客户端发送请求会被 graphql 解析,根据映射关系找到对应的 resolver。路由将数据传递到对应的 resolverresolver 去调用对应的 connector 进行处理,connector 再调用 service 进行数据库处理。

从MongoDB查询数据

安装 egg-mongoose

代码语言:javascript复制
yarn add egg-mongoose

配置

代码语言:javascript复制
// config/plugin.ts
exports.mongoose = {
  enable: true,
  package: 'egg-mongoose',
};

// config/config.default.ts
exports.mongoose = {
  client: {
    url: 'mongodb://127.0.0.1/example',
    options: {},
    // mongoose global plugins, expected a function or an array of function and options
    plugins: [createdPlugin, [updatedPlugin, pluginOptions]],
  },
};

Schema

代码语言:javascript复制
// app/model/user.ts
module.exports = (app: any) => {
  const mongoose = app.mongoose;
  const Schema = mongoose.Schema;

  const UserSchema = new Schema({
    open_id: String!,
    info: {
      nick: String!,
      name: String!,
      gender: Number!,
      city: String!,
      province: String!,
      country: String!,
      avatar: String!,
      phone: String!
    },
    address: [
      {
        name: String!,
        phone: String!,
        addr: String!,
        default: Boolean,
        sign: String!
      }
    ],
    carts: [
      {
        gid: Number!,
        num: Number!
      }
    ],
    favorite: {
      goods: [String],
      content: [String]
    },
    browse: [String],
    createdAt: Number,
    updatedAt: Number
  });

  return mongoose.model('User', UserSchema);
}

Controller

代码语言:javascript复制
// app/model/user.ts
import { Controller } from 'egg';

export default class UserController extends Controller {
  public async index() {
    const { ctx } = this;
    ctx.body = await ctx.model.User.find({});
  }
}

Router

代码语言:javascript复制
// app/router.ts
import { Application } from 'egg';

export default (app: Application) => {
  const { controller, router } = app;
  router.get('/user', controller.user.index);
};

结果

0 人点赞