首页
/ 使用Benzene构建GraphQL服务器入门指南

使用Benzene构建GraphQL服务器入门指南

2025-06-06 04:58:55作者:丁柯新Fawn

前言

在现代Web开发中,GraphQL已经成为构建API的热门选择。Benzene是一个轻量级的GraphQL服务器实现,它提供了构建GraphQL服务器所需的核心功能。本文将带你使用Benzene构建一个简单的书籍投票应用,涵盖查询、变更和订阅等GraphQL核心概念。

项目概述

我们将构建一个具有以下功能的书籍投票应用:

  • 获取书籍和作者列表
  • 为书籍投票
  • 实时同步投票变化

环境准备

首先创建一个新项目并安装必要的依赖:

mkdir book-votes
cd book-votes
npm init -y
npm i ws graphql @benzene/http @benzene/ws @graphql-tools/schema

确保在package.json中设置"type": "module"以使用ES模块语法。

定义GraphQL模式

创建一个schema.js文件,定义我们的GraphQL模式:

import { makeExecutableSchema } from "@graphql-tools/schema";
import { on, EventEmitter } from "events";

// 定义作者和书籍的初始数据
const authors = [
  { id: 1, name: "Tom Coleman" },
  { id: 2, name: "Sashko Stubailo" },
  { id: 3, name: "Mikhail Novikov" },
];

const books = [
  { id: 1, authorId: 1, title: "Introduction to GraphQL", votes: 2 },
  { id: 2, authorId: 2, title: "Welcome to Meteor", votes: 3 },
  { id: 3, authorId: 2, title: "Advanced GraphQL", votes: 1 },
  { id: 4, authorId: 3, title: "Launchpad is Cool", votes: 7 },
];

// 定义GraphQL类型
const typeDefs = `
  type Author {
    id: Int!
    name: String
    books: [Book]
  }

  type Book {
    id: Int!
    title: String
    author: Author
    votes: Int
  }

  type Query {
    books: [Book]
  }

  type Mutation {
    bookUpvote (
      bookId: Int!
    ): Book
  }

  type Subscription {
    bookSubscribe: Book
  }
`;

// 使用事件发射器实现订阅功能
const ee = new EventEmitter();

// 定义解析器
const resolvers = {
  Query: {
    books: () => books,
  },
  Mutation: {
    bookUpvote: (_, { bookId }) => {
      const book = books.find((book) => book.id === bookId);
      if (!book) {
        throw new Error(`找不到ID为${bookId}的书籍`);
      }
      book.votes += 1;
      ee.emit("BOOK_SUBSCRIBE", { bookSubscribe: book });
      return book;
    },
  },
  Subscription: {
    bookSubscribe: {
      subscribe: async function* bookSubscribe() {
        for await (const event of on(ee, "BOOK_SUBSCRIBE")) {
          yield event[0];
        }
      },
    },
  },
  Author: {
    books: (author) => books.filter((book) => book.authorId === author.id),
  },
  Book: {
    author: (book) => authors.find((author) => author.id === book.authorId),
  },
};

// 创建可执行模式
const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
});

export default schema;

这个模式定义了三种操作:

  1. 查询:获取所有书籍及其作者信息
  2. 变更:为指定书籍投票
  3. 订阅:实时监听书籍投票变化

创建服务器

接下来创建server.js文件,设置HTTP和WebSocket服务器:

import { createServer } from "http";
import WebSocket from "ws";
import { Benzene, parseGraphQLBody, makeHandler } from "@benzene/http";
import { makeHandler as makeHandlerWs } from "@benzene/ws";
import schema from "./schema.js";

// 读取请求体
function readBody(request) {
  return new Promise((resolve) => {
    let body = "";
    request.on("data", (chunk) => (body += chunk));
    request.on("end", () => resolve(body));
  });
}

// 创建Benzene实例
const GQL = new Benzene({ schema });

// 创建HTTP和WebSocket处理器
const graphqlHTTP = makeHandler(GQL);
const graphqlWS = makeHandlerWs(GQL);

// 创建HTTP服务器
const server = createServer(async (req, res) => {
  const rawBody = await readBody(req);
  const result = await graphqlHTTP({
    method: req.method,
    headers: req.headers,
    body: parseGraphQLBody(rawBody, req.headers["content-type"]),
  });
  res.writeHead(result.status, result.headers);
  res.end(JSON.stringify(result.payload));
});

