graphql

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

GraphQL with Node.js Skill

Node.js 开发 GraphQL 技能详解

Master building flexible, efficient GraphQL APIs using Node.js with Apollo Server, type-safe schemas, and real-time subscriptions.
掌握使用Node.js结合Apollo Server、类型安全的Schema以及实时订阅来构建灵活、高效的GraphQL API。

Quick Start

快速开始

GraphQL API in 4 steps:
  1. Define Schema - Types, queries, mutations
  2. Write Resolvers - Data fetching logic
  3. Setup Server - Apollo Server configuration
  4. Connect Data - Database integration
4步构建GraphQL API:
  1. 定义Schema - 类型、查询、变更
  2. 编写解析器 - 数据获取逻辑
  3. 配置服务器 - Apollo Server 配置
  4. 连接数据 - 数据库集成

Core Concepts

核心概念

Schema Definition (SDL)

Schema定义(SDL)

graphql
type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  content: String
  author: User!
}

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
  post(id: ID!): Post
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User
  deleteUser(id: ID!): Boolean!
}

input CreateUserInput {
  name: String!
  email: String!
  password: String!
}

type Subscription {
  postCreated: Post!
}
graphql
type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  content: String
  author: User!
}

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
  post(id: ID!): Post
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User
  deleteUser(id: ID!): Boolean!
}

input CreateUserInput {
  name: String!
  email: String!
  password: String!
}

type Subscription {
  postCreated: Post!
}

Apollo Server Setup

Apollo Server 配置

javascript
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const { readFileSync } = require('fs');

const typeDefs = readFileSync('./schema.graphql', 'utf-8');

const resolvers = {
  Query: {
    user: async (_, { id }, { dataSources }) => {
      return dataSources.userAPI.getUser(id);
    },
    users: async (_, { limit = 10, offset = 0 }, { dataSources }) => {
      return dataSources.userAPI.getUsers(limit, offset);
    }
  },
  Mutation: {
    createUser: async (_, { input }, { dataSources }) => {
      return dataSources.userAPI.createUser(input);
    }
  },
  User: {
    posts: async (parent, _, { dataSources }) => {
      return dataSources.postAPI.getPostsByAuthor(parent.id);
    }
  }
};

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

app.use('/graphql', express.json(), expressMiddleware(server, {
  context: async ({ req }) => ({
    user: req.user,
    dataSources: {
      userAPI: new UserAPI(),
      postAPI: new PostAPI()
    }
  })
}));
javascript
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const { readFileSync } = require('fs');

const typeDefs = readFileSync('./schema.graphql', 'utf-8');

const resolvers = {
  Query: {
    user: async (_, { id }, { dataSources }) => {
      return dataSources.userAPI.getUser(id);
    },
    users: async (_, { limit = 10, offset = 0 }, { dataSources }) => {
      return dataSources.userAPI.getUsers(limit, offset);
    }
  },
  Mutation: {
    createUser: async (_, { input }, { dataSources }) => {
      return dataSources.userAPI.createUser(input);
    }
  },
  User: {
    posts: async (parent, _, { dataSources }) => {
      return dataSources.postAPI.getPostsByAuthor(parent.id);
    }
  }
};

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

app.use('/graphql', express.json(), expressMiddleware(server, {
  context: async ({ req }) => ({
    user: req.user,
    dataSources: {
      userAPI: new UserAPI(),
      postAPI: new PostAPI()
    }
  })
}));

Learning Path

学习路径

Beginner (2-3 weeks)

入门阶段(2-3周)

  • ✅ Understand GraphQL concepts
  • ✅ Define schemas with SDL
  • ✅ Write basic resolvers
  • ✅ Query and mutation basics
  • ✅ 理解GraphQL核心概念
  • ✅ 使用SDL定义Schema
  • ✅ 编写基础解析器
  • ✅ 查询与变更基础

Intermediate (4-6 weeks)

进阶阶段(4-6周)

  • ✅ Field resolvers and relationships
  • ✅ Input validation
  • ✅ Error handling
  • ✅ DataLoader for N+1
  • ✅ 字段解析器与关联关系
  • ✅ 输入验证
  • ✅ 错误处理
  • ✅ 使用DataLoader解决N+1问题

Advanced (8-10 weeks)

高级阶段(8-10周)

  • ✅ Subscriptions (real-time)
  • ✅ Authentication and authorization
  • ✅ Federation for microservices
  • ✅ Performance optimization
  • ✅ 实时订阅(Subscriptions)
  • ✅ 身份验证与授权
  • ✅ 微服务联邦(Federation)
  • ✅ 性能优化

DataLoader for N+1 Problem

使用DataLoader解决N+1查询问题

javascript
const DataLoader = require('dataloader');

