NodeJs中使用Apollo Server构建GraphQL API服务

简介: GraphQL是一种通过强类型查询语言构建api的新方法。GraphQL于2015年由Facebook发布,目前正迅速获得关注,并被Twitter和Github等其他大型公司所采用,之前写过一篇《浅谈NodeJS搭建GraphQL API服务》只是简单介绍构建API。在本文中,我们将介绍如何使用Apollo Server在Node.js中设置GraphQL服务器。

image.png

GraphQL是一种通过强类型查询语言构建api的新方法。GraphQL于2015年由Facebook发布,目前正迅速获得关注,并被Twitter和Github等其他大型公司所采用,之前写过一篇《浅谈NodeJS搭建GraphQL API服务》只是简单介绍构建API。在本文中,我们将介绍如何使用Apollo Server在Node.js中设置GraphQL服务器。

服务器上GraphQL的高级概述

一旦熟悉了所有的活动部件,GraphQL的上手实际上就非常简单。GraphQL服务是通过一个模式定义的,其工作原理大致如下:

image.png

Types:类型

类型是数据模型的强类型表示,这是一个使用Apollographql-tools定义的帖子类型的示例,在本教程中将使用它来定义架构。

import User from "./user_type";
const Post = `
  type Post {
    id: Int!
    title: String!
    body: String!
    author_id: Int!
    author: User
  }
`;
export default () => [Post, User];


Queries:查询

查询是定义可以针对架构运行哪些查询的方式,这是模式的RootQuery中的一个查询的示例;

const RootQuery = `
  type RootQuery {
    posts: [Post]
    post(id:Int!): Post
    users: [User]
    user(id:Int!): User
  }
`;


Mutations:更改

更改(Mutations)类似于post请求(尽管它们实际上只是查询的同步版本),它们允许将数据发送到服务器以执行插入、更新或者删除。下面是一个为新博客文章定义更改(Mutations)的例子,它接受输入类型PostInput并将新创建的文章作为post类型返回。

const RootMutation = `
  type RootMutation {
    createPost(input: PostInput!): Post
  }
`;


Subscriptions:订阅

订阅允许通过GraphQL订阅服务器发布实时事件,下面定义了一个订阅的示例:

const RootSubscription = `
  type RootSubscription {
    postAdded(title: String): Post
  }
`;


现在,可以通过在createPost突变解析器中运行此事件,将事件发布到订阅的事件。

pubsub.publish(‘postAdded’, { postAdded: post });


Resolvers:解析器

解析器是执行操作以响应查询、变异或订阅的地方,在这里,可以进入数据库层执行CRUD操作并返回适当的响应。如下的示例:

resolvers: {
  RootQuery: {
    posts: () => posts,
    post: async (_, { id }) => 
      await Post.query()
  },
  RootMutations: {
    createPost: async (_, { input }) => 
      await Post.query.insert(input)
  },
  RootSubscriptions: {
    postAdded: {
    subscribe: () => 
      pubsub.asyncIterator('postAdded')
  },
  Post: {
    author: async post => 
      await User.query().where("id", "=", post.author_id)
  }
}


Schema:模式

模式(Schema)是将所有活动部分连接在一起,构建服务的API。

开始进入项目

如果想要查看的代码,请在此处找到一个仓库。

安装依赖

首先创建一个项目,这里命名为:graphql-hello-api

mkdir graphql-hello-api


然后进入目录,执行一下命令:

yarn init


添加必须的依赖:

yarn add apollo-server graphql


创建Hello

创建一个名为src的文件夹,为了更好展示整个过程,不同的示例命名为不同的文件名称,先来创建一个文件:index001.js

首先定义了一个查询类型:

const typeDefs = gql`
    type Query {
        hello: String
    }
`;


接下来定义解析器(或GraphQL教程中的根)来解析给定的查询:

const resolvers = {
    Query: {
        hello: () => {
            return "Hello World!";
        },
    },
};


最后,实例化ApolloServer,然后启动服务。

const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
    console.log(`🚀 GraphQL Server ready at ${url}`);
});


index001.js的所有代码如下:

const { ApolloServer, gql } = require("apollo-server");
const typeDefs = gql`
    type Query {
        hello: String
    }
`;
const resolvers = {
    Query: {
        hello: () => {
            return "Hello World!";
        },
    },
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
    console.log(`🚀 GraphQL Server ready at ${url}`);
});


下面我们来启动GraphQL Server,进入文件夹src,执行如下命令,打开浏览器,输入http://localhost:3005/

