跳到主要内容

在 Cursor 中开发 GraphQL API

GraphQL API Development in Cursor

GraphQL 通过允许客户端精确请求所需数据,彻底改变了我们构建 API 的方式。当与 Cursor(一款 AI 驱动的 IDE 编辑器)结合使用时,您的 GraphQL 开发工作流程将变得更加快速和智能。本指南涵盖从架构设计到生产级解析器实现的所有内容,使用 Cursor 的高级功能。

在 Cursor 中入门 GraphQL​

Cursor 通过其智能代码补全、AI 驱动的代码生成以及对架构关系的深度理解,为 GraphQL 开发提供了卓越的环境。无论您是从头开始构建新 API 还是维护现有 API,Cursor 的功能都能帮助您用更少的精力编写更好的 GraphQL 代码。

Cursor 与 GraphQL 工具的集成使您能够:

  • 从 TypeScript 类型自动生成架构
  • 接收解析器实现的智能建议
  • 实时针对架构验证查询
  • 借助 AI 辅助重构大型架构
  • 直接从代码生成文档

在深入了解实现细节之前,请确保您的工作区中已安装以下工具:

  • Node.js 18 或更高版本
  • Cursor IDE(建议使用最新版本)
  • 用于测试查询的 GraphQL 客户端扩展

在 Cursor 中设置 GraphQL 项目​

结构良好的项目对于可扩展的 GraphQL 开发至关重要。Cursor 擅长帮助您从一开始就组织代码库。

首先在 Cursor 中打开此目录并创建以下文件夹结构:

src/
schema/
typeDefs.ts
resolvers/
index.ts
userResolver.ts
postResolver.ts
prisma/
schema.prisma
server.ts

此结构清晰地分离了关注点,并使 Cursor 的 AI 能够理解架构定义、解析器逻辑和数据库模型之间的边界。当您在 Cursor 中打开此结构时,编辑器会自动索引所有类型关系并提供跨文件建议。

为 Apollo Server 和 Prisma 设置安装核心依赖:

npm install @apollo/server graphql
npm install @prisma/client
npm install -D prisma typescript ts-node @types/node

安装完成后,在项目中初始化 Prisma。Cursor 的终端集成使此过程变得无缝,AI 可以帮助调试设置期间出现的任何配置问题。

提示

使用 Cursor 的内联聊天询问有关包兼容性的问题。例如,选择您的 package.json 并询问:"这些 GraphQL 依赖项是否与最新的 Apollo Server 版本兼容?" Cursor 将分析版本并建议更新。

借助 AI 辅助设计架构​

架构设计是任何 GraphQL API 的基础。Cursor 的 AI 功能通过根据应用程序需求建议类型、字段和关系,使此过程更加直观。

创建类型定义​

首先在 src/schema/typeDefs.ts 中定义您的核心类型。Cursor 为 GraphQL SDL(架构定义语言)语法提供智能自动补全,包括内置标量、指令和自定义类型。

export const typeDefs = `#graphql
type User {
id: ID!
email: String!
name: String
posts: [Post!]!
createdAt: String!
updatedAt: String!
}

type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
createdAt: String!
updatedAt: String!
}

type Query {
users: [User!]!
user(id: ID!): User
posts: [Post!]!
post(id: ID!): Post
}

type Mutation {
createUser(email: String!, name: String): User!
createPost(title: String!, content: String!, authorId: ID!): Post!
publishPost(id: ID!): Post!
deletePost(id: ID!): Post!
}
`;

当您在 Cursor 中键入类型定义时,编辑器会识别 GraphQL 语法并提供上下文感知建议。AI 理解类型之间的关系,并可以在您定义新实体时建议缺失的字段。

从 Prisma 模型生成架构​

Cursor 最强大的工作流程之一是直接从您的 Prisma 模型生成 GraphQL 架构。在 prisma/schema.prisma 中定义数据库架构后,您可以使用 Cursor 的 AI 聊天将这些模型转换为 GraphQL 类型定义。

