NestJSでGraphQLを使用する

GraphQL は、API のクエリ言語とサーバーサイドランタイムの仕様であり、クライアントがデータを正確に要求することを可能にします。この記事では、GraphQL の基本概念を紹介し、NestJS で GraphQL を統合および使用する方法について、Schema の定義、複雑なクエリ、およびミューテーションの実装を含めて説明します。

GraphQL の基本概念

GraphQL は Facebook によって開発された API 用のクエリ言語であり、REST API の柔軟かつ効率的な代替手段を提供します。GraphQL は、クライアントが必要とする正確なデータを要求できるようにし、複数のリソースからデータを取得するために必要なリクエストの数を減らすことができます。

主な概念には以下が含まれます:

  • Schema:API でクエリ可能なすべてのデータタイプとその関係を定義します。
  • Query:データを読み取るリクエストです。
  • Mutation:データを変更するリクエストです。
  • Resolver:クエリとミューテーションのリクエストを処理する関数です。

NestJS の GraphQL モジュール

NestJS は、コードファースト(code-first)およびスキーマファースト(schema-first)の両方の方法を使用して GraphQL スキーマを定義できる強力な GraphQL モジュールを提供しています。

NestJS での GraphQL の使用

プロジェクトディレクトリ構造

GraphQL を使用する際には、コードを整理するために特定のファイルとディレクトリを作成する必要があります:

1
src/
2
├── app.module.ts
3
├── main.ts
4
├── user/
5
│ ├── user.model.ts
6
│ ├── user.input.ts
7
│ ├── user.resolver.ts
8
│ ├── user.service.ts

インストールと設定

  1. 必要なパッケージをインストール:
Terminal window
1
npm install @nestjs/graphql @nestjs/apollo graphql apollo-server-express @nestjs/typeorm typeorm mysql2
  1. GraphQL モジュールとデータベース接続を設定:

src/app.module.ts で GraphQL モジュールと TypeORM モジュールを設定します:

1
import { Module } from "@nestjs/common";
2
import { GraphQLModule } from "@nestjs/graphql";
3
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
4
import { TypeOrmModule } from "@nestjs/typeorm";
5
import { join } from "path";
6
import { UserModule } from "./user/user.module";
7
import { User } from "./user/user.model";
8
9
@Module({
10
imports: [
11
GraphQLModule.forRoot<ApolloDriverConfig>({
12
driver: ApolloDriver,
13
autoSchemaFile: join(process.cwd(), "src/schema.gql"), // スキーマファイルを自動生成
14
}),
15
TypeOrmModule.forRoot({
16
type: "mysql",
17
host: "localhost",
18
port: 3306,
19
username: "root",
20
password: "password",
21
database: "test",
22
entities: [User],
23
synchronize: true,
24
}),
25
UserModule,
26
],
27
})
28
export class AppModule {}

コードファーストとスキーマファースト

コードファースト(Code-First)

コードファーストは、デコレーターを使用してスキーマを定義し、コンパイル時に自動的に GraphQL スキーマファイルを生成する方法です。この方法では、開発者はコードを通じて直接スキーマを定義および管理できます。

例:

src/user/user.model.ts
1
import { ObjectType, Field, Int } from "@nestjs/graphql";
2
import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";
3
4
@Entity()
5
@ObjectType()
6
export class User {
7
@PrimaryGeneratedColumn()
8
@Field(() => Int)
9
id: number;
10
11
@Column()
12
@Field()
13
name: string;
14
15
@Column()
16
@Field()
17
email: string;
18
}
スキーマファースト(Schema-First)

スキーマファーストは、手動でスキーマファイルを作成し、それを解析してタイプとリゾルバーを生成する方法です。この方法では、開発者は直接 GraphQL スキーマファイルを編集および管理できます。

例:

まず、スキーマファイルを作成します:

src/user/user.schema.graphql
1
type User {
2
id: Int!
3
name: String!
4
email: String!
5
}
6
7
type Query {
8
getUsers: [User]
9
}
10
11
type Mutation {
12
createUser(name: String!, email: String!): User
13
}

次に、NestJS がこのスキーマファイルを使用するように設定します:

src/app.module.ts
1
import { Module } from "@nestjs/common";
2
import { GraphQLModule } from "@nestjs/graphql";
3
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
4
import { join } from "path";
5
import { UserModule } from "./user/user.module";
6
7
@Module({
8
imports: [
9
GraphQLModule.forRoot<ApolloDriverConfig>({
10
driver: ApolloDriver,
11
typePaths: ["./**/*.graphql"],
12
}),
13
UserModule,
14
],
15
})
16
export class AppModule {}

スキーマの定義

コードファーストモードでは、デコレーターを使用して既にスキーマを定義しました。

Resolver の作成と使用

src/user/user.resolver.ts で、クエリとミューテーションを処理する Resolver を作成します:

1
import { Resolver, Query, Mutation, Args } from "@nestjs/graphql";
2
import { User } from "./user.model";
3
import { CreateUserInput } from "./user.input";
4
import { UserService } from "./user.service";
5
6
@Resolver(() => User)
7
export class UserResolver {
8
constructor(private readonly userService: UserService) {}
9
10
@Query(() => [User])
11
async getUsers(): Promise<User[]> {
12
return this.userService.getUsers();
13
}
14
15
@Mutation(() => User)
16
async createUser(@Args("input") input: CreateUserInput): Promise<User> {
17
return this.userService.createUser(input);
18
}
19
}

複雑なクエリの実装

src/user/user.resolver.ts で、複雑なクエリを実装します:

