GraphQL基础
本教程共 47 篇 · 第 37 篇 · 更新于 2026-08-09 · 约 13 分钟阅读
本节目标:在 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是类型映射函数。string和boolean可以省略,但number必须指明是Int还是Floatnullable: 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,再调 @ResolveField 的 posts 方法,把 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 自动继承了 offset 和 limit 字段。
GraphQL Playground
启动应用后,打开浏览器访问 http://localhost:3000/graphql,你会看到 GraphQL Playground——一个交互式的 GraphQL IDE。
你可以在里面写查询、mutation,实时看结果。调试接口非常方便。
NoteApollo 默认的 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 提供交互式调试界面