以下是一个 Prisma 架构示例:

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

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

model User {
id String @id @default(uuid())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

model Post {
id String @id @default(uuid())
title String
content String
published Boolean @default(false)
authorId String
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

在 Cursor 中选择 Prisma 架构并使用 AI 聊天功能,提示如:"为这些 Prisma 模型生成相应的 GraphQL 类型定义,包括 Query 和 Mutation 类型。" Cursor 将生成准确反映您数据库结构的 SDL。

注意

始终审查 AI 生成的架构,以确保满足业务逻辑需求。AI 理解结构,但可能会遗漏应用程序所需的特定领域验证规则或自定义标量。

在 Cursor 中实现解析器​

解析器是 GraphQL API 发挥作用的地方。Cursor 的 TypeScript 集成使编写类型安全的解析器效率显著提高。

编写类型安全的解析器​

创建具有完整 TypeScript 支持的解析器文件。Cursor 理解您的架构与解析器类型之间的关系,为父对象、参数和上下文提供自动补全。

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export const userResolver = {
Query: {
users: async () => {
return await prisma.user.findMany({
include: { posts: true },
});
},

user: async (_: unknown, { id }: { id: string }) => {
return await prisma.user.findUnique({
where: { id },
include: { posts: true },
});
},
},

User: {
posts: async (parent: { id: string }) => {
return await prisma.post.findMany({
where: { authorId: parent.id },
});
},
},

Mutation: {
createUser: async (
_: unknown,
{ email, name }: { email: string; name?: string }
) => {
return await prisma.user.create({
data: { email, name },
});
},
},
};

Cursor 的 IntelliSense 根据您的架构定义为解析器参数提供准确的类型信息。当您将鼠标悬停在参数或返回值上时,您会看到映射到 TypeScript 的精确 GraphQL 类型。

使用 AI 生成复杂解析器​

对于涉及数据转换、聚合或第三方 API 调用的复杂解析器逻辑,Cursor 的 AI 生成功能非常宝贵。选择架构的一部分并要求 Cursor 生成相应的解析器实现。

例如,给定此高级架构定义:

type Query {
searchPosts(query: String!, filters: PostFilters): SearchResult!
}

input PostFilters {
publishedOnly: Boolean
dateFrom: String
dateTo: String
authorIds: [ID!]
}

type SearchResult {
posts: [Post!]!
totalCount: Int!
hasMore: Boolean!
}

您可以提示 Cursor:"实现一个 searchPosts 的解析器,该解析器按提供的条件过滤,支持默认限制为 20 的分页,并返回 totalCount 和 hasMore 标志。" AI 将使用 Prisma 的过滤 API 生成完整的实现。

searchPosts: async (
_: unknown,
{
query,
filters,
}: {
query: string;
filters?: {
publishedOnly?: boolean;
dateFrom?: string;
dateTo?: string;
authorIds?: string[];
};
},
context: { userId?: string }
) => {
const limit = 20;
const where: any = {
OR: [
{ title: { contains: query, mode: 'insensitive' } },
{ content: { contains: query, mode: 'insensitive' } },
],
};

if (filters?.publishedOnly) {
where.published = true;
}

if (filters?.dateFrom || filters?.dateTo) {
where.createdAt = {};
if (filters.dateFrom) where.createdAt.gte = new Date(filters.dateFrom);
if (filters.dateTo) where.createdAt.lte = new Date(filters.dateTo);
}

if (filters?.authorIds?.length) {
where.authorId = { in: filters.authorIds };
}

const [posts, totalCount] = await Promise.all([
prisma.post.findMany({
where,
take: limit,
include: { author: true },
}),
prisma.post.count({ where }),
]);

return {
posts,
totalCount,
hasMore: totalCount > limit,
};
},
提示

在生成复杂解析器时,始终审查 Prisma 查询是否存在 N+1 问题。Cursor 的 AI 通常生成基本实现,可能不包括用于批量加载相关实体的 DataLoader 模式。

Apollo Server 集成​

在 Cursor 的帮助下,将您的架构和解析器连接到 Apollo Server 非常简单。编辑器帮助您使用正确的 TypeScript 类型和中间件配置服务器。

设置服务器​

使用 Apollo Server 4 配置创建您的 server.ts 文件:

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { typeDefs } from './schema/typeDefs';
import { resolvers } from './schema/resolvers';

interface MyContext {
token?: string;
userId?: string;
}

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

const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async ({ req }): Promise<MyContext> => {
const token = req.headers.authorization || '';
// 在此处添加身份验证逻辑
return { token };
},
});

