基于上一篇的项目继续深入学习和介绍项目中如何使用GraphQl.本篇更侧重于实际项目中使用GraphQL.如果还没有了解基本的GraphQl知识和没用想要新建项目开始演练的可从
上一篇开始如何 在NodeJs搭建GraphQL service Apollo Server (1).
1.重构app.js和service.mjs
1.1 修改service.mjs
上一篇提到我们使用的是epress-generator创建的的我们nodejs的express项目.所以我们当时开辟了两个进程挂载server. Aollo Server很贴心的支持express,所以我们现在(2024/5) 可以在一个进程挂载Nodejs的epress server 和Graphql的plugin server.
在Graphql的实际项目中我们更习惯使用http,rest这种方式去发送和获取数据.首先安装一个扩展包 datasource-rest
npm i @apollo/datasource-rest
Apollo的rest扩展包可以方便我fetch数据和编写fetch的一些列有relate的操作函数.
在sercive.mjs
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'
引入rest包和express的中间件包
创建express得server和设置Apollp server.并启动Apollo 的服务.
const httpServer = http.createServer(app);
// Set up Apollo Server
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
1.2 创建nodejs中间件
app.use(
'/graphql',
cors(),
bodyParser.json(),
expressMiddleware(server, {
context: async () => {
const { cache } = server;
return {
dataSources: {
randomUserApi: new GetUserApi({ cache }),
},
}
}
}),
);
其中 dataScource可以理解指代的是我们rest api的api集合.
randomUserApi 这个就是我们的api的接口名称
GetUserApi: 实际rest的实体(实例)
dataSources: {
randomUserApi: new GetUserApi({ cache }),
},
因为是一个实例所以当我有N多个API的时候我还会像抽出来单独管理比较简洁
所以单独抽出这个文件
1.3 getUserApi.js
import {RESTDataSource} from '@apollo/datasource-rest'
class GetUserApi extends RESTDataSource {
constructor() {
super();
this.baseURL = "http://localhost:8001/";
}
async getUserInfor () {
const result = await this.get(`test/`)
return result.results
}
async getUserInforByName (userName) {
return await this.post(
`getUserInforByName`,
{body: {userName}},
)
}
async deleteUserInforById (id) {
return await this.delete(
`deleteUserInforById/${encodeURIComponent(id)}`
)
}
}
export default GetUserApi
这个实例类中的几个method很好理解.分别给get, post,delete使用.post的请求只是一个条件传参.Graphql的多组参数,条件参数,多表关联查询以后会讲.
baseURL:这个指向的就是我们的数据源
1.4 创建测试数据
作为demo的数据演示,我在db中加了一些测试数据,并开了一个db的server,postman看下测试的数据结构
实际的应用中db的数据源可以和Graphql的server使用同一个,例子中方便理解额外开了一个.
1.4. 创建schema& 封装typeDefs
在utils下创建一个 schema.js文件, 并声明初我们的rest api的接口名称.这里的randomeDates就是我们声明的接口名称,其中dataScourece对应NodeJs中间件中的api集合.这里的意思是调用在这个接口下返回我们getUserInfor的函数的值(getUserInfor是我们getUserApi.js中定义.
const typeDefs = `#graphql
enum LengthUnit {
METER
KILOMETER
MILE
FOOT
}
type userDatas{
username: String
dateCreate:String
id: String
length(unit: LengthUnit = METER): Float
}
type Query {
randomeDatas:[userDatas]
}
`;
const resolvers = {
Query: {
randomeDatas: (_, __, {dataSources}) =>
dataSources.randomUserApi.getUserInfor()
},
};
export {
typeDefs,
resolvers
}
回到service.mjs 引用我们的typeDefs和resolvers.最终如下
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'
// import {startStandaloneServer} from '@apollo/server/standalone'
import http from 'http';
import cors from 'cors';
import bodyParser from 'body-parser';
import app from '../../app.js'
import {typeDefs, resolvers} from './schema.js'
import GetUserApi from './getUserApi.js'
const httpServer = http.createServer(app);
// Set up Apollo Server
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
app.use(
'/graphql',
cors(),
bodyParser.json(),
expressMiddleware(server, {
context: async () => {
const { cache } = server;
return {
dataSources: {
randomUserApi: new GetUserApi({ cache }),
},
}
}
}),
);
await new Promise((resolve) => httpServer.listen({ port: 4000 }, resolve));
console.log(`🚀 Server ready at http://localhost:4000`);
1.5 执行resapi查看测试结果
在游览器器4000端口下查看结果
2. post和get的调用
一般在网上的介绍中到这步就完事.但是作为一个全栈这显然只完成了后端的工作.如何调用这个接口并且发送post和rest等请求呢
干货
get请求格式:
http://myapi/graphql?query={me{name}}
post请求格式:
{
"query": "...",
"operationName": "...",
"variables": { "myVariable": "someValue", ... }
}
operationName是使用多个条件下配置 - 可选
varibales是可以额外添加一个json格式的的查询参数. -可选
2.1 Nodejs创建前端调用实例
这里我就不用前端的框架了直接nodejs来做一个简单测试页面.
改写 app.js
import express from 'express'
import path, {dirname} from 'path'
import {fileURLToPath} from 'url'
import cookieParser from 'cookie-parser'
import logger from 'morgan'
import cors from 'cors';
import indexRouter from './routes/index.js'
const app = express();
const __fileURLToPath =fileURLToPath(import.meta.url);
const __dirname = dirname(__fileURLToPath);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(cookieParser());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
app.use(logger("dev"))
const corsOptions = {
origin: '*',
optionsSuccessStatus: 200,
credentials:true
}
app.use(cors(corsOptions))
app.use("/", indexRouter)
export default app;
创建一个"/"路由指向index.js.配置下模版路径和使用的模版类型.
配置运行环境和设置cookies & 日志
2.2 ejs模版
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body >
<div>Hello Evan <%= title %></div>
<div> <%= msg %></div>
</body>
配置下要显示的结果集
2.3 创建请求实例
注:在router中创建实例并不是一个好主意,实际项目中最后另外整合到一个请求集合中.
创建路由捕获,并加载一个中间件,在得到返回值后显示返回数据渲染模版输出
router.get('/a',getBacicServerAddress ,async (req, res, next) => {
const result = await handleGetUserData(req?.body?.basicUrl);
res.render('table', {userInfo:result.data.randomeDatas})
});
中间件
const getBacicServerAddress = (req, res, next) => {
const serverAddress = `${req.protocol}://${req.hostname}:${req.socket.localPort}`;
req.body = {
...req.body,
basicUrl: serverAddress
}
next();
}
这里主要作用是配置获取Graphql的server地址,地址最好配置在一个环境文件中.就不需要这么动态去取了.省点js也是好的.
2.4 post 函数
const handlePostUserData = async (serverUrl) => {
const parames = {
"query": "query RandomeDatas { randomeDatas { id, username ,dateCreate}}"
}
const url = `${serverUrl}/graphql/`
const header = {
headers:{
'Content-Type': 'application/json'
}
}
const res = await axios.post(url, parames, header);
return res.data
}
2.5 get 函数
const handleGetUserData = async (serverUrl) => {
const query = "query RandomeDatas { randomeDatas {id, username}}"
const header = {
headers:{
'Content-Type': 'application/json'
}
}
const baseURL = `${serverUrl}/graphql`
const fullUrl = `${baseURL}?query=${encodeURIComponent(query)}`
const res = await axios.get(fullUrl, header)
return res.data;
}
在游览器中打开路由. localhost:4000/a,我这里设置了查询id, name, 和创建的时间.看下结果
查询速度是非常非常快的.
托管代码地址GraphQL demo
3.总结下
Graphql是一个非常好的帮助数据快速查询的工具,减少了前端人员和后端人员不必要的请求数据的沟通.前端人员可以按照自己想要的写出请求实体给后端,或者后端人员创建好API后,前端可以按需使用,不需要因为加减字段频繁创建和修改的sql.整体库很轻量自带GUI也为后端人员测试提供了不小的帮助.可以理解为一个小型的ES.但还是那句话学习成本很高.可以现在大环境前后端分离的状态下很难统一思想使用这个,全栈更好一些.使用手感上很好,适合百万级以上数据量使用体验更好.至于是否使用还是看各自项目需求和个人喜好吧.