热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

使用Node.js+Docker+GraphQL+MongoDB构建服务

用了GraphQL之后,就不想再用RESTful了。本文将使用Node.js+Docker+GraphQL+MongoDB构建一个具有CRUD功能的完整微服务。运行代码,需要安装d

用了GraphQL之后,就不想再用RESTful了。

本文将使用Node.js+Docker+GraphQL+MongoDB构建一个具有CRUD功能的完整微服务。

运行代码,需要安装docker和docker-compose。

完整代码见:leinue/node-mongodb-graphql-docker

注:阅读本文需要有GraphQL基础。

Docker

dockerfile

FROM node
WORKDIR /app
EXPOSE 5555

从node构建,将工作目录设置为/app,暴露5555端口

docker-compose

version: "3"
services:
user_service:
build: .
links:
- user_db
command: ["node", "index.js", ' && /bin/bash']
hostname: user_service_in_container
volumes:
- ./:/app
deploy:
replicas: 1
restart_policy:
condition: on-failure
ports:
- "5555:5555"
user_db:
image: mongo
volumes:
- "/tmp/db:/data/db"
restart: always

声明了以下任务:

  1. 声明user_service服务和user_db服务
  2. user_service:
    1. 从当前目录的dockerfile构建
    2. 与user_db连接
    3. 在启动时执行node index.js和/bin/bash
    4. 挂载当前目录到/app目录下
    5. 复制一份
    6. 在失败时重启
    7. 暴露容器内的5555端口到宿主机的5555端口
  3. user_db
    1. 从mongo构建
    2. 将容器内的/data/db挂载到宿主机上的/tmp/db上(数据持久化)
    3. 总是重启

运行命令

docker-compose up -d

实现代码

使用babel编译为ES6代码

事实上现在的node v8已经支持大部分ES6代码了,但是async仍不支持,为了使用async不得不使用babel编译。

.babelrc

{
"presets": ["es2015"],
"plugins": ["syntax-async-generators", "transform-async-generator-functions", "transform-regenerator"]
}

需要的babel包

{
"babel-plugin-syntax-async-generators": "^6.13.0",
"babel-plugin-transform-async-generator-functions": "^6.24.1",
"babel-plugin-transform-regenerator": "^6.26.0"
"babel-core": "^6.26.0",
"babel-polyfill": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
}

安装之后创建index.js

require('babel-core/register');
require("babel-polyfill");
require('./server.js');

这样就可以在server.js中使用es6的代码了。

server.js

server.js用来初始化graphql服务器、路由和连接mongodb。这里使用koa和graphql服务端的插件来初始化。

import koa from 'koa'; // koa@2 import koaRouter from 'koa-router'; // koa-router@next import koaBody from 'koa-bodyparser'; // koa-bodyparser@next import { graphqlKoa, graphiqlKoa } from 'apollo-server-koa';
import cors from 'koa-cors';
import convert from 'koa-convert';
import configs from './configs';
import mongoose from 'mongoose';
const app = new koa();
const router = new koaRouter();
const db = mongoose.createConnection(['mongodb://', configs.mongodb.ip, '/', configs.mongodb.dbname].join(''));
if(db) {
console.log('mongodb connected successfully');
global.db = db;
}else {
console.log('mongodb connected failed');
}
import schemaRouters from './routers/schemaRouters';
const schemas = schemaRouters().default;
router.post('/graphql', koaBody(), graphqlKoa({ schema: schemas.HelloSchema }));
router.get('/graphql', graphqlKoa({ schema: schemas.HelloSchema }));
router.get('/graphiql', graphiqlKoa({ endpointURL: '/graphql' }));
app.use(convert(cors(configs.cors)));
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(configs.port, () => {
console.log('app started successfully, listening on port ' + configs.port);
});

这段初始化代码将/graphql作为graphql数据收发的路由地址,服务将启动在5555端口。

构建MongoDB数据类型

import { Schema } from 'mongoose';
var helloSchema = new Schema({
email: String,
lastIP: String,
});
export default global.db.model('Hello', helloSchema);

初始化一个helloSchema,并将这个model命名为hello

还需要一个index.js将数据类型全部引入:

import HelloModel from './HelloModel'
export default {
HelloModel,
}

构建GraphQL Schema

一个schema需要分成三部分:

  1. mutations
    1. 修改/删除/增加操作
  2. queries
    1. 查询操作
  3. types
    1. 数据类型定义(输入/输出)

以HelloSchema为例,其文件结构如下:

.
├── index.js
├── mutations
├── add.js
├── index.js
├── remove.js
└── update.js
├── queries
├── hello.js
└── index.js
└── types
├── Hello.js
├── HelloAddInput.js
├── HelloFields.js
└── HelloUpdateInput.js

各文件作用如下:

  • index.js
    • 初始化query和mutation
  • mutations
    • add.js
      • 执行增加操作
    • index.js
      • 将增加/修改/删除操作引用到一起
    • remove.js
      • 执行删除操作
    • update.js
      • 执行更新操作
  • queries
    • hello.js
      • 执行查询操作
    • index.js
      • 将查询操作引用到一起
  • types
    • Hello.js
      • 定义返回结果的数据类型
    • HelloAddInput.js
      • 定义增加操作时输入结构的数据类型
    • HelloUpdateInput.js
      • 定义更新操作时输入结构的数据类型
    • HelloFields.js
      • 定义HelloSchema的通用数据结构(和HelloModel内容相同)

index.js

初始化query和mutation