node index001.js


将看下如下界面:

image.png

按照上面的图的步骤,录入定义的查询{hello},运行结果如下:

image.png

GraphQL查询的基本类型可以由字符串、整数、浮点数、布尔值和ID及其列表[]组成,下面开始添加一些逻辑代码。

在这里,使用不同的类型如下定义typeDefs。这!表示不可为空的结果。接下来我们创建index002.js,定义3个查询,分别为字符串、浮点数和[]。

const typeDefs = gql`
    type Query {
        today: String
        random: Float!
        fibonacci: [Int]
    }
`;


相应地设置解析器,如下:

const resolvers = {
    Query: {
        today: () => {
            return new Date().toDateString();
        },
        random: () => {
            return Math.random();
        },
        fibonacci: () => {
            return fibonacci(10);
        },
    },
};


现在可以看看完整的代码,即index.js的完整代码:

const { ApolloServer, gql } = require("apollo-server");
const fibonacci = (length) => {
    let nums = [0, 1];
    for (let i = 2; i <= length; i++) {
        nums[i] = nums[i - 1] + nums[i - 2];
    }
    return nums;
};
const typeDefs = gql`
    type Query {
        today: String
        random: Float!
        fibonacci: [Int]
    }
`;
const resolvers = {
    Query: {
        today: () => {
            return new Date().toDateString();
        },
        random: () => {
            return Math.random();
        },
        fibonacci: () => {
            return fibonacci(10);
        },
    },
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
    console.log(`🚀 GraphQL Server ready at ${url}`);
});


运行结果如下:

image.png

传递参数

现在来展示如何使用查询将一些参数传递给服务器,创建index003.js,本示例我们将定义查询获取一个指定长度的斐波那契数组,定义参数length。代码如下:

const typeDefs = gql`
    type Query {
        fibonacci(length:Int!): [Int]
    }
`;


接下来就是解析器,请注意,使用Apollo Server时,它的API于GraphQL API略有不同。参数通过第二个参数传递,格式为:fibonacci: (_, { length }),这里暂时忽略带有_的第一个参数。

const resolvers = {
    Query: {
        fibonacci: (_, { length }) => {
            return fibonacci(length);
        },
    },
};


这里是完整的代码:

const { ApolloServer, gql } = require("apollo-server");
const fibonacci = (length) => {
    let nums = [0, 1];
    for (let i = 2; i <= length; i++) {
        nums[i] = nums[i - 1] + nums[i - 2];
    }
    return nums;
};
const typeDefs = gql`
    type Query {
        fibonacci(length: Int!): [Int]
    }
`;
const resolvers = {
    Query: {
        fibonacci: (_, { length }) => {
            return fibonacci(length);
        },
    },
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
    console.log(`🚀 GraphQL Server ready at ${url}`);
});


在左边窗口输入查询:

{
  fibonacci(length:10)
}


运行结果如下:

image.png

对象类型

有时需要返回一个由基本类型构造的更复杂的对象,可以通过为它声明一个类(JavaScript ES6)类型来实现,新建一个文件index004.js,完整代码如下:

const { ApolloServer, gql } = require("apollo-server");
/**
 * 定义一个基础查询,返回查询RandomDie
 */
const typeDefs = gql`
    type RandomDie {
        numSides: Int!
        rollOnce: Int!
        roll(numRolls: Int!): [Int]
    }
    type Query {
        getDie(numSides: Int): RandomDie
    }
`;
class RandomDie {
    constructor(numSides) {
        this.numSides = numSides;
    }
    rollOnce() {
        return 1 + Math.floor(Math.random() * this.numSides);
    }
    roll({ numRolls }) {
        const output = [];
        for (let i = 0; i < numRolls; i++) {
            output.push(this.rollOnce());
        }
        return output;
    }
}
const resolvers = {
    Query: {
        getDie: (_, { numSides }) => {
            return new RandomDie(numSides || 6);
        },
    },
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
    console.log(`🚀 GraphQL Server ready at ${url}`);
});


在录入查询的时候就当调用getDie作为基础查询,如下:

{
  getDie(numSides:6){
    numSides,
    rollOnce,
    roll(numRolls:10)
  }
}


运行结果如下:

image.png

使用mutation

前面介绍了Mutations:更改,如果要修改服务器端数据,需要使用mutation代替query,创建index005.js