console.log(`Server ready at: ${url}`);

Cursor 为 Apollo Server API 提供自动补全,并帮助您在整个配置过程中保持正确的类型。AI 还可以为身份验证、日志记录和错误处理建议中间件模式。

添加自定义标量和指令​

高级 GraphQL API 通常需要自定义标量来处理专门的数据类型。Cursor 帮助您以完整的类型安全实现这些。

import { GraphQLScalarType, Kind } from 'graphql';

export const dateScalar = new GraphQLScalarType({
name: 'Date',
description: 'Custom Date scalar',
serialize(value: unknown) {
if (value instanceof Date) {
return value.toISOString();
}
throw Error('GraphQL Date Scalar serializer expected a Date object');
},
parseValue(value: unknown) {
if (typeof value === 'string') {
return new Date(value);
}
throw new Error('GraphQL Date Scalar parser expected a string');
},
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
return new Date(ast.value);
}
return null;
},
});

当您定义自定义标量时,Cursor 的 TypeScript 语言服务器确保您的序列化和解析函数处理正确的类型,防止运行时错误。

Prisma 集成最佳实践​

Prisma 和 GraphQL 在 Cursor 中的组合创造了强大的全栈开发体验。本节介绍有效集成这些技术的最佳实践。

生成 Prisma 客户端类型​

修改 Prisma 架构后,运行 generate 命令。Cursor 的终端集成捕获输出,如果有错误,AI 可以帮助您修复架构语法问题。

npx prisma generate

生成的 Prisma 客户端类型与 Cursor 的 IntelliSense 无缝集成。在编写解析器时,您可以获得模型字段、筛选选项和关系查询的完整自动补全。

使用 DataLoader 处理关系​

为避免 GraphQL 解析器中的 N+1 查询问题,请将 DataLoader 集成到您的 Cursor 项目中。AI 可以帮助您设置批量加载函数,Prisma 将高效执行这些函数。

import DataLoader from 'dataloader';
import { PrismaClient, User } from '@prisma/client';

const prisma = new PrismaClient();

export const createUserLoader = () => {
return new DataLoader<string, User | null>(async (userIds) => {
const users = await prisma.user.findMany({
where: { id: { in: [...userIds] } },
});

const userMap = new Map(users.map((user) => [user.id, user]));
return userIds.map((id) => userMap.get(id) || null);
});
};

在您的服务器上下文中,初始化加载器并将它们传递给解析器:

interface MyContext {
userLoader: ReturnType<typeof createUserLoader>;
postLoader: ReturnType<typeof createPostLoader>;
}

const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async (): Promise<MyContext> => {
return {
userLoader: createUserLoader(),
postLoader: createPostLoader(),
};
},
});
注意

DataLoader 实例应该按请求创建,以避免不同用户之间的缓存问题。Cursor 的 AI 有时会建议加载器的单例模式;始终验证您的实现为每个 GraphQL 请求创建新实例。

AI 查询生成与测试​

Cursor 的突出功能之一是基于您的架构生成和测试 GraphQL 查询。这加速了开发和调试工作流程。

从架构生成查询​

在 Cursor 中选择您的类型定义并要求 AI 生成示例查询和变更。这对于需要了解可用 API 界面的前端开发人员特别有用。

对于上述 User 和 Post 架构,Cursor 可能会生成:

query GetUserWithPosts($userId: ID!) {
user(id: $userId) {
id
email
name
posts {
id
title
published
createdAt
}
}
}