1
import { Resolver, Query, Args, Int } from "@nestjs/graphql";
2
import { User } from "./user.model";
3
import { UserService } from "./user.service";
4
5
@Resolver(() => User)
6
export class UserResolver {
7
constructor(private readonly userService: UserService) {}
8
9
@Query(() => [User])
10
async getUsers(
11
@Args("page", { type: () => Int, nullable: true }) page: number = 1,
12
@Args("limit", { type: () => Int, nullable: true }) limit: number = 10,
13
): Promise<User[]> {
14
return this.userService.getUsersWithPagination(page, limit);
15
}
16
17
@Query(() => [User])
18
async searchUsers(
19
@Args("keyword", { type: () => String }) keyword: string,
20
): Promise<User[]> {
21
return this.userService.searchUsers(keyword);
22
}
23
}
24
25
// src/user/user.service.ts
26
import { Injectable } from "@nestjs/common";
27
import { InjectRepository } from "@nestjs/typeorm";
28
import { Repository } from "typeorm";
29
import { User } from "./user.model";
30
import { CreateUserInput } from "./user.input";
31
32
@Injectable()
33
export class UserService {
34
constructor(
35
@InjectRepository(User)
36
private usersRepository: Repository<User>,
37
) {}
38
39
async getUsers(): Promise<User[]> {
40
return this.usersRepository.find();
41
}
42
43
async createUser(input: CreateUserInput): Promise<User> {
44
const user = this.usersRepository.create(input);
45
return this.usersRepository.save(user);
46
}
47
48
async getUsersWithPagination(page: number, limit: number): Promise<User[]> {
49
const [result] = await this.usersRepository.findAndCount({
50
skip: (page - 1) * limit,
51
take: limit,
52
});
53
return result;
54
}
55
56
async searchUsers(keyword: string): Promise<User[]> {
57
return this.usersRepository
58
.createQueryBuilder("user")
59
.where("user.name LIKE :keyword", { keyword: `%${keyword}%` })
60
.orWhere("user.email LIKE :keyword", { keyword: `%${keyword}%` })
61
.getMany();
62
}
63
}

フロントエンドとの連携

フロントエンド環境の設定

フロントエンドプロジェクトでは、通常 Apollo Client を使用して GraphQL API とやり取りします。まず、Apollo Client 関連の依存関係をインストールします:

Terminal window
1
npm install @apollo/client graphql

GraphQL リクエストの送信

フロントエンドプロジェクトで Apollo Client を設定します:

src/apollo-client.js
1
import { ApolloClient, InMemoryCache } from "@apollo/client";
2
3
const client = new ApolloClient({
4
uri: "http://localhost:3000/graphql",
5
cache: new InMemoryCache(),
6
});
7
8
export default client;

クエリとミューテーションの処理

Apollo Client を使用してクエリとミューテーションを送信します:

src/components/Users.js
1
import React from "react";
2
import { useQuery, gql } from "@apollo/client";
3
4
const GET_USERS = gql`
5
query GetUsers {
6
getUsers {
7
id
8
name
9
email
10
}
11
}
12
`;
13
14
function Users() {
15
const { loading, error, data } = useQuery(GET_USERS);
16
17
if (loading) return <p>Loading...</p>;
18
if (error) return <p>Error :(</p>;
19
20
return data.getUsers.map(({ id, name, email }) => (
21
<div key={id}>
22
<p>
23
{name}: {email}
24
</p>
25
</div>
26
));
27
}
28
29
export default Users;

ミューテーションの処理:

src/components/AddUser.js
1
import React, { useState } from "react";
2
import { useMutation, gql } from "@apollo/client";
3
4
const CREATE_USER = gql`
5
mutation CreateUser($name: String!, $email: String!) {
6
createUser(input: { name: $name, email: $email }) {
7
id
8
name
9
email
10
}
11
}
12
`;
13
14
function AddUser() {
15
const [name, setName] = useState("");
16
const [email, setEmail] = useState("");
17
const [createUser, { data, loading, error }] = useMutation(CREATE_USER);
18
19
const handleSubmit = (e) => {
20
e.preventDefault();
21
createUser({ variables: { name, email } });
22
setName("");
23
setEmail("");
24
};
25
26
return (
27
<div>
28
<form onSubmit={handleSubmit}>
29
<input
30
value={name}
31
onChange={(e) => setName(e.target.value)}
32
placeholder="Name"
33
/>
34
<input
35
value={email}
36
onChange={(e) => setEmail(e.target.value)}
37
placeholder="Email"
38
/>
39
<button type="submit">Add User</button>
40
</form>
41
{loading && <p>Loading...</p>}
42
{error && <p>Error :(</p>}
43
{data && <p>User {data.createUser.name} created!</p>}
44
</div>
45
);
46
}
47
48
export default AddUser;

フロントエンドアプリケーションでこれらのコンポーネントを使用します:

src/App.js
1
import React from "react";
2
import { ApolloProvider } from "@apollo/client";
3
import client from "./apollo-client";
4
import Users from "./components/Users";
5
import AddUser from "./components/AddUser";
6
7
function App() {
8
return (
9
<ApolloProvider client={client}>
10
<div>
11
<h2>My first Apollo app 🚀</h2>
12
<AddUser />
13
<Users />
14
</div>
15
</ApolloProvider>
16
);
17
}
18
19
export default App;

まとめ

この記事では、GraphQL の基本概念と、NestJS プロジェクトで GraphQL を統合および使用する方法について説明しました。GraphQL モジュールの設定、スキーマの定義、クエリとミューテーションの作成および実装、複雑なクエリの処理方法を通じて、開発者が GraphQL の強力な機能を最大限に活用して効率的で柔軟な API を構築できるようにします。最後に、Apollo Client を使用してフロントエンドプロジェクトで GraphQL API とやり取りする方法を示しました。これらの例を通じて、NestJS で独自の GraphQL API を作成し、さまざまなビジネスニーズに対応するフロントエンドプロジェクトとシームレスに統合できます。