const { ApolloServer, gql } = require("apollo-server");
const fakeDb = {};
const typeDefs = gql`
    type Mutation {
        setTitle(title: String): String
    }
    type Query {
        getTitle: String
    }
`;
const resolvers = {
    Mutation: {
        setTitle: (_, { title }) => {
            fakeDb.title = title;
            return title;
        },
    },
    Query: {
        getTitle: () => {
            return fakeDb.title;
        },
    },
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
    console.log(`🚀 GraphQL Server ready at ${url}`);
});


输入查询:

mutation{
  setTitle(title:"Hello DevPoint!")
}


执行结果如下:image.png


输入类型

有时希望将同类信息设计在一个对象里面进行维护或者规范输入,可以按照接口的方式定义输入类型结构,创建 index006.js,实现一个维护网站基本信息的示例,整体代码如下:

const { ApolloServer, gql } = require("apollo-server");
const { nanoid } = require("nanoid");
const typeDefs = gql`
    input SiteInput {
        title: String
        author: String
        url: String
    }
    type SiteDetail {
        id: ID!
        title: String
        author: String
        url: String
    }
    type Query {
        getSite(id: ID!): SiteDetail
    }
    type Mutation {
        createSite(input: SiteInput): SiteDetail
        updateSite(id: ID!, input: SiteInput): SiteDetail
    }
`;
class SiteDetail {
    constructor(id, { author, title, url }) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.url = url;
    }
}
const fakeDb = {};
const resolvers = {
    Mutation: {
        createSite: (_, { input }) => {
            var id = nanoid();
            fakeDb[id] = input;
            return new SiteDetail(id, input);
        },
        updateSite: (_, { id, input }) => {
            if (!fakeDb[id]) {
                throw new Error("信息不存在 " + id);
            }
            fakeDb[id] = input;
            return new SiteDetail(id, input);
        },
    },
    Query: {
        getSite: (_, { id }) => {
            if (!fakeDb[id]) {
                throw new Error("信息不存在 " + id);
            }
            return new SiteDetail(id, fakeDb[id]);
        },
    },
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
    console.log(`🚀 GraphQL Server ready at ${url}`);
});


执行node index006.js运行,输入一下语句创建一个site信息对象,并查询新创建对象的ID:

mutation{
  createSite(input:{
    title:"DevPoint",
    author:"QuintionTang",
    url:"https://www.devpoint.com"}
  ){
    id
  }
}


运行结果如下:

image.png

接下来根据返回的ID,查询信息:

query{
  getSite(id:"w3pFxgiCyHgZ8vF6ip1D2"){
    id,
    author,
    title,
    author
  }
}


运行结果如下:

image.png

执行更新操作并查询最新数数据:

mutation{
  updateSite(id:"w3pFxgiCyHgZ8vF6ip1D2",input:{
    title:"DevPoint WebSite"
  }){
    id,
    title,
    author
  }
}


运行结果如下:

image.png

验证

之前有朋友问到是否有统一验证的地方。

GraphQL有没有统一的入口可以验证参数的有效性?

答案是有的,可以使用GraphQL的context在HTTP服务器和GraphQL服务器之间实现身份验证。通过自定义context构建功能,实现请求及用户权限的验证。本文只是简单介绍一下,下期专门写一遍GraphQL中的身份及请求合法性验证文章。

const server = new ApolloServer({
    typeDefs,
    resolvers,
    context: ({ req }) => {
        // 在这里进行请求验证
        const author = "QuintionTang";
        return { author };
    },
});


上面所有代码都提交到Github上了,github.com/QuintionTan…

