Як побудувати GraphQL API з NestJS?
GraphQL у NestJS
NestJS надає першокласну підтримку GraphQL через пакет @nestjs/graphql, підтримуючи як code-first, так і schema-first підходи.
Налаштування (Code-First)
bash
npm install @nestjs/graphql @nestjs/apollo @apollo/server graphqltypescript
// app.module.ts
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: 'schema.gql', // Автоматичне генерування схеми
sortSchema: true,
playground: true,
}),
],
})
export class AppModule {}Об'єктні типи
typescript
import { ObjectType, Field, ID, Int } from '@nestjs/graphql';
@ObjectType()
export class User {
@Field(() => ID)
id: string;
@Field()
name: string;
@Field()
email: string;
@Field(() => Int)
age: number;
@Field(() => [Post], { nullable: true })
posts?: Post[];
@Field()
createdAt: Date;
}Вхідні типи
typescript
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class CreateUserInput {
@Field()
@IsString()
name: string;
@Field()
@IsEmail()
email: string;
@Field(() => Int)
@Min(18)
age: number;
}
@InputType()
export class UpdateUserInput extends PartialType(CreateUserInput) {}Розв'язувачі (як Контролери)
typescript
import { Resolver, Query, Mutation, Args, ResolveField, Parent } from '@nestjs/graphql';
@Resolver(() => User)
export class UsersResolver {
constructor(
private usersService: UsersService,
private postsService: PostsService,
) {}
@Query(() => [User], { name: 'users' })
findAll() {
return this.usersService.findAll();
}
@Query(() => User, { name: 'user' })
findOne(@Args('id', { type: () => ID }) id: string) {
return this.usersService.findOne(id);
}
@Mutation(() => User)
createUser(@Args('input') input: CreateUserInput) {
return this.usersService.create(input);
}
@Mutation(() => User)
updateUser(
@Args('id', { type: () => ID }) id: string,
@Args('input') input: UpdateUserInput,
) {
return this.usersService.update(id, input);
}
@Mutation(() => Boolean)
deleteUser(@Args('id', { type: () => ID }) id: string) {
return this.usersService.delete(id);
}
// Вирішення вкладеного поля
@ResolveField(() => [Post])
posts(@Parent() user: User) {
return this.postsService.findByUserId(user.id);
}
}Аутентифікація в GraphQL
typescript
@Resolver(() => User)
export class UsersResolver {
@Query(() => User)
@UseGuards(GqlAuthGuard)
me(@CurrentUser() user: User) {
return user;
}
}
// GQL Auth Guard
@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}Підписки (Реальний час)
typescript
@Resolver(() => Message)
export class MessagesResolver {
@Subscription(() => Message, {
filter: (payload, variables) =>
payload.messageAdded.roomId === variables.roomId,
})
messageAdded(@Args('roomId') roomId: string) {
return pubSub.asyncIterator('messageAdded');
}
@Mutation(() => Message)
async sendMessage(@Args('input') input: SendMessageInput) {
const message = await this.messagesService.create(input);
pubSub.publish('messageAdded', { messageAdded: message });
return message;
}
}DataLoader (Проблема N+1)
typescript
import DataLoader from 'dataloader';
@Injectable({ scope: Scope.REQUEST })
export class PostsLoader {
constructor(private postsService: PostsService) {}
readonly batchPosts = new DataLoader<string, Post[]>(
async (userIds: string[]) => {
const posts = await this.postsService.findByUserIds([...userIds]);
return userIds.map((id) => posts.filter((p) => p.userId === id));
},
);
}
// У розв'язувачі
@ResolveField(() => [Post])
posts(@Parent() user: User) {
return this.postsLoader.batchPosts.load(user.id);
}GraphQL проти REST
| Особливість | REST | GraphQL |
|---|---|---|
| Отримання | Багато кінцевих точок | Одна кінцева точка |
| Перевантаження | Поширене | Клієнт вказує поля |
| Недовантаження | Багато запитів | Один запит |
| Версії | На основі URL (v1, v2) | Еволюція схеми |
| Кешування | HTTP кешування | Складніше |
| Крива навчання | Нижча | Вища |
Порада: Використовуйте GraphQL, коли вашому фронтенду потрібне гнучке отримання даних або коли у вас є кілька клієнтів (веб, мобільні) з різними вимогами до даних. Використовуйте REST для простіших API або коли важливе HTTP кешування.
Коротка відповідь
Для співбесідиPremium
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.