发布于 2026-01-06 5 阅读
0

如何使用 Apollo-Server 和 Prisma 构建 GraphQL API 💻 📓 📕 📗 📘 📙 📔 📒 📚 📖 💙 💜 💓 💗 💖 💘 💝 🎁 🎊 🎉

如何使用 Apollo-Server 和 Prisma 构建 GraphQL API

💻 📓 📕 📗 📘 📙 📔 📒 📚 📖 💙 💜 💓 💗 💖 💘 💝 🎁 🎊 🎉

介绍

GraphQL 是一种查询语言,旨在通过提供直观灵活的语法和系统来构建客户端应用程序,从而描述其数据需求和交互。在上一课中您学习了如何结合使用 GraphQL 和Prisma,因为它们的功能相辅相成。
在本课中,您将学习如何处理具有复杂关系的多个模型,这些模型能够真实地反映业务需求。

内容

🔷 步骤 1 — 创建 Node.js 项目

🔷 步骤 2 — 使用 PostgreSQL 设置 Prisma

🔷 第三步 — 使用 Prisma 创建和迁移数据库

🔷 第 4 步 — 定义 GraphQL 模式

🔷 第 5 步 — 定义 GraphQL 解析器

🔷 第 6 步 — 创建 GraphQL 服务器

🔷 第 7 步 — 测试和部署

先决条件

🔷 步骤 1 — 创建 Node.js 项目

首先,为你的项目创建一个新目录,初始化 npm 并安装依赖项:

$ mkdir node-graphql-lesson-04

$ cd node-graphql-lesson-04

$ npm init --yes

$ npm install apollo-server graphql
Enter fullscreen mode Exit fullscreen mode
  • Apollo Server: Apollo Server是一个由社区维护的开源 GraphQL 服务器,兼容任何 GraphQL 客户端。它是构建可用于生产环境、自文档化的 GraphQL API 的最佳方式,该 API 可以使用来自任何来源的数据。

  • graphql: GraphQL.js是 GraphQL 的 JavaScript 参考实现。它提供了两个重要功能:构建类型模式和针对该类型模式提供查询服务。

您已创建项目并安装了依赖项。下一步,您需要定义 GraphQL schema,它决定了 API 可以处理的操作。

🔷 步骤 2 — 使用 PostgreSQL 设置 Prisma

Prisma schema 是 Prisma 设置的主要配置文件,其中包含数据库架构。

首先使用以下命令安装 Prisma CLI:

$ npm install prisma -D
Enter fullscreen mode Exit fullscreen mode

Prisma CLI 可以帮助处理数据库工作流程,例如运行数据库迁移和生成 Prisma 客户端。

接下来,您将使用 Docker 设置 PostgreSQL 数据库。使用以下命令创建一个新的 Docker Compose 文件:

$  touch docker-compose.yml
Enter fullscreen mode Exit fullscreen mode

现在将以下代码添加到新创建的文件中:

# node-graphql-lesson-04/docker-compose.yml

version: '3.8'
services:
  postgres:
    image: postgres:13
    restart: always
    environment:
      - POSTGRES_USER=db_user
      - POSTGRES_PASSWORD=db_password
    volumes:
      - postgres:/var/lib/postgresql/data
    ports:
      - '5432:5432'
volumes:
  postgres:

Enter fullscreen mode Exit fullscreen mode

此 Docker Compose 配置文件负责在您的机器上启动官方 PostgreSQL Docker 镜像。POSTGRES_USER 和 POSTGRES_PASSWORD 环境变量设置超级用户(具有管理员权限的用户)的凭据。您还将使用这些凭据将 Prisma 连接到数据库。最后,您需要定义一个卷,PostgreSQL 将在其中存储数据,并将您机器上的 5432 端口绑定到 Docker 容器中的相同端口。

完成以上设置后,请使用以下命令启动 PostgreSQL 数据库服务器:

$ docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

PostgreSQL 容器运行后,现在可以创建 Prisma 配置了。从 Prisma CLI 运行以下命令:

$ npx prisma init
Enter fullscreen mode Exit fullscreen mode
# node-graphql-lesson-04/prisma/.env

DATABASE_URL="postgresql://db_user:db_password@localhost:5432/college_db?schema=public"
Enter fullscreen mode Exit fullscreen mode

🔷 第三步 — 使用 Prisma 创建和迁移数据库