mutation CreateNewPost($title: String!, $content: String!, $authorId: ID!) {
createPost(title: $title, content: $content, authorId: $authorId) {
id
title
author {
id
name
}
}
}

这些生成的查询根据您的架构进行了完整类型化和验证,减少了前端集成错误的可能性。

使用生成的查询测试解析器​

使用 Cursor 的 AI 为解析器生成全面的测试套件。选择一个解析器函数并提示:"为包括模拟 Prisma 调用和边缘情况的解析器生成 Jest 测试。"

import { userResolver } from './userResolver';
import { PrismaClient } from '@prisma/client';

jest.mock('@prisma/client', () => {
const mockPrisma = {
user: {
findMany: jest.fn(),
findUnique: jest.fn(),
create: jest.fn(),
},
};
return { PrismaClient: jest.fn(() => mockPrisma) };
});

describe('User Resolver', () => {
const prisma = new PrismaClient();

beforeEach(() => {
jest.clearAllMocks();
});

it('should return all users with their posts', async () => {
const mockUsers = [
{ id: '1', email: '[email protected]', posts: [] },
{ id: '2', email: '[email protected]', posts: [] },
];
(prisma.user.findMany as jest.Mock).mockResolvedValue(mockUsers);

const result = await userResolver.Query.users();

expect(prisma.user.findMany).toHaveBeenCalledWith({
include: { posts: true },
});
expect(result).toEqual(mockUsers);
});

it('should return a single user by id', async () => {
const mockUser = { id: '1', email: '[email protected]', posts: [] };
(prisma.user.findUnique as jest.Mock).mockResolvedValue(mockUser);

const result = await userResolver.Query.user(null, { id: '1' });

expect(prisma.user.findUnique).toHaveBeenCalledWith({
where: { id: '1' },
include: { posts: true },
});
expect(result).toEqual(mockUser);
});
});

错误处理与验证​

健壮的 GraphQL API 需要全面的错误处理。Cursor 帮助您在解析器中实现一致的错误模式。

自定义错误类​

定义扩展 Apollo GraphQL 错误类型的自定义错误类:

import { GraphQLError } from 'graphql';

export class AuthenticationError extends GraphQLError {
constructor(message: string = 'Not authenticated') {
super(message, {
extensions: { code: 'UNAUTHENTICATED', http: { status: 401 } },
});
}
}

export class ValidationError extends GraphQLError {
constructor(message: string) {
super(message, {
extensions: { code: 'VALIDATION_FAILED', http: { status: 400 } },
});
}
}

export class NotFoundError extends GraphQLError {
constructor(resource: string) {
super(`${resource} not found`, {
extensions: { code: 'NOT_FOUND', http: { status: 404 } },
});
}
}

在您的解析器中使用这些错误,利用 Cursor 的自动补全支持:

user: async (_: unknown, { id }: { id: string }) => {
const user = await prisma.user.findUnique({
where: { id },
include: { posts: true },
});

if (!user) {
throw new NotFoundError('User');
}

return user;
},
提示

为 Apollo Server 创建一个错误插件,用于记录错误并为其格式化以发送给客户端。Cursor 可以根据您的自定义错误类生成此插件。

结论​

在 Cursor 中开发 GraphQL API 将现代 IDE 的强大功能与智能 AI 辅助相结合。从架构设计到解析器实现、测试和部署,Cursor 简化了 GraphQL 开发的方方面面。

本指南的关键要点:

  • 使用 Cursor 的 AI 从 Prisma 模型和 TypeScript 类型生成架构
  • 利用 TypeScript 集成进行类型安全的解析器开发
  • 实现 DataLoader 模式以防止 N+1 查询
  • 从架构定义自动生成测试和查询
  • 使用自定义错误类保持一致的错误处理

随着您继续使用 GraphQL 和 Cursor 进行构建,请尝试使用 AI 聊天功能来重构大型架构、优化数据库查询和生成文档。人类专业知识与 AI 辅助的结合可产生健壮、高性能且可维护的 API。