GraphQLAPI设计与开发Skill graphql

GraphQL API 设计与开发技能,提供 GraphQL 模式设计、解析器实现、数据加载器优化、自定义指令、实时订阅、身份验证授权以及查询性能优化等后端 API 开发全流程解决方案。关键词:GraphQL,API 设计,后端开发,数据加载器,实时订阅,性能优化。

后端开发 0 次安装 4 次浏览 更新于 2/26/2026

name: graphql description: GraphQL 模式设计、解析器、指令、订阅以及 API 开发的最佳实践。 allowed-tools: Read, Write, Edit, Bash, Glob, Grep

GraphQL 技能

为设计和实现 GraphQL API 提供专家级协助。

能力

  • 使用 SDL 设计 GraphQL 模式
  • 实现解析器和数据加载器
  • 创建自定义指令
  • 为实时功能设置订阅
  • 处理身份验证和授权
  • 优化查询性能

使用场景

在以下情况时调用此技能:

  • 设计 GraphQL API 模式
  • 实现解析器
  • 添加实时订阅
  • 创建自定义指令
  • 优化 N+1 查询问题

输入参数

参数 类型 是否必需 描述
typeName 字符串 GraphQL 类型名称
operations 数组 查询、变更、订阅
directives 数组 自定义指令

模式设计模式

类型定义

# schema.graphql

# 标量类型
scalar DateTime
scalar JSON

# 枚举类型
enum Role {
  USER
  ADMIN
}

enum SortOrder {
  ASC
  DESC
}

# 对象类型
type User {
  id: ID!
  name: String!
  email: String!
  role: Role!
  posts: [Post!]!
  createdAt: DateTime!
  updatedAt: DateTime!
}

type Post {
  id: ID!
  title: String!
  content: String!
  published: Boolean!
  author: User!
  comments: [Comment!]!
  createdAt: DateTime!
}

type Comment {
  id: ID!
  content: String!
  author: User!
  post: Post!
  createdAt: DateTime!
}

# 分页
type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

type UserEdge {
  node: User!
  cursor: String!
}

type UserConnection {
  edges: [UserEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

# 输入类型
input CreateUserInput {
  name: String!
  email: String!
  password: String!
  role: Role = USER
}

input UpdateUserInput {
  name: String
  email: String
  role: Role
}

input UsersFilterInput {
  search: String
  role: Role
}

input PaginationInput {
  first: Int
  after: String
  last: Int
  before: String
}

# 查询
type Query {
  me: User
  user(id: ID!): User
  users(
    filter: UsersFilterInput
    pagination: PaginationInput
    orderBy: SortOrder
  ): UserConnection!
  post(id: ID!): Post
  posts(published: Boolean): [Post!]!
}

# 变更
type Mutation {
  # 身份验证
  login(email: String!, password: String!): AuthPayload!
  register(input: CreateUserInput!): AuthPayload!

  # 用户
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!

  # 文章
  createPost(title: String!, content: String!): Post!
  updatePost(id: ID!, title: String, content: String, published: Boolean): Post!
  deletePost(id: ID!): Boolean!
}

# 订阅
type Subscription {
  postCreated: Post!
  postUpdated(id: ID!): Post!
  commentAdded(postId: ID!): Comment!
}

# 身份验证负载
type AuthPayload {
  token: String!
  user: User!
}

# 指令
directive @auth(requires: Role = USER) on FIELD_DEFINITION
directive @deprecated(reason: String) on FIELD_DEFINITION

解析器

// resolvers/index.ts
import { Resolvers } from '../generated/graphql';
import { Context } from '../context';
import { userResolvers } from './user.resolvers';
import { postResolvers } from './post.resolvers';
import { authResolvers } from './auth.resolvers';

export const resolvers: Resolvers<Context> = {
  Query: {
    ...userResolvers.Query,
    ...postResolvers.Query,
  },
  Mutation: {
    ...authResolvers.Mutation,
    ...userResolvers.Mutation,
    ...postResolvers.Mutation,
  },
  Subscription: {
    ...postResolvers.Subscription,
  },
  User: userResolvers.User,
  Post: postResolvers.Post,
};

// resolvers/user.resolvers.ts
import { GraphQLError } from 'graphql';
import { Context } from '../context';

export const userResolvers = {
  Query: {
    me: async (_: unknown, __: unknown, { user, prisma }: Context) => {
      if (!user) throw new GraphQLError('未通过身份验证');
      return prisma.user.findUnique({ where: { id: user.id } });
    },

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

    users: async (
      _: unknown,
      { filter, pagination, orderBy }: any,
      { prisma }: Context
    ) => {
      const { first = 10, after } = pagination || {};

      const where = filter?.search
        ? { name: { contains: filter.search, mode: 'insensitive' } }
        : undefined;

      const users = await prisma.user.findMany({
        where,
        take: first + 1,
        cursor: after ? { id: after } : undefined,
        skip: after ? 1 : 0,
        orderBy: { name: orderBy || 'asc' },
      });

      const hasNextPage = users.length > first;
      const edges = users.slice(0, first).map((user) => ({
        node: user,
        cursor: user.id,
      }));

      return {
        edges,
        pageInfo: {
          hasNextPage,
          hasPreviousPage: !!after,
          startCursor: edges[0]?.cursor,
          endCursor: edges[edges.length - 1]?.cursor,
        },
        totalCount: await prisma.user.count({ where }),
      };
    },
  },

  Mutation: {
    updateUser: async (
      _: unknown,
      { id, input }: { id: string; input: any },
      { user, prisma }: Context
    ) => {
      if (!user) throw new GraphQLError('未通过身份验证');
      if (user.id !== id && user.role !== 'ADMIN') {
        throw new GraphQLError('未获得授权');
      }

      return prisma.user.update({
        where: { id },
        data: input,
      });
    },
  },

  User: {
    posts: async (parent: any, _: unknown, { prisma }: Context) => {
      return prisma.post.findMany({ where: { authorId: parent.id } });
    },
  },
};

用于解决 N+1 问题的数据加载器

// loaders/index.ts
import DataLoader from 'dataloader';
import { PrismaClient, User, Post } from '@prisma/client';

export function createLoaders(prisma: PrismaClient) {
  return {
    userLoader: new DataLoader<string, User | null>(async (ids) => {
      const users = await prisma.user.findMany({
        where: { id: { in: ids as string[] } },
      });
      const userMap = new Map(users.map((u) => [u.id, u]));
      return ids.map((id) => userMap.get(id) || null);
    }),

    postsByAuthorLoader: new DataLoader<string, Post[]>(async (authorIds) => {
      const posts = await prisma.post.findMany({
        where: { authorId: { in: authorIds as string[] } },
      });
      const postsByAuthor = new Map<string, Post[]>();
      posts.forEach((post) => {
        const authorPosts = postsByAuthor.get(post.authorId) || [];
        authorPosts.push(post);
        postsByAuthor.set(post.authorId, authorPosts);
      });
      return authorIds.map((id) => postsByAuthor.get(id) || []);
    }),
  };
}

// 在解析器中的用法
User: {
  posts: (parent, _, { loaders }) => loaders.postsByAuthorLoader.load(parent.id),
}

最佳实践

  • 对变更操作使用输入类型
  • 实现基于游标的分页
  • 使用 DataLoader 防止 N+1 问题
  • 添加适当的错误处理
  • 使用描述信息记录模式

目标流程

  • graphql-api-开发
  • api-设计
  • 后端-开发
  • 实时-应用程序