您的大学 GraphQL API 目前只有一个名为Student 的实体。在此步骤中,您将通过在 Prisma schema 中定义一个新模型并调整 GraphQL schema 以使用该新模型来扩展 API。您将引入TeacherCourseDepartment模型。此外, Department模型与Student模型之间以及TeacherCourse 模型之间都存在一对多关系。这将允许您表示一位教师和一门课程,例如,将多门课程与一位教师关联起来。然后,您将扩展 GraphQL schema,以允许通过 API 创建教师并将课程与教师关联起来。

首先,打开 Prisma schema 并添加以下内容:

大学管理系统基本应包含以下几个方面:

  • 学生
  • 教师
  • 部门
  • 课程

其他实体,例如课程、费用、成绩单和班级,显然也是解决方案的一部分,但就本课而言并非必要。请参见下面的实体图:

前往 node-graphql/prisma/schema.prisma 文件,并向其中添加以下模型定义:

//* node-graphql-lesson-04/prisma/schema.prisma

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Student {
  id        Int        @id @default(autoincrement())
  email     String     @unique @db.VarChar(255)
  fullName  String?    @db.VarChar(255)
  enrolled  Boolean    @default(false)
  dept      Department @relation(fields: [deptId], references: [id])
  deptId    Int
  createdAt DateTime   @default(now())
  updatedAt DateTime   @updatedAt

  @@map(name: "student")
}

model Department {
  id          Int       @id @default(autoincrement())
  name        String    @unique
  description String?   @db.VarChar(500)
  students    Student[]
  courses     Course[]
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt

  @@map(name: "department")
}

model Teacher {
  id        Int         @id @default(autoincrement())
  email     String      @unique @db.VarChar(255)
  fullName  String?     @db.VarChar(255)
  courses   Course[]
  type      TeacherType @default(FULLTIME)
  createdAt DateTime    @default(now())
  updatedAt DateTime    @updatedAt

  @@map(name: "teacher")
}

model Course {
  id          Int         @id @default(autoincrement())
  code        String      @unique
  title       String      @db.VarChar(255)
  description String?     @db.VarChar(500)
  teacher     Teacher?    @relation(fields: [teacherId], references: [id])
  teacherId   Int?
  dept        Department? @relation(fields: [deptId], references: [id])
  deptId      Int?
  createdAt   DateTime    @default(now())
  updatedAt   DateTime    @updatedAt

  @@map(name: "course")
}

enum TeacherType {
  FULLTIME
  PARTTIME
}

Enter fullscreen mode Exit fullscreen mode

您已将以下内容添加到 Prisma 架构中:

  • 模型用于表示课程专业方向。
  • 教师模型用于表示课程讲师/辅导员。
  • 课程模型用于表示学科内容

学生模型已修改如下:

  • 两个关系字段:dept 和 deptId。关系字段定义了 Prisma 层级模型之间的连接,它们并不存在于数据库中。这些字段用于生成 Prisma 客户端,以及通过 Prisma 客户端访问关系。

  • deptId 字段由 @relation 属性引用。Prisma 将在数据库中创建一个外键,用于连接学生和部门。

请注意,学生模型中的“系”字段是可选的,与课程模型中的“教师”字段类似。这意味着您可以创建未关联任何系的学生,也可以创建没有关联教师的课程。

这种关系是有道理的,因为课程通常会分配给教师,而且注册学生通常会被分配到某个系。

接下来,使用以下命令在本地创建并应用迁移:

$  npx prisma migrate dev
Enter fullscreen mode Exit fullscreen mode

如果迁移成功,您将收到以下内容:

Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "college_db", schema "public" at "localhost:5432"

Database reset successful

The following migration(s) have been applied:

migrations/
  └─ 20210821201819_init/
    └─ migration.sql

✔ Generated Prisma Client (2.29.1) to ./node_modules/@prisma/client in 109ms

Enter fullscreen mode Exit fullscreen mode

该命令还会生成 Prisma 客户端,以便您可以使用新表和字段。

现在,您需要更新 GraphQL schema 和解析器,以使用更新后的数据库 schema。

模型构建完成后,即可使用 Prisma Migrate 在数据库中创建相应的表。这可以通过 `migrate dev` 命令完成,该命令会创建迁移文件并运行它们。

再次打开终端并运行以下命令:

$ npx prisma migrate dev --name "init" 
Enter fullscreen mode Exit fullscreen mode

您现在已经创建了数据库架构。接下来,您将安装 Prisma 客户端。