// 创建WebSocket服务器
const wss = new WebSocket.Server({ server });

wss.on("connection", (ws) => {
  graphqlWS(ws);
});

// 启动服务器
server.listen(3000, () => {
  console.log(`🚀 服务器已启动: http://localhost:3000`);
});

关键点解析

  1. 请求体处理:我们自定义了readBody函数来读取请求体,并使用parseGraphQLBody处理不同内容类型的GraphQL请求。

  2. Benzene实例:创建Benzene实例时传入我们定义的模式,然后使用它创建HTTP和WebSocket处理器。

  3. 通用性设计:Benzene的设计允许它与各种框架和运行时环境配合使用,只需提供符合规范的请求对象即可。

性能优化建议

对于追求更高性能的场景,可以考虑使用graphql-jit进行查询编译:

import { makeCompileQuery } from "@benzene/jit";

const GQL = new Benzene({
  compileQuery: makeCompileQuery(),
});

这种方式可以获得2倍以上的性能提升。

测试应用

启动服务器:

node ./server.js

测试查询

使用浏览器开发者工具测试查询功能:

await fetch("/graphql", {
  method: "POST",
  headers: { "content-type": "application/graphql+json" },
  body: JSON.stringify({
    query: "query { books { id, title, author { name }, votes } }",
  }),
}).then((res) => res.json());

测试订阅

建立WebSocket连接并订阅书籍变化:

const websocket = new WebSocket(
  "ws://localhost:3000/graphql",
  "graphql-transport-ws"
);
websocket.send(JSON.stringify({ type: "connection_init" }));
websocket.send(
  JSON.stringify({
    type: "subscribe",
    id: "1",
    payload: { query: "subscription { bookSubscribe { title, votes } }" },
  })
);

测试变更

执行投票变更操作:

await fetch("/graphql", {
  method: "POST",
  headers: { "content-type": "application/graphql+json" },
  body: JSON.stringify({ query: "mutation { bookUpvote(bookId: 1) { id } }" }),
}).then((res) => res.json());

此时订阅者会收到更新通知,显示书籍的最新投票数。

总结

通过本教程,我们使用Benzene构建了一个完整的GraphQL服务器,实现了查询、变更和订阅功能。Benzene的轻量级设计和灵活性使其成为构建GraphQL服务的优秀选择,特别是当你需要自定义程度较高的解决方案时。

这个基础示例可以进一步扩展,例如添加认证、数据验证或集成数据库等。Benzene的模块化设计使得这些扩展变得简单而直观。

登录后查看全文
热门项目推荐

热门内容推荐

项目优选

收起
ohos_react_nativeohos_react_native
React Native鸿蒙化仓库
C++
176
261
RuoYi-Vue3RuoYi-Vue3
🎉 (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统
Vue
860
511
ShopXO开源商城ShopXO开源商城
🔥🔥🔥ShopXO企业级免费开源商城系统,可视化DIY拖拽装修、包含PC、H5、多端小程序(微信+支付宝+百度+头条&抖音+QQ+快手)、APP、多仓库、多商户、多门店、IM客服、进销存,遵循MIT开源协议发布、基于ThinkPHP8框架研发
JavaScript
93
15
openGauss-serveropenGauss-server
openGauss kernel ~ openGauss is an open source relational database management system
C++
129
182
openHiTLSopenHiTLS
旨在打造算法先进、性能卓越、高效敏捷、安全可靠的密码套件,通过轻量级、可剪裁的软件技术架构满足各行业不同场景的多样化要求,让密码技术应用更简单,同时探索后量子等先进算法创新实践,构建密码前沿技术底座!
C
259
300
kernelkernel
deepin linux kernel
C
22
5
cherry-studiocherry-studio
🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端
TypeScript
595
57
CangjieCommunityCangjieCommunity
为仓颉编程语言开发者打造活跃、开放、高质量的社区环境
Markdown
1.07 K
0
HarmonyOS-ExamplesHarmonyOS-Examples
本仓将收集和展示仓颉鸿蒙应用示例代码,欢迎大家投稿,在仓颉鸿蒙社区展现你的妙趣设计!
Cangjie
398
371
Cangjie-ExamplesCangjie-Examples
本仓将收集和展示高质量的仓颉示例代码,欢迎大家投稿,让全世界看到您的妙趣设计,也让更多人通过您的编码理解和喜爱仓颉语言。
Cangjie
332
1.08 K