首页
/ Next.js 集成 Neo4j 实战:用 API Routes 构建电影图谱应用

Next.js 集成 Neo4j 实战:用 API Routes 构建电影图谱应用

2026-09-07 18:39:39作者:申梦珏Efrain

本文以 examples/with-neo4j 官方示例为主线,系统讲解如何在 Next.js 应用中通过 API Routes 接入 Neo4j 图数据库:从创建数据库、灌入电影示例数据、配置环境变量,到 Cypher 查询在服务端 API 中的落地方式,以及前端基于 SWR 的渲染链路。读完你可以完整复现一个"人物—电影—关系"的图谱 Web 应用,并理解 neo4j-javascript-driver 在服务端会话与事务的正确用法。

with-neo4j 是 Next.js 官方仓库中演示"Next.js + 图数据库"的经典示例:它使用 Neo4j 官方的 Movies 示例数据集(Movie 节点、Person 节点,以及 ACTED_INDIRECTEDPRODUCEDREVIEWED 等关系类型),通过 3 个 API 路由暴露查询能力,前端页面则负责把关系型数据渲染成可供点击跳转的影片列表、演员详情页与影片详情页。

示例整体结构

先看这个示例在仓库中的文件布局(目录:examples/with-neo4j):

examples/with-neo4j/
├── components/
│   ├── footer.js          # 页脚
│   └── header.js          # 页头,支持传入标题
├── lib/
│   └── fetcher.js         # SWR 的全局 fetch 封装,返回 res.json()
├── pages/
│   ├── _app.js            # 注入全局样式
│   ├── index.js           # 首页:影片列表
│   ├── actor/[name].js    # 演员详情页(动态路由)
│   ├── movie/[title].js   # 影片详情页(动态路由)
│   └── api/
│       ├── movies/index.js      # GET /api/movies      → 全部影片
│       ├── movies/[title].js    # GET /api/movies/:title → 单部影片
│       └── actors/[name].js     # GET /api/actors/:name  → 单个演员
├── util/
│   └── neo4j.js           # 创建并缓存 Neo4j 驱动(单例)
├── styles/
│   └── global.js
├── .env.local.example     # 环境变量模板
├── movie-sample.md        # 可选的 Cypher 电影样本数据
└── package.json