import {
GraphQLObjectType,
GraphQLSchema,
GraphQLList
} from 'graphql';
import mutations from './mutations';
import queries from './queries';
let schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: queries
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: mutations
})
});
export default schema;

queries/hello.js

执行查询操作

import { GraphQLList, GraphQLString } from 'graphql';
import HelloType from '../types/Hello.js';
import HelloModel from '../../../models/HelloModel';
const hello = {
type: new GraphQLList(HelloType),
async resolve (root, params, options) {
var hello = await HelloModel.find({});
return hello;
}
}
export default hello;

定义了返回结果是一个HelloType的数组列表 ,resolve中使用了async函数进行mongodb异步查询。

mutations/add.js

执行增加操作

import { GraphQLNonNull } from 'graphql';
import HelloType from '../types/Hello.js';
import HelloAddInput from '../types/HelloAddInput.js';
import HelloModel from '../../../models/HelloModel';
const add = {
type: HelloType,
args: {
info: {
name: 'info',
type: new GraphQLNonNull(HelloAddInput)
}
},
async resolve (root, params, options) {
const HelloModel = new HelloModel(params.info);
const newHello = await HelloModel.save();
if (!newHello) {
return false;
}
return newHello;
}
};
export default add;

注意其中args,其类型是HelloAddInput

HelloAddInput定义如下:

import {
GraphQLInputObjectType,
GraphQLString,
GraphQLID,
GraphQLNonNull
} from 'graphql';
export default new GraphQLInputObjectType({
name: 'HelloAddInput',
fields: {
email: {
type: GraphQLString
},
lastIP: {
type: GraphQLString
}
}
});

声明了在执行添加操作时需要输入email和lastIP参数。

mutations/update.js

执行更新操作

import { GraphQLNonNull } from 'graphql';
import HelloType from '../types/Hello.js';
import HelloModel from '../../../models/HelloModel';
import HelloUpdateInput from '../types/HelloUpdateInput.js';
const update = {
type: HelloType,
args: {
options: {
name: 'options',
type: new GraphQLNonNull(HelloUpdateInput)
}
},
async resolve (root, params, options) {
const updated = await HelloModel.findOneAndUpdate({
_id: params.options._id
}, params.options);
const hello = await HelloModel.findOne({
_id: params.options._id
});
return hello;
}
};
export default update

注意其中的参数options,其数据类型为HelloUpdateInput。

HelloUpdateInput定义如下:

import {
GraphQLObjectType,
GraphQLInputObjectType,
GraphQLNonNull,
GraphQLString,
GraphQLID,
GraphQLInt,
GraphQLBoolean
} from 'graphql';
import HelloFields from './HelloFields';
export default new GraphQLInputObjectType({
name: 'HelloUpdateInput',
fields: HelloFields
});

定义了在更新字段时可以使用HelloFields内的任意字段,HelloFields定义如下:

import {
GraphQLString,
GraphQLInt,
GraphQLBoolean
} from 'graphql';
export default {
_id: {
type: GraphQLString
},
email: {
type: GraphQLString
},
lastIP: {
type: GraphQLString
}
}

其和model是一样的

mutations/remove.js

执行删除操作

import { GraphQLList, GraphQLString } from 'graphql';
import HelloType from '../types/Hello.js';
import HelloModel from '../../../models/HelloModel';
const remove = {
type: new GraphQLList(HelloType),
args: {
ids: {
name: 'ids',
type: new GraphQLList(GraphQLString)
}
},
async resolve (root, params, options) {
let removedList = [];
for (var i = 0; i < params.ids.length; i++) {
const _id = params.ids[i];
const removed = await HelloModel.findOneAndRemove({
_id
});
if(removed) {
removedList.push(removed)
}
};
return removedList;
}
}
export default remove

注意其中的ids是一个GraphQLList字符串数组,返回结果是HelloType对象。

执行GraphQL查询

启动程序

docker-compose up -d

打开GraphQL测试界面:http://localhost:5555/graphiql

先来执行一个查询:

《使用Node.js+Docker+GraphQL+MongoDB构建服务》
《使用Node.js+Docker+GraphQL+MongoDB构建服务》

可以看到结果返回空。

再执行一个增加操作:

《使用Node.js+Docker+GraphQL+MongoDB构建服务》
《使用Node.js+Docker+GraphQL+MongoDB构建服务》

可以看到右侧返回了新增的数据及其id

我们再将emai修改为“fuck_shit”:

《使用Node.js+Docker+GraphQL+MongoDB构建服务》
《使用Node.js+Docker+GraphQL+MongoDB构建服务》

右侧成功返回了修改之后的数据。

最后,我们删除这条数据:

《使用Node.js+Docker+GraphQL+MongoDB构建服务》
《使用Node.js+Docker+GraphQL+MongoDB构建服务》

返回了被删除的id。

再最后执行一次查询:

《使用Node.js+Docker+GraphQL+MongoDB构建服务》
《使用Node.js+Docker+GraphQL+MongoDB构建服务》

可以看到结果又变为空了。

至此,整个GraphQL+MongoDB的CRUD操作测试成功。

小Tips

graphql的测试器右侧可以查看数据类型:

《使用Node.js+Docker+GraphQL+MongoDB构建服务》
《使用Node.js+Docker+GraphQL+MongoDB构建服务》

《使用Node.js+Docker+GraphQL+MongoDB构建服务》

《使用Node.js+Docker+GraphQL+MongoDB构建服务》

完整代码见:leinue/node-mongodb-graphql-docker


推荐阅读
author-avatar
守望天空2502870383
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有