相关文章
|
JSON 安全 Java
API 一键转换 MCP 服务!Higress 助今日投资快速上线 MCP 市场
今日投资的技术负责人介绍了如何通过Higress MCP 市场完善的解决方案,快捷地将丰富的金融数据 API 转化为 MCP 工具,帮助用户通过 MCP 的方式非常轻松地调用专业金融数据,自由快速地构建自己的金融大模型应用。
1427 23
|
缓存 安全 API
RESTful与GraphQL:电商API接口设计的技术细节与适用场景
本文对比了RESTful与GraphQL这两种主流电商API接口设计方案。RESTful通过资源与HTTP方法定义操作,简单直观但可能引发过度或欠获取数据问题;GraphQL允许客户端精确指定所需字段,提高灵活性和传输效率,但面临深度查询攻击等安全挑战。从性能、灵活性、安全性及适用场景多维度分析,RESTful适合资源导向场景,GraphQL则适用于复杂数据需求。实际开发中需根据业务特点选择合适方案,或结合两者优势,以优化用户体验与系统性能。
|
人工智能 算法 API
国产化用于单导联和六导联的心电算法及API服务
随着智能设备普及,心电图功能逐渐应用于智能手表、体脂仪等设备。苏州唯理推出单导联及6导联心电算法API服务,由AI驱动,1分钟内快速评估心律失常、房颤、早搏等问题,已广泛用于医疗设备及三甲医院。其算法还可评估压力、疲劳、情绪状态,筛查效率远超进口设备。唯理率先实现国产医疗级心电芯片,支持快速集成与私有化部署,适用于多种智能硬件。
|
人工智能 API 开发工具
GitHub官方开源MCP服务!GitHub MCP Server:无缝集成GitHub API,实现Git流程完全自动化
GitHub MCP Server是基于Model Context Protocol的服务器工具,提供与GitHub API的无缝集成,支持自动化处理问题、Pull Request和仓库管理等功能。
3675 2
GitHub官方开源MCP服务!GitHub MCP Server:无缝集成GitHub API,实现Git流程完全自动化
|
人工智能 算法 安全
OpenRouter 推出百万 token 上下文 AI 模型!Quasar Alpha:提供完全免费的 API 服务,同时支持联网搜索和多模态交互
Quasar Alpha 是 OpenRouter 推出的预发布 AI 模型,具备百万级 token 上下文处理能力,在代码生成、指令遵循和低延迟响应方面表现卓越,同时支持联网搜索和多模态交互。
1125 1
OpenRouter 推出百万 token 上下文 AI 模型!Quasar Alpha:提供完全免费的 API 服务,同时支持联网搜索和多模态交互
|
缓存 边缘计算 前端开发
从业务需求到技术栈:电商API选型RESTful还是GraphQL?这5个维度帮你决策
在数字经济时代,电商平台的竞争已延伸至用户体验与系统效能。作为连接前后端及各类服务的核心,API接口的架构设计至关重要。本文对比RESTful与GraphQL两大主流方案,从电商场景出发,分析两者的技术特性、适用场景与选型逻辑,帮助开发者根据业务需求做出最优选择。
|
人工智能 自然语言处理 API
硅基流动入驻阿里云云市场,核心API服务将全面接入阿里云百炼平台💐
2025年6月18日,AI Infra企业硅基流动与阿里云达成战略合作,加入“繁花计划”并入驻云市场。其大模型推理平台SiliconCloud核心API将接入阿里云百炼平台,依托灵骏智能计算集群为客户提供高效服务。作为国内领先的MaaS平台,SiliconCloud已集成百余款开源大模型,服务600万用户及众多企业。双方将在算力协同、行业解决方案等领域深化合作,推动AI生态发展。
1579 0
|
前端开发 JavaScript NoSQL
使用 Node.js、Express 和 React 构建强大的 API
本文详细介绍如何使用 Node.js、Express 和 React 构建强大且动态的 API。从开发环境搭建到集成 React 前端,再到利用 APIPost 高效测试 API,适合各水平开发者。内容涵盖 Node.js 运行时、Express 框架与 React 库的基础知识及协同工作方式,还涉及数据库连接和前后端数据交互。通过实际代码示例,助你快速上手并优化应用性能。
|
Kubernetes API 网络安全
当node节点kubectl 命令无法连接到 Kubernetes API 服务器
当Node节点上的 `kubectl`无法连接到Kubernetes API服务器时,可以通过以上步骤逐步排查和解决问题。首先确保网络连接正常,验证 `kubeconfig`文件配置正确,检查API服务器和Node节点的状态,最后排除防火墙或网络策略的干扰,并通过重启服务恢复正常连接。通过这些措施,可以有效解决与Kubernetes API服务器通信的常见问题,从而保障集群的正常运行。
1297 17
|
人工智能 JavaScript 测试技术
构建智能 API 开发环境:在 Cursor 中连接 Apifox MCP Server
本文介绍了如何将Apifox MCP Server与Cursor结合,通过AI直接获取和理解API文档,大幅提升开发效率。首先需配置Apifox的Access Token和项目ID,并在Cursor中设置MCP连接。实际应用场景包括快速生成模型代码、同步更新接口文档与代码、生成CRUD操作、搜索API文档及自动生成测试用例。此外,还提供了管理多项目、安全性实践和优化AI响应质量的技巧。这种组合可显著减少从API规范到代码实现的时间,降低错误率并加速迭代过程,为开发者带来更高效的体验。