Prisma Client 是一个自动生成且类型安全的对象关系映射器 (ORM),您可以使用它从 Node.js 应用程序以编程方式读取和写入数据库中的数据。在此步骤中,您将在项目中安装 Prisma Client。

再次打开终端并安装 Prisma Client npm 包:

$  npm install @prisma/client
Enter fullscreen mode Exit fullscreen mode

数据库和 GraphQL schema 创建完毕,Prisma Client 也已安装完成。现在,您将在 GraphQL 解析器中使用 Prisma Client 来读写数据库中的数据。具体操作是替换 database.js 文件的内容,该文件目前用于存储您的数据。

//* node-graphql-lesson-04/src/database.js

const { PrismaClient } = require('@prisma/client')
const prisma = new PrismaClient();

module.exports = {
  prisma,
}
Enter fullscreen mode Exit fullscreen mode

接下来,在项目 src 目录下创建一个名为 database.js 的文件,并将 students 数组添加到该文件中,如下所示:

🔷 第 4 步 — 定义 GraphQL 模式

模式(schema)是一组类型定义(即 typeDefs)的集合,它们共同定义了可以针对 API 执行的查询的结构。这将把 GraphQL 模式字符串转换为 Apollo 期望的格式。创建一个src目录,并在其中创建schema.js文件。

$ mkdir src
$ touch src/schema.js
Enter fullscreen mode Exit fullscreen mode

现在将以下代码添加到文件中:

//* node-graphql-lesson-04/src/schema.js

const { gql } = require("apollo-server")

const typeDefs = gql `

 type Student {
    id: ID!
    email: String!
    fullName: String!
    dept: Department!
    enrolled: Boolean
    updatedAt: String
    createdAt: String
  }

  type Department {
    id: ID!
    name: String!
    description: String
    students: [Student]
    courses: [Course]
    updatedAt: String
    createdAt: String
  }

  type Teacher {
    id: ID!
    email: String!
    fullName: String!
    courses: [Course]
    type: TeacherType
    updatedAt: String
    createdAt: String
  }

  type Course {
    id: ID!
    code: String!
    title: String!
    description: String
    teacher: Teacher
    dept: Department
    updatedAt: String
    createdAt: String
  }

  input TeacherCreateInput {
    email: String!
    fullName: String!
    courses: [CourseCreateWithoutTeacherInput!]
  }

  input CourseCreateWithoutTeacherInput {
    code: String!
    title: String!
    description: String
  }

  type Query {
    enrollment: [Student!]
    students: [Student!]
    student(id: ID!): Student
    departments: [Department!]!
    department(id: ID!): Department
    courses: [Course!]!
    course(id: ID!): Course
    teachers: [Teacher!]!
    teacher(id: ID!): Teacher
  }

  type Mutation {
    registerStudent(email: String!, fullName: String!, deptId: Int!): Student!
    enroll(id: ID!): Student
    createTeacher(data: TeacherCreateInput!): Teacher!
    createCourse(code: String!, title: String!, teacherEmail: String): Course!
    createDepartment(name: String!, description: String): Department!
  }

enum TeacherType {
  FULLTIME
  PARTTIME
}
`

module.exports = {
    typeDefs,
  }

Enter fullscreen mode Exit fullscreen mode

在更新后的代码中,您对 GraphQL schema 进行了以下更改:

  • Teacher类型,返回一个Course数组。
  • Department类型,返回一个Student数组。
  • 具有教师类型的课程类型
  • 将“部门”类型的字段转换“学生”类型。
  • createTeacher mutation 需要 TeacherCreateInput 作为其输入类型。

  • CourseCreateWithoutTeacherInput 输入类型用于 TeacherCreateInput 输入,以便在 createTeacher mutation 中创建教师。

  • createCourse mutation 的 teacherEmail 可选参数。

模式创建完成后,接下来需要创建与该模式匹配的解析器。

🔷 第 5 步 — 定义 GraphQL 解析器

在src目录下创建一个名为resolvers 的子目录 然后在resolvers子目录下创建三个文件:index.jsquery.jsmutation.js ,内容如下:

$ mkdir src/resolvers
$ touch src/resolvers/index.js
$ touch src/resolvers/query.js
$ touch src/resolvers/mutation.js
Enter fullscreen mode Exit fullscreen mode

在 mutation.js 文件中,输入以下内容:

//* node-graphql-lesson-04/src/resolvers/mutation.js

const { prisma } = require("../database.js");