const createUserLoader = () =>
  new DataLoader(async (userIds) => {
    const users = await User.find({ _id: { $in: userIds } });
    const userMap = new Map(users.map(u => [u.id, u]));
    return userIds.map(id => userMap.get(id));
  });

// Use in resolver
User: {
  posts: (parent, _, { loaders }) => {
    return loaders.userLoader.load(parent.authorId);
  }
}
javascript
const DataLoader = require('dataloader');

const createUserLoader = () =>
  new DataLoader(async (userIds) => {
    const users = await User.find({ _id: { $in: userIds } });
    const userMap = new Map(users.map(u => [u.id, u]));
    return userIds.map(id => userMap.get(id));
  });

// Use in resolver
User: {
  posts: (parent, _, { loaders }) => {
    return loaders.userLoader.load(parent.authorId);
  }
}

Authentication & Authorization

身份验证与授权

javascript
const { shield, rule, allow } = require('graphql-shield');

const isAuthenticated = rule()((parent, args, { user }) => {
  return user !== null;
});

const isAdmin = rule()((parent, args, { user }) => {
  return user?.role === 'admin';
});

const permissions = shield({
  Query: { '*': allow, users: isAuthenticated },
  Mutation: {
    createPost: isAuthenticated,
    deleteUser: isAdmin
  }
});
javascript
const { shield, rule, allow } = require('graphql-shield');

const isAuthenticated = rule()((parent, args, { user }) => {
  return user !== null;
});

const isAdmin = rule()((parent, args, { user }) => {
  return user?.role === 'admin';
});

const permissions = shield({
  Query: { '*': allow, users: isAuthenticated },
  Mutation: {
    createPost: isAuthenticated,
    deleteUser: isAdmin
  }
});

Error Handling

错误处理

javascript
const { ApolloError, UserInputError } = require('apollo-server-express');

class NotFoundError extends ApolloError {
  constructor(message) {
    super(message, 'NOT_FOUND');
  }
}

// In resolvers
const resolvers = {
  Query: {
    user: async (_, { id }) => {
      const user = await User.findById(id);
      if (!user) throw new NotFoundError(`User ${id} not found`);
      return user;
    }
  }
};
javascript
const { ApolloError, UserInputError } = require('apollo-server-express');

class NotFoundError extends ApolloError {
  constructor(message) {
    super(message, 'NOT_FOUND');
  }
}

// In resolvers
const resolvers = {
  Query: {
    user: async (_, { id }) => {
      const user = await User.findById(id);
      if (!user) throw new NotFoundError(`User ${id} not found`);
      return user;
    }
  }
};

Unit Test Template

单元测试模板

javascript
const { createTestClient } = require('apollo-server-testing');

describe('GraphQL API', () => {
  let query, mutate;

  beforeAll(() => {
    const server = new ApolloServer({ typeDefs, resolvers });
    const testClient = createTestClient(server);
    query = testClient.query;
    mutate = testClient.mutate;
  });

  it('should return users list', async () => {
    const GET_USERS = `query { users { id name } }`;
    const res = await query({ query: GET_USERS });

    expect(res.errors).toBeUndefined();
    expect(res.data.users).toBeDefined();
  });
});
javascript
const { createTestClient } = require('apollo-server-testing');

describe('GraphQL API', () => {
  let query, mutate;

  beforeAll(() => {
    const server = new ApolloServer({ typeDefs, resolvers });
    const testClient = createTestClient(server);
    query = testClient.query;
    mutate = testClient.mutate;
  });

  it('should return users list', async () => {
    const GET_USERS = `query { users { id name } }`;
    const res = await query({ query: GET_USERS });

    expect(res.errors).toBeUndefined();
    expect(res.data.users).toBeDefined();
  });
});

Troubleshooting

常见问题排查

ProblemCauseSolution
N+1 query issueField resolversUse DataLoader
Slow queriesNo cachingImplement Apollo Cache
Type errorsSchema mismatchValidate with codegen
Auth bypassMissing guardsUse graphql-shield
问题原因解决方案
N+1查询问题字段解析器设计使用DataLoader
查询缓慢未启用缓存实现Apollo缓存
类型错误Schema不匹配使用代码生成工具验证
授权绕过缺少防护机制使用graphql-shield

When to Use

适用场景

Use GraphQL when:
  • Clients need flexible data fetching
  • Multiple clients with different data needs
  • Avoiding over-fetching/under-fetching
  • Real-time updates needed
  • API evolution without versioning
在以下场景选择GraphQL:
  • 客户端需要灵活的数据获取方式
  • 存在多个数据需求不同的客户端
  • 避免过度获取/获取不足数据
  • 需要实时更新功能
  • API演进无需版本迭代

Related Skills

相关技能

  • Express REST API (alternative approach)
  • Database Integration (data sources)
  • JWT Authentication (securing API)
  • Express REST API(替代方案)
  • 数据库集成(数据源)
  • JWT身份验证(API安全)

Resources

参考资源