从结构上可以清晰看出本示例的核心架构模式:

  • 服务端查询集中在 pages/api/*(API Routes)。Node.js 运行时天然适合承载 Neo4j 官方驱动,所有 Cypher 查询都运行在服务端,避免把数据库凭据暴露到浏览器。
  • 浏览器渲染使用 SWRswr@^2.0.0)请求这些 API,并配合动态路由展示影片/演员的详情与互链关系。
  • 依赖清单(见 package.json)非常精简:nextreactreact-domneo4j-driver@^4.3.1swr@^2.0.0,脚本为标准的 dev / build / start

快速启动示例

推荐用 create-next-app 直接以该示例为模板初始化项目(示例 CLI 位于 packages/create-next-app),三种包管理器任选其一:

npx create-next-app --example with-neo4j with-neo4j-app
yarn create next-app --example with-neo4j with-neo4j-app
pnpm create next-app --example with-neo4j with-neo4j-app

命令执行后会在当前目录生成 with-neo4j-app 子项目,随后进入目录安装依赖并启动开发服务器:

cd with-neo4j-app
npm install   # 或 yarn / pnpm install
npm run dev   # 或 yarn dev / pnpm dev

启动后访问 http://localhost:3000 即可看到影片列表首页。注意:在配置好数据库连接之前,页面与 API 会因无法连接 Neo4j 而返回加载失败或空数据,所以接下来需要完成数据库准备。

前提条件:创建 Neo4j 数据库并写入 Movies 数据

Step 1:创建 Neo4j 数据库

本示例需要一台可达的 Neo4j 实例。官方示例中推荐了免费方案:Neo4j Desktop(本地桌面版,自带空数据库实例)以及 Neo4j Sandbox / AuraDB(在线托管)。无论是本地实例还是云端实例,都需要记下三个连接要素:

  • 连接 URI(例如 bolt://localhost:7687,或云端实例提供的 neo4j+s://xxxx.databases.neo4j.io
  • 用户名(默认通常是 neo4j
  • 密码

Step 2:灌入电影图谱数据

示例约定数据库中存在 Movies 图模型。Neo4j 官方在这些工具中内置了一个标准 Movies 数据集,可以在 Neo4j Browser 中通过内置教程引导加载:

:play movie-graph

如果你想用一段独立的 Cypher 脚本一次性建好全量数据,示例还附带了一份电影样本查询 movie-sample.md。该文件定义了两类节点与五类关系

  • 节点:Movie(属性含 titlereleasedtagline)、Person(属性含 nameborn
  • 关系:ACTED_IN(角色/演员参演)、DIRECTED(导演)、PRODUCED(制片)、WROTE(编剧)、FOLLOWS(关注)、REVIEWED(评论,含 summaryrating

其 Cypher 写法以《黑客帝国》三部曲为开篇,例如:

CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})
CREATE (Keanu:Person {name:'Keanu Reeves', born:1964})
CREATE (Carrie:Person {name:'Carrie-Anne Moss', born:1967})
CREATE
(Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrix),
(Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrix),
(LillyW)-[:DIRECTED]->(TheMatrix),
(JoelS)-[:PRODUCED]->(TheMatrix)

Step 3:配置环境变量

复制示例目录下的环境变量模板为 .env.local(该文件会被 Git 忽略,不会进入版本库):

cp .env.local.example .env.local

.env.local.example查看原文件)中一共声明了三个变量:

# Environment variables required to connect the app with your Neo4j database
NEO4J_URI=
NEO4J_USER=
NEO4J_PASSWORD=

将它们分别填上你数据库的实际 URI、用户名与密码即可。这三个变量正是 util/neo4j.js 中读取的连接参数来源:

const defaultOptions = {
  uri: process.env.NEO4J_URI,
  username: process.env.NEO4J_USER,
  password: process.env.NEO4J_PASSWORD,
};

因此在本地,三个变量的示例取值形如:

NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=your_password

服务端连接管理:驱动单例与整数处理

示例把连接逻辑收敛在 util/neo4j.js 这一个文件中,核心是惰性单例 + 会话事务

import neo4j from "neo4j-driver";

let driver;

const defaultOptions = {
  uri: process.env.NEO4J_URI,
  username: process.env.NEO4J_USER,
  password: process.env.NEO4J_PASSWORD,
};

export default function getDriver() {
  const { uri, username, password } = defaultOptions;
  if (!driver) {
    driver = neo4j.driver(uri, neo4j.auth.basic(username, password), {
      disableLosslessIntegers: true,
    });
  }
  return driver;
}

几个值得展开的实现细节:

  1. let driver 模块级缓存:由于各 API 路由(pages/api/movies/index.jspages/api/movies/[title].jspages/api/actors/[name].js)都从同一模块导入 getDriver,模块作用域中的 driver 变量在整个服务进程内共享,因此驱动只被创建一次,重复请求复用同一个连接池。可以推断:这正是示例有意避免为每个请求重复创建 driver 的设计——driver 在官方驱动中本应作为长生命周期对象被复用。
  2. disableLosslessIntegers: true:Neo4j 服务端返回的整数在驱动中默认包装为 Neo4j 的 Long(无损整数)类型,前端要拿到普通 JS number 需要逐个调用 .toNumber()。示例源码注释明确说明启用该选项是为了省去对每个整数值调用 .toNumber() 的繁琐操作releasedbornrating 等属性即可直接作为普通数字使用),代价是放弃超过 JS 安全整数范围的超大整数支持。这是采用图查询返回值直接序列化为 JSON 传给前端时非常实用的一个配置。
  3. neo4j.auth.basic(username, password):使用用户名密码的基础认证方式连接数据库。

在此基础上,每个 API 路由文件在模块顶层就建立了一次会话(session):

const driver = getDriver();
const session = driver.session();

并在每个请求处理函数内通过 session.readTransaction(...) 开启只读事务,驱动会在事务提交/回滚后管理好会话的生命周期。

三个 API 路由与 Cypher 查询实现

示例的全部查询能力由三个 API Routes 暴露,它们只响应 GET 方法,其他方法一律返回 400 { success: false }

1. GET /api/movies —— 影片列表

实现位于 pages/api/movies/index.js。它通过模式理解(pattern comprehension)在一次 Cypher 中同时取出影片自身属性、参演演员名单与导演名单,并按片名升序排列:

MATCH (movie:Movie)
RETURN movie {.*,
  actors: [ (movie)<-[:ACTED_IN]-(actor) | actor.name ],
  directed: [ (movie)<-[:DIRECTED]-(director) | director.name ]
} as movie
ORDER BY movie.title ASC

其中 movie {.*, ...} 是 Cypher map projection.* 表示展开节点的全部属性(titlereleasedtagline),actorsdirected 两个键则通过 [(movie)<-[:REL]-(x) | x.name] 这种列表模式理解把"反向关联到该影片的演员/导演姓名"收集成字符串数组。这样返回的每条 movie 已经是一份可直接序列化的 JSON 对象。

处理逻辑为:开启只读事务执行 Cypher,将每条记录的 movie 取出并放入数组,最后响应 200 { success: true, movies };任何查询异常则落入 catch 返回 400 { success: false }

2. GET /api/movies/:title —— 单部影片详情

实现位于 pages/api/movies/[title].js。动态段 title 从路由 query 中读取,并先经过 decodeURIComponent(title) 还原——因为影片标题会作为 URL 路径参数传递(例如 The Devil's Advocate 这类含空格与特殊字符的标题)。

查询使用参数化 Cypher,避免字符串拼接注入:

MATCH (movie:Movie {title: $movieTitle})
RETURN movie {.*,
  actors: [ (actor)-[:ACTED_IN]->(movie) | actor.name ],
  directed: [ (director)-[:DIRECTED]->(movie) | director.name ]
} as movie

注意这里的关系方向与列表接口相反(从关联节点指向影片),取 records 的第一条作为 movie 返回。

3. GET /api/actors/:name —— 演员详情

实现位于 pages/api/actors/[name].js,逻辑与影片详情对称:先 decodeURIComponent(name),再按 name 精确匹配 Person 节点,并通过 ACTED_IN 关系反查其参演过的全部影片标题:

MATCH (actor:Person {name: $actorName})
RETURN actor {.*,
  movies: [ (actor)-[:ACTED_IN]->(m) | m.title ]
} as actor

结果中的 actor 对象包含 nameborn 以及 movies 数组,供详情页渲染演员基本信息与作品列表。

前端渲染链路:SWR 拉取 + 动态路由跳转

浏览器侧由 SWR 发起数据请求,统一使用 lib/fetcher.js 这个极简封装作为数据源:

export default async function fetcher(...args) {
  const res = await fetch(...args);
  return res.json();
}

首页:影片总览

pages/index.js 挂载即请求 /api/movies

const { data, error, isLoading } = useSWR("/api/movies", fetcher);

if (error) return <div>failed to load</div>;
if (isLoading) return <div>loading...</div>;
if (!data) return null;

拿到 data.movies 后渲染表格,每行包含片名、上映年份、标语、导演列表与演员列表。影片标题与演员姓名都用 <Link> 包成可点击链接,跳转时使用 encodeURIComponent 处理 URL:

<Link
  href={`/movie/${encodeURIComponent(movie.title)}`}
  className="link"
>
  {movie.title}
</Link>
<Link
  href={`/actor/${encodeURIComponent(actor)}`}
  className="link"
>
  {actor}
</Link>

影片详情页

pages/movie/[title].jsuseRouter() 读取 router.query.title,再请求 /api/movies/${title}(SWR 的 key 是模板字符串,title 变化会自动重新请求),展示 tagline、上映年份、演员(再次链接到演员详情)与导演名单。

演员详情页

pages/actor/[name].js 请求 /api/actors/${name},展示出生年份与其参演影片列表,影片名称可继续跳回对应影片详情页。示例中还演示了一种把"动态路由 + 动态路径"合并使用的 Link 写法:

<Link
  href="/movie/[title]"
  as={{
    pathname: `/movie/${encodeURIComponent(movie)}`,
  }}
>
  {movie}
</Link>

页面统一通过 pages/_app.js 引入 styles/global.js 中的全局样式,并用 components/header.jstitle prop 区分"欢迎页"与"详情页"两种标题形态。

至此,完整的数据流为:浏览器 SWR → Next.js API Routes(服务端 Neo4j 驱动 + Cypher)→ Neo4j 图数据库 → 结构化 JSON 返回 → React 渲染 + 动态路由互跳。图数据库中"关系"的表达能力(一部影片的演员与导演、一名演员的所有作品)被天然映射为页面的导航网络,这正是图模型在 Web 应用中典型的落地形态。

部署到云端

部署本地项目

将项目推送到 Git 远程仓库后,在 Vercel(或任意支持 Node.js 的服务平台)中导入该仓库即可完成部署。由于 .env.local 不会进入版本库,在云平台必须手动补齐环境变量:在项目的 Environment Variables 配置中逐项添加 NEO4J_URINEO4J_USERNEO4J_PASSWORD,并确保值与本地 .env.local 一致,同时数据库地址需要是该平台能够访问到的实例(例如 Neo4j Aura 托管实例)。若使用 Next.js 官方模板方式部署,平台同样会在创建项目时要求预先填写这三项环境变量,其含义与 .env.local.example 完全对应。

一个部署注意事项

本示例的 API 路由在模块顶层同步调用 getDriver() 并创建 session。因此部署到 Serverless 或无服务器函数环境时,需要留意:模块初始化发生在函数冷启动阶段,环境变量必须在该阶段就已注入;而请求级事务通过 session.readTransaction() 执行。对于生产环境的高并发场景,通常需要结合平台特性管理连接池与函数的生命周期,但本示例作为入门模板,默认以长驻 Node.js 进程(本地 next start 或传统 Node 部署)为前提即可顺畅运行。

小结

with-neo4j 示例用最少量的代码完整示范了三条可迁移到真实项目中的经验:

  1. 把图查询收敛在 API Routes:Cypher 与驱动只出现在服务端,前端只需消费 JSON,天然隔离了数据库凭据。
  2. 驱动单例 + 参数化 Cypher + 事务执行util/neo4j.js 中的 getDriver() 模式(配合 disableLosslessIntegers 免去 .toNumber())与各 API 中的 session.readTransaction() 是官方驱动推荐的用法骨架。
  3. 用 map projection 直接产出 JSONmovie {.*, actors: [...], directed: [...] } 让服务端不必做二次组装,一条查询即可回传页面所需的结构化数据。

你可以基于该模板继续扩展:例如增加 POST /api/movies 写事务(session.writeTransaction)、用 REVIEWED 关系做评分聚合,或把 Cypher 换成更复杂的多跳关系查询(如"合作过的演员"),从而更充分地发挥 Neo4j 关系遍历的威力。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.74 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
595
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.63 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
518
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
389