const Mutation = {
    registerStudent: (parent, args) => {
      return prisma.student.create({
        data: {
          email: args.email,
          fullName: args.fullName,
          dept: args.deptId && {
            connect: { id: args.deptId },
          },
        },
      });
    },
    enroll: (parent, args) => {
      return prisma.student.update({
        where: { id: Number(args.id) },
        data: {
          enrolled: true,
        },
      });
    },

    createTeacher: (parent, args) => {
      return prisma.teacher.create({
        data: {
          email: args.data.email,
          fullName: args.data.fullName,
          courses: {
            create: args.data.courses,
          },
        },
      });
    },

    createCourse: (parent, args) => {
      console.log(parent, args)
      return prisma.course.create({
        data: {
          code: args.code,
          title: args.title,
          teacher: args.teacherEmail && {
            connect: { email: args.teacherEmail },
          },
        },
      });
    },

    createDepartment: (parent, args) => {
      return prisma.department.create({
        data: {
          name: args.name,
          description: args.description,
        },
      });
    },
  };

  module.exports = {
    Mutation,
  }


Enter fullscreen mode Exit fullscreen mode

在 query.js 文件中,输入以下内容:

//* node-graphql-lesson-04/src/resolvers/query.js

const { prisma } = require("../database.js");

const Query = {
    enrollment: (parent, args) => {
      return prisma.student.findMany({
        where: { enrolled: true },
      });
    },
    student: (parent, args) => {
      return prisma.student.findFirst({
        where: { id: Number(args.id) },
      });
    },

    students: (parent, args) => {
      return prisma.student.findMany({});
    },

    departments: (parent, args) => {
      return prisma.department.findMany({});
    },

    department: (parent, args) => {
      return prisma.department.findFirst({
        where: { id: Number(args.id) },
      });
    },

    courses: (parent, args) => {
      return prisma.course.findMany({});
    },

    course: (parent, args) => {
      return prisma.course.findFirst({
        where: { id: Number(args.id) },
      });
    },

    teachers: (parent, args) => {
      return prisma.teacher.findMany({});
    },

    teacher: (parent, args) => {
      return prisma.teacher.findFirst({
        where: { id: Number(args.id) },
      });
    },
  };

  module.exports = {
    Query,
  }

Enter fullscreen mode Exit fullscreen mode

最后,在 index.js 文件中,输入以下内容:

//* node-graphql-lesson-04/src/resolvers/index.js

const { prisma } = require("../database.js");
const { Query } = require("./query.js");
const { Mutation } = require("./mutation.js");

const Student = {
  id: (parent, args, context, info) => parent.id,
  email: (parent) => parent.email,
  fullName: (parent) => parent.fullName,
  enrolled: (parent) => parent.enrolled,
  dept: (parent, args) => {
    return prisma.department.findFirst({
      where: { id: parent.dept },
    });
  },
};

const Department = {
  id: (parent) => parent.id,
  name: (parent) => parent.name,
  description: (parent) => parent.description,
  students: (parent, args) => {
    return prisma.department.findUnique({
        where: { id: parent.id },
      }).students();
  },
  courses: (parent, args) => {
    return prisma.department.findUnique({
        where: { id: parent.id },
      }).courses();
  },
};

const Teacher = {
  id: (parent) => parent.id,
  email: (parent) => parent.email,
  fullName: (parent) => parent.fullName,
  courses: (parent, args) => {
    return prisma.teacher.findUnique({
        where: { id: parent.id },
      }).courses();
  },
};

const Course = {
  id: (parent) => parent.id,
  code: (parent) => parent.code,
  title: (parent) => parent.title,
  description: (parent) => parent.description,
  teacher: (parent, args) => {
    return prisma.course.findUnique({
        where: { id: parent.id },
      }).teacher();
  },
  dept: (parent, args) => {
    return prisma.course.findUnique({
      where: { id: parent.id },
    }).dept();
  },
};

const resolvers = {
  Student,
  Department,
  Teacher,
  Course,
  Query,
  Mutation,
};

module.exports = {
  resolvers,
};

Enter fullscreen mode Exit fullscreen mode

让我们来详细分析一下解析器的变化:

  • createCourse 变更解析器现在使用 teacherEmail 参数(如果传递)在创建的课程和现有教师之间建立关系。

  • 新的 createTeacher 变更解析器使用嵌套写入创建教师和相关课程。

  • Teacher.courses 和 Post.teacher 解析器定义了在查询 Teacher 或 Post 时如何解析 courses 和 teacher 字段。它们使用 Prisma 的 Fluent API 来获取关联关系。

