Neo4j 与 JavaScript (Node.js) 入门
在这篇博文中,我们将通过构建一个 JavaScript (Node.js) 应用程序来了解 Neo4j 的优势,并演示 JavaScript 生态系统中的开发者如何利用图数据库来解决复杂的数据挑战。
在开始编码之前,让我们先来探讨一下为什么 JavaScript 开发者会想要使用图数据库。

为什么 JavaScript 开发者会选择图数据库?
图数据库的兴起是为了满足对高度关联的动态数据进行建模和查询的需求——而这些需求在传统关系表甚至许多 NoSQL 存储中往往显得笨拙或低效。Neo4j 的属性图模型(节点和关系拥有各自丰富的属性集)反映了我们在白板上勾勒真实世界数据的方式,使得即使是最复杂的领域也能轻松实现概念化和落地。
在 JavaScript 和 Node.js 应用中,使用关系型或文档型存储可能需要无休止地进行 JSON 转换、映射连接或深度嵌套数据来模拟关系。相比之下,图数据库让 JavaScript 开发者能够原生表达真实世界的网络、层级和依赖关系,并使用 Cypher(专为图设计的查询语言)高效地遍历它们。
想要在社交应用中映射用户关联吗?需要追踪 NPM 包之间的依赖关系或可视化 API 调用吗?想要构建推荐系统、探索业务层级或实时发现欺诈团伙吗?这正是图数据库的用武之地,也是为什么图数据库在构建实时分析、推荐系统、知识图谱等领域的 JavaScript 工程师中越来越受欢迎的原因。
此外,JavaScript 数据建模的灵活性和动态性(使用普通对象、类、数组和引用)与图数据的结构高度契合。你可以将节点和关系建模为对象、数组和引用,并在内存结构与图查询之间进行低阻抗转换。
创建项目
让我们通过设置一个简单的 Node.js 项目来连接 Neo4j 并查询一些关于书籍和作者的数据。
先决条件:你的系统上应安装 Node.js(推荐 v16+)和 npm。我们还需要官方的 Neo4j JavaScript 驱动程序和一个可供连接的 Neo4j 数据库。在本教程中,我们将使用 Neo4j 的免费 Goodreads 演示数据库,其中包含书籍、作者和评论数据(源自 UCSD Book Graph 项目)。你可以通过 Neo4j Browser 界面访问它,或者自己启动一个实例。
使用演示数据库:点击此处打开 Neo4j Browser: https://demo.neo4jlabs.com:7473/browser/。使用以下凭据进行连接:
- URI: neo4j+s://demo.neo4jlabs.com
- 用户名: goodreads
- 密码: goodreads
- 数据库: goodreads
你可以通过在浏览器中运行模式可视化查询来验证数据集是否可用,例如:
CALL db.schema.visualization;Code language: CSS (css)
这应该显示如下所示的节点,如“Author”、“Book”、“Review”,以及作者与书籍之间“AUTHORED”的关系。

