NodeJs + GraphQl 基于Apollo Server (2)

 基于上一篇的项目继续深入学习和介绍项目中如何使用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.但还是那句话学习成本很高.可以现在大环境前后端分离的状态下很难统一思想使用这个,全栈更好一些.使用手感上很好,适合百万级以上数据量使用体验更好.至于是否使用还是看各自项目需求和个人喜好吧.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值