🔷 第 6 步 — 创建 GraphQL 服务器

在此步骤中,您将使用 Apollo Server 创建 GraphQL 服务器并将其绑定到端口,以便服务器可以接受连接。

首先,运行以下命令为服务器创建文件:

$ touch src/index.js
Enter fullscreen mode Exit fullscreen mode

现在将以下代码添加到文件中:

//* node-graphql-lesson-04/src/index.js

const { ApolloServer } = require('apollo-server')
const { typeDefs } = require('./schema')
const { resolvers } = require('./resolvers')

const port = process.env.PORT || 9090;

const server = new ApolloServer({ resolvers, typeDefs });

server.listen({ port }, () => console.log(`Server runs at: http://localhost:${port}`));
Enter fullscreen mode Exit fullscreen mode

启动服务器以测试 GraphQL API:

$  npm start
$  npm install nodemon -D
Enter fullscreen mode Exit fullscreen mode

最后,你的 package.json 文件看起来像这样:

{
  "name": "node-graphql-lesson-04",
  "version": "1.0.0",
  "description": "Graphql backend with node, prisma, postgres and docker",
  "main": "index.js",
  "scripts": {
    "start": "nodemon src/"
  },
  "keywords": [
    "Graphql",
    "Backend",
    "Prisma",
    "Postgre",
    "Docker",
    "Node.js"
  ],
  "author": "Nditah Sam <nditah@telixia.com>",
  "license": "ISC",
  "dependencies": {
    "@prisma/client": "^2.29.1",
    "apollo-server": "^3.1.2",
    "graphql": "^15.5.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.12",
    "prisma": "^2.29.1"
  }
}
Enter fullscreen mode Exit fullscreen mode

🔷 第 7 步 — 测试和部署

通过执行以下 GraphQL 查询和变更来测试 node-graphql-prisma 后端:

创建部门

 mutation {
  createDepartment(name: "Backend Engineering", description: "Express, ApolloServer, Prisma, Docker, Postgres") {
    id
    name
    description

  }
}

mutation {
  createDepartment(name: "Frontend Development", description: "React, Angular, Vue, Gatsby, CSS, Bootstrap") {
    id
    name
    description
  }
}
Enter fullscreen mode Exit fullscreen mode

### 创建课程


mutation CreateCourseMutation($createCourseCode: String!, $createCourseTitle: String!) {
  createCourse(code: $createCourseCode, title: $createCourseTitle) {
    id
    code
    title
    description
    teacher {
      id
      fullName
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

### 创建教师

mutation CreateTeacherMutation($createTeacherData: TeacherCreateInput!) {
  createTeacher(data: $createTeacherData) {
    id
    fullName
    createdAt
    courses {
      id
      code
      title
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

请注意,只要查询的返回值是 Course,您就可以获取教师信息。在本例中,将调用 Course.teacher 解析器。

最后,提交更改并推送以部署 API:

$  git add .
$  git commit -m "Feature: Add Teacher, Couse, Department"
$  git push
Enter fullscreen mode Exit fullscreen mode

您已使用 Prisma Migrate 成功迁移了数据库模式,并在 GraphQL API 中公开了新模型。
该项目的 GitHub 代码库位于此处

结论

尽管本课的目的并非比较 REST 和 GraphQL,但仍需强调以下几点:

🔷 虽然 GraphQL 简化了数据消费,但由于其缓存特性、安全性、完善的工具社区和卓越的可靠性,REST 设计标准仍然受到许多行业的青睐。正因如此,加上 REST 的良好口碑,许多 Web 服务都倾向于采用 REST 设计。

🔷 无论选择哪种方案,后端开发人员都必须准确理解前端用户将如何与 API 交互,才能做出正确的设计选择。虽然某些 API 风格比其他风格更容易上手,但只要有完善的文档和演练,后端工程师就能构建出高质量的 API 平台,让前端开发人员无论使用哪种风格都能满意。

延伸阅读

[1] Prisma Fluent-Api
[2] Prisma 组件
[3] GraphQL 入门
[4] Apollo Server 入门

阅读与编程愉快!

💻 📓 📕 📗 📘 📙 📔 📒 📚 📖 💙 💜 💓 💗 💖 💘 💝 🎁 🎊 🎉

文章来源:https://dev.to/nditah/how-to-build-a-graphql-api-with-apollo-server-and-prisma-1bfj