首页 / NestJS 入门教程 / GraphQL基础

NestJS 入门教程

GraphQL基础

本教程共 47 篇 · 第 37 篇 · 更新于 2026-08-09 · 约 13 分钟阅读

NestJSGraphQLApolloResolverObjectTypeQuery

本节目标:在 NestJS 中搭建 GraphQL 服务,理解 code first 和 schema first 两种模式,学会写解析器。

什么是 GraphQL

REST API 有个经典问题:前端要什么字段,你控制不了。接口返回一堆数据,前端可能只用其中两个字段。列表接口更夸张——有的页面要显示作者信息,有的不需要,但你只能返回固定结构。

GraphQL 就是来解决这类问题的。客户端自己描述想要什么数据,服务端就返回什么。不多不少。

打个比方:REST 像去餐厅点套餐,厨师决定给你什么。GraphQL 像自助餐,你自己选要什么菜。

安装

NestJS 支持两种 GraphQL 引擎:Apollo(主流选择)和 Mercurius(Fastify 专用)。

以 Apollo + Express 为例:

npm install @nestjs/graphql @nestjs/apollo @apollo/server @as-integrations/express5 graphql

然后在根模块里配置:

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: true, // 自动生成 schema
    }),
  ],
})
export class AppModule {}

autoSchemaFile: true 表示在内存中自动生成 schema。你也可以传一个文件路径,让它写到磁盘上:

autoSchemaFile: join(process.cwd(), 'src/schema.gql'),

两种开发模式

NestJS 的 GraphQL 支持两种模式:

Code First(代码优先):用 TypeScript 类 + 装饰器定义数据模型,框架自动生成 GraphQL schema。适合 TypeScript 重度用户,所有定义都在代码里。

Schema First(Schema 优先):先手写 .graphql 的 SDL 文件定义 schema,框架自动生成对应的 TypeScript 类型。适合前后端先对齐接口再开发的团队。

本教程以 Code First 为主,因为它跟 NestJS 的装饰器风格最搭。

定义数据模型(ObjectType)

@ObjectType() 装饰器定义 GraphQL 的对象类型:

import { Field, Int, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class Author {
  @Field(() => Int)
  id: number;

  @Field({ nullable: true })
  firstName?: string;

  @Field({ nullable: true })
  lastName?: string;

  @Field(() => [Post])
  posts: Post[];
}

几个要点:

  • @Field() 标记哪些属性暴露给 GraphQL
  • () => Int 是类型映射函数。stringboolean 可以省略,但 number 必须指明是 Int 还是 Float
  • nullable: true 表示该字段可以为空,默认都是必填的
  • 数组类型必须写 () => [Post]

再看 Post 的定义:

@ObjectType()
export class Post {
  @Field(() => Int)
  id: number;

  @Field()
  title: string;

  @Field(() => Int, { nullable: true })
  votes?: number;
}

这些装饰器会自动生成对应的 SDL:

type Author {
  id: Int!
  firstName: String
  lastName: String
  posts: [Post!]!
}

type Post {
  id: Int!
  title: String!
  votes: Int
}

写解析器(Resolver)

数据模型定义好了,接下来要告诉 GraphQL 怎么获取这些数据。这就是解析器的职责。

import { Resolver, Query, Args, Int } from '@nestjs/graphql';

@Resolver(() => Author)
export class AuthorsResolver {
  constructor(private authorsService: AuthorsService) {}

  @Query(() => Author)
  async author(@Args('id', { type: () => Int }) id: number) {
    return this.authorsService.findOneById(id);
  }

  @Query(() => [Author])
  async authors() {
    return this.authorsService.findAll();
  }
}

@Query() 标记这是一个查询入口。方法名就是 GraphQL 查询的字段名——author 方法对应 author(id: Int!): Author 查询。

如果你想让方法名和查询名不一样:

@Query(() => Author, { name: 'author' })
async getAuthor(@Args('id', { type: () => Int }) id: number) {
  return this.authorsService.findOneById(id);
}

name: 'author' 指定了 GraphQL schema 里的字段名,方法本身可以叫任何名字。

字段解析器(ResolveField)

有时候一个字段的数据不是直接查出来的,而是需要根据父对象去二次查询。比如查 Author 的时候,posts 字段需要根据 author 的 id 去查文章列表。

@Resolver(() => Author)
export class AuthorsResolver {
  constructor(
    private authorsService: AuthorsService,
    private postsService: PostsService,
  ) {}

  @Query(() => Author)
  async author(@Args('id', { type: () => Int }) id: number) {
    return this.authorsService.findOneById(id);
  }

  @ResolveField()
  async posts(@Parent() author: Author) {
    const { id } = author;
    return this.postsService.findAll({ authorId: id });
  }
}

@Parent() 注入的是父级对象的值。当客户端查询 author { posts { title } } 时,NestJS 先调 @Query 拿到 Author,再调 @ResolveFieldposts 方法,把 Author 对象传进来。

Tip

@Resolver(() => Author) 里的类型参数在有 @ResolveField 时是必填的,因为它告诉框架这些字段解析器属于哪个父类型。

变更操作(Mutation)

查询是读数据,变更是写数据。定义 Mutation 跟 Query 类似:

import { Mutation, Args } from '@nestjs/graphql';

@Resolver(() => Author)
export class AuthorsResolver {
  @Mutation(() => Author)
  async createAuthor(
    @Args('name') name: string,
  ) {
    return this.authorsService.create({ name });
  }
}

客户端这样调用:

mutation {
  createAuthor(name: "张三") {
    id
    name
  }
}

参数处理

内联参数

简单的参数直接用 @Args

@Query(() => Author)
async author(
  @Args('id', { type: () => Int }) id: number,
) {
  return this.authorsService.findOneById(id);
}

多个参数就写多个 @Args

@Query(() => [Author])
async authors(
  @Args('firstName', { nullable: true }) firstName?: string,
  @Args('lastName', { defaultValue: '' }) lastName?: string,
) {
  return this.authorsService.find({ firstName, lastName });
}

参数类

参数多了可以封装成一个类:

import { ArgsType, Field, Int } from '@nestjs/graphql';

@ArgsType()
class GetAuthorsArgs {
  @Field({ nullable: true })
  firstName?: string;

  @Field({ defaultValue: '' })
  lastName: string;

  @Field(() => Int, { defaultValue: 10 })
  limit: number;
}

在解析器里直接注入整个参数对象:

@Query(() => [Author])
async authors(@Args() args: GetAuthorsArgs) {
  return this.authorsService.find(args);
}
Note

@ArgsType() 类跟 ValidationPipe 配合使用,可以直接用 class-validator 的装饰器做参数校验。

类继承

ObjectType 和 ArgsType 都支持继承。比如定义通用的分页参数:

@ArgsType()
class PaginationArgs {
  @Field(() => Int)
  offset: number = 0;

  @Field(() => Int)
  limit: number = 10;
}

@ArgsType()
class GetAuthorsArgs extends PaginationArgs {
  @Field({ nullable: true })
  name?: string;
}

GetAuthorsArgs 自动继承了 offsetlimit 字段。

GraphQL Playground

启动应用后,打开浏览器访问 http://localhost:3000/graphql,你会看到 GraphQL Playground——一个交互式的 GraphQL IDE。

你可以在里面写查询、mutation,实时看结果。调试接口非常方便。

Note

Apollo 默认的 Playground 已被标记为废弃,推荐用 GraphiQL 代替:

GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  autoSchemaFile: true,
  graphiql: true,
}),

模块组织

解析器就是一个普通的 provider,注册到模块里就行:

@Module({
  imports: [PostsModule],
  providers: [AuthorsService, AuthorsResolver],
})
export class AuthorsModule {}

建议按领域模型组织——模型、解析器、服务放在同一个模块目录下。跟 REST 控制器的组织方式类似。

小结

  • 安装 @nestjs/graphql + @nestjs/apollo + graphql 等包
  • Code First 用装饰器定义模型,自动生成 schema
  • @ObjectType() 定义数据类型,@Field() 标记字段
  • @Query() 定义查询入口,@Mutation() 定义变更操作
  • @ResolveField() + @Parent() 处理嵌套字段的二次查询
  • @ArgsType() 封装复杂参数,支持继承和校验
  • GraphQL Playground / GraphiQL 提供交互式调试界面