或者使用 Neo4j Aura:作为替代方案,你可以在云端创建自己的免费 Neo4j Aura 实例。访问 https://console.neo4j.io/ 并创建一个新数据库(AuraDB Free)。你将获得唯一的 Bolt URI 和凭据。请记住此文件在计算机上的保存位置,因为稍后需要访问这些凭据。如果选择此路径,只需在下面的步骤中替换为你自己的 Aura 连接 URI、用户名和密码即可。
项目结构
为项目创建一个新目录(我们称之为 neo4j-js-tutorial)。在内部,我们将拥有一个非常简单的结构(现在不要手动创建它们,我们将随教程进度进行创建)。
neo4j-js-tutorial/
├── index.js # Our Node.js script
├── .env # Environment variables for Neo4j connection
└── package.json # Project configuration and dependenciesCode language: PHP (php)
初始化项目
导航到项目目录并使用默认设置初始化 Node.js 项目:
npm init -yCode language: Shell Session (shell)
这将创建一个基本的 package.json。接下来,安装 Neo4j 驱动程序和一个用于处理环境变量的小工具:
npm install neo4j-driver dotenvCode language: Shell Session (shell)
这会将两个依赖项添加到你的 package.json 文件中:neo4j-driver(官方 Neo4j JS 驱动程序,最新的 5.x 版本)和 dotenv(用于从我们的 .env 文件加载环境变量)。安装完成后,你的 package.json 中应该会列出这些依赖项,例如:
{
"dependencies": {
"neo4j-driver": "^5.28.1",
"dotenv": "^17.2.0"
}
}Code language: JSON / JSON with Comments (json)
配置 Neo4j 凭据
在项目根目录下创建一个名为 .env 的文件,并将下面的 Neo4j 连接详情添加到该文件中。最佳实践是永远不要在项目代码中(即 .env 文件之外)硬编码(或写入)凭据,以免代码上传到互联网后被他人可见和利用。因此,dotenv 库将从这个受保护的 .env 文件中读取并加载它们。
NEO4J_URI = "neo4j+s://demo.neo4jlabs.com"
NEO4J_USERNAME = "goodreads"
NEO4J_PASSWORD = "goodreads"
NEO4J_DATABASE = "goodreads"Code language: JavaScript (javascript)
如果你使用自己的 Neo4j Aura 数据库,请将 NEO4J_URI 设置为 Bolt URL(以 neo4j+s://... 开头),并将用户名/密码设置为 Neo4j 提供的值。URI 中的 neo4j+s 方案表示带有加密(SSL)的 Neo4j Bolt 连接,这正是 Aura 或演示数据库所需要的。
使用 Neo4j 进行连接和查询
是时候写代码了!在编辑器中创建 index.js 并添加以下内容:
Code language: JavaScript (javascript)
我们引入了 neo4j-driver 包和 dotenv 包,用于读取 .env 文件中的环境变量。
// Import dependencies
const neo4j = require('neo4j-driver');
require('dotenv').config();Code language: JavaScript (javascript)
然后,我们使用 NEO4J_URI 和身份验证令牌(本例中使用 NEO4J_USERNAME 和 NEO4J_PASSWORD 进行基本身份验证)调用 neo4j.driver(...)。这创建了一个驱动程序实例,负责管理到数据库的连接池。我们使用 dotenv.config() 从 .env 文件加载环境变量,以便 process.env.NEO4J_URI 等变量在此文件中可用。
// Initialize the Neo4j driver
const driver = neo4j.driver(
process.env.NEO4J_URI,
neo4j.auth.basic(process.env.NEO4J_USERNAME, process.env.NEO4J_PASSWORD)
);Code language: JavaScript (javascript)
接着,我们使用立即执行的异步函数(IIFE)来包含异步代码(这样我们就可以在顶层使用 await)。在内部,调用 driver.getServerInfo() 以确保连接详情正确。该方法返回一个 Promise,如果驱动程序能够连接,它会解析成功(如果无法连接则抛出错误)。如果成功,我们会打印一条确认消息。
(async () => {
try {
// Verify the connection details
const serverInfo = await driver.getServerInfo();
console.log(`Connected to Neo4j server version: ${serverInfo.version}`);Code language: JavaScript (javascript)
接下来,我们定义一个 Cypher 查询字符串。此查询查找具有指定名称的 Author 节点,找到该作者 AUTHORED 的 Book 节点,并返回每本书的标题和平均评分。我们在查询中包含了一个 $name 参数(Cypher 中的参数占位符使用 $ 语法),并按评分降序排列结果,限制返回 5 本书。(你将在下文找到此查询的更详细解释。现在,可以直接复制粘贴。)
// Define a Cypher query to find books by a given author
const cypherQuery = `
MATCH (a:Author)-[:AUTHORED]->(b:Book)
WHERE a.name = $name
RETURN b.title AS title, b.average_rating AS rating
ORDER BY b.average_rating DESC
LIMIT 5;
`;Code language: JavaScript (javascript)
我们使用 driver.executeQuery(cypherQuery, { name: "Chimamanda Ngozi Adichie" }) 执行查询。第二个参数是参数对象——这里我们为 $name 传入了实际的作者姓名。Neo4j 驱动程序将负责安全地将查询和参数发送到数据库。这个新的 executeQuery API(在 Neo4j 驱动程序 5.8 中引入)会在底层自动运行一个读事务,并直接返回包含记录的结果。
// Execute the query with a parameter
const result = await driver.executeQuery(cypherQuery, { name: "Chimamanda Ngozi Adichie" });Code language: JavaScript (javascript)
返回的结果包含一个 records 数组(每条记录对应 Cypher 结果中的一行)。我们遍历 result.records,对于每一条记录,使用 record.get(<field>) 来访问返回的字段。我们在 Cypher RETURN 子句中将字段命名为 title 和 rating,因此使用 record.get('title') 和 record.get('rating')。我们打印出每本书的标题和评分。
最后,无论查询是成功还是抛出错误,我们都在 finally 块中使用 await driver.close() 关闭驱动程序。这对于释放资源(套接字、内存)非常重要。在长时间运行的应用程序中,你会保持驱动程序打开并重用它,但在像这样的小脚本中,我们在执行结束时将其关闭。
// Loop through the returned records and output the data
for (const record of result.records) {
console.log(`Title: ${record.get('title')}, Rating: ${record.get('rating')}`);
}
} catch (err) {
console.error('Error querying Neo4j:', err);
} finally {
// Close the driver connection
await driver.close();
}
})();Code language: JavaScript (javascript)
index.js 文件的完整代码块如下所示:
// Import dependencies
const neo4j = require('neo4j-driver');
require('dotenv').config();
// Initialize the Neo4j driver
const driver = neo4j.driver(
process.env.NEO4J_URI,
neo4j.auth.basic(process.env.NEO4J_USERNAME, process.env.NEO4J_PASSWORD)
);
(async () => {
try {
// Verify the connection details
const serverInfo = await driver.getServerInfo();
console.log(`Connected to Neo4j server version: ${serverInfo.version}`);
// Define a Cypher query to find books by a given author
const cypherQuery = `
MATCH (a:Author)-[:AUTHORED]->(b:Book)
WHERE a.name = $name
RETURN b.title AS title, b.average_rating AS rating
ORDER BY b.average_rating DESC
LIMIT 5;
`;
// Execute the query with a parameter
const result = await driver.executeQuery(cypherQuery, { name: "Chimamanda Ngozi Adichie" });
// Loop through the returned records and output the data
for (const record of result.records) {
console.log(`Title: ${record.get('title')}, Rating: ${record.get('rating')}`);
}
} catch (err) {
console.error('Error querying Neo4j:', err);
} finally {
// Close the driver connection
await driver.close();
}
})();Code language: JavaScript (javascript)
运行应用程序
确保你的 Neo4j 实例(演示或 Aura)已启动并可访问,且 .env 文件中具有正确的凭据。然后运行脚本:
node index.jsCode language: Shell Session (shell)
如果一切设置正确,你应该会看到类似以下的输出:
Connected to Neo4j
Title: We Should All Be Feminists, Rating: 4.47
Title: Dovremmo essere tutti femministi, Rating: 4.47
Title: Paarse hibiscus, Rating: 4.12Code language: CSS (css)
(注意:你的具体结果可能会有所不同,但这里我们看到了数据库中由 Chimamanda Ngozi Adichie 所著的评分最高的书籍,以及它们在 Goodreads 上的平均评分。)
详细查询解释
让我们剖析一下代码中使用的 Cypher 查询,理解 Cypher 查询将有助于你掌握图数据库的真正力量。
- MATCH (a:Author)-[:AUTHORED]->(b:Book) – 此模式匹配一个通过 AUTHORED 关系连接到 Book 节点 b 的 Author 节点。换句话说,它查找所有关系类型为 AUTHORED 的 (Author)–(Book) 对。在 Cypher 中,圆括号表示节点,-[:LABEL]-> 表示有向关系。因此,我们专门查找编写过某本书的作者。如果一位作者没有编写过任何书籍,他们将不会出现在此匹配中。
- WHERE a.name = $name – 这会过滤 MATCH 的结果。我们只保留那些 name 属性等于参数 $name 的作者节点。在我们的代码中,我们提供了 $name = “Chimamanda Ngozi Adichie”。在这里使用参数(而不是直接嵌入字符串)是安全和性能方面的最佳实践——它防止了 Cypher 注入,并允许 Neo4j 缓存查询计划。我们本可以在查询中硬编码名称(例如 WHERE a.name = “Chimamanda Ngozi Adichie”),但使用参数可以轻松地为任何作者姓名重用此查询。
- RETURN b.title AS title, b.average_rating AS rating – 这告诉 Neo4j 为每个匹配返回什么数据。我们返回两个字段:书籍的标题和书籍的平均评分,并将它们分别别名为 title 和 rating。如果我们只返回 b(整个书籍节点),Neo4j 会返回带有所有属性的完整节点(以及一个引用 ID)。通过仅指定我们关心的属性,我们使输出更易于处理(并避免了像存储在节点上的长描述文本或向量嵌入等巨大的数据转储)。
- ORDER BY b.average_rating DESC – 这会按书籍的平均评分按降序(最高分优先)对返回的记录进行排序。Cypher 的 ORDER BY 的工作方式与 SQL 类似。在这里,我们想要该作者评分最高的书籍。如果我们省略它,书籍将以任意顺序返回(取决于内部存储或查询执行顺序)。
- LIMIT 5 – 顾名思义,这会将结果限制为 5 条记录。演示数据集对于某些作者可能有多于或少于五本书,但在此示例中我们只想显示前 5 本。限制结果还有助于在仅需要部分结果时减少数据传输。
总之,Cypher 查询的意思是:“找到由 Chimamanda Ngozi Adichie 撰写的书籍,然后返回它们的标题和评分,按评分排序,并给我前五名。”Cypher 通过作者名称的索引查找(如果 Author(name) 上存在索引)高效地执行此操作,随后遍历到该作者的书籍。由于 Neo4j 原生存储关系,它可以在常数时间内获取每个作者关联的节点(书籍),这就是为什么图数据库擅长遍历查询的主要原因。
在 JavaScript 端,驱动程序的 executeQuery 方法处理了启动会话、开启事务、运行查询和提交事务的所有样板代码。我们得到了一个记录列表并进行了迭代。在我们的案例中,每条记录有两个字段(title 和 rating),可以通过 get 方法或索引访问。我们用简单的模板字符串打印了它们。驱动程序还提供了 result.summary 等元数据,我们可以用它来检查返回了多少条记录、查询花费了多少时间等,但为了简单起见,这里我们省略了。
恭喜——你刚刚完成了第一个从 Node.js 发出的 Neo4j 查询!
在数据库中验证
如果你想验证应用程序的输出是否与数据库中看到的一致,你可以使用相同的 Cypher 查询,只需将 $name 属性替换为带引号的实际名称参数,并在 demo.neo4jlabs.com/7343 浏览器中运行即可——“Chimamanda Ngozi Adichie”。
MATCH (a:Author)-[:AUTHORED]->(b:Book)
WHERE a.name = "Chimamanda Ngozi Adichie"
RETURN b.title AS title, b.average_rating AS rating
ORDER BY b.average_rating DESC
LIMIT 5;
`;
结果

通过对第 3 行的 Cypher RETURN 语句进行一些小的编辑,我们还可以将可用数据可视化为图表,并点击其中一个“book”节点来阅读一些额外的信息。

后续步骤
从这里开始,你已经有了进一步构建的基础。以下是一些后续步骤的建议:
- 构建 Web API:你可以使用 Express 等框架将此 Neo4j 查询逻辑集成到 Web 服务器中。例如,创建一个端点(例如
/top-books?author=Chimamanda%20Ngozie%20Adichie),该端点使用查询参数并以 JSON 格式返回查询结果。这样,你可以构建一个前端(例如 React 或 Vue 应用),以用户友好的方式显示来自 Neo4j 的数据。 - 使用更高级的 ORM/OGM:虽然直接使用驱动程序很简单,但对于较大的应用程序,你可能需要考虑对象图映射器(Object Graph Mapper),例如 Neode,或者如果你喜欢 GraphQL,还可以使用 Node.js 的 Neo4j GraphQL 库。这些库可以分别抽象部分 Cypher 查询,并将结果映射到 JavaScript 类或 GraphQL 类型。
- 扩展数据模型:我们重点关注了作者和书籍。该数据集还有 Review 节点,可能还有 User 节点(编写评论的人)。你可以编写查询来查找例如顶级评论者或进行推荐(例如,如果用户 X 喜欢与用户 Y 相同的书,用户 Y 还对什么评价很高?)。尝试编写新的 Cypher 查询来探索该图。
- 错误处理和配置:在生产应用中,你需要添加更健壮的错误处理。例如,如果 getServerInfo() 失败,你可能希望重试或优雅地退出。你还需要通过环境变量(我们使用了 dotenv)管理配置(如 URI 和凭据)。如果使用多个 Neo4j 环境(开发/生产),你将相应地进行调整。Neo4j 驱动程序还允许调整连接池大小和路由(针对集群),文档对此有详细介绍。
现在你已经知道如何从 Node.js 连接和运行基本查询,你可以开始解决那些理解数据中关系背后的上下文信息至关重要的问题——同时继续使用 JavaScript 语法和数据结构。
资源
- 继续构建 – Neo4j 电影应用 (JS):查看 Neo4j 示例项目 movies-javascript-bolt (https://github.com/neo4j-examples/movies-javascript-bolt),它展示了一个使用 Neo4j JavaScript 驱动程序和电影数据集的简单 Web 应用。这是了解如何在 Node.js 中构建更大规模 Neo4j 应用结构的好方法。
- 免费在线课程:Neo4j GraphAcademy 上的 Building Neo4j Applications with Node.js 课程通过逐步教程指导你使用驱动程序(类似于我们所做的,但在 Web 应用的背景下)。你可以在此处找到它: https://graphacademy.neo4j.com/courses/app-nodejs/。
- Neo4j JavaScript 驱动程序文档:官方驱动程序手册是深入了解高级功能的极佳参考。请访问 /docs/javascript-manual/current/ 获取有关显式事务、异步结果流、驱动程序配置等主题的详细信息。
- Neo4j AuraDB (免费层级):没有运行中的 Neo4j 实例?在 https://console.neo4j.io 获取云端的免费 AuraDB 实例。这是一个托管的 Neo4j 实例,因此你无需在本地安装任何东西——这对开发和原型设计非常有帮助。
欢迎来到图数据库的世界!


