在NestJS中使用GraphQL

GraphQL 是一種用於 API 查詢語言和伺服器端運行時的規範,它能夠滿足客戶端對資料的準確需求。本文將介紹 GraphQL 的基本概念,並講解如何在 NestJS 中集成和使用 GraphQL,包括 Schema 的定義、複雜查詢和變更的實現。

GraphQL 的基本概念

GraphQL 是由 Facebook 開發的一種用於 API 的查詢語言,它提供了一個靈活和高效的替代方案來替代 REST API。GraphQL 允許客戶端請求它們需要的確切資料,不多也不少,並且從多個資源中獲取資料只需要一個請求。

主要概念包括:

  • Schema:定義 API 中可以查詢的所有資料類型和它們之間的關係。
  • Query:讀取資料的請求。
  • Mutation:修改資料的請求。
  • Resolver:處理查詢和變更請求的函數。

NestJS 中的 GraphQL 模組

NestJS 提供了強大的 GraphQL 模組,支持使用代碼優先(code-first)和模式優先(schema-first)兩種方法來定義 GraphQL Schema。

在 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"), // 自動生成 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)

代碼優先是通過裝飾器定義 Schema,在編譯時自動生成 GraphQL Schema 文件。這種方法使得開發者可以通過代碼直接定義和管理 Schema。

示例:

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)

模式優先是手動編寫 Schema 文件,然後通過解析 Schema 文件生成類型和解析器。這種方法使得開發者可以直接編輯和管理 GraphQL Schema 文件。

示例:

首先編寫 Schema 文件:

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 使用這個 Schema 文件:

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 {}

定義 Schema

在代碼優先模式下,我們已經通過裝飾器定義了 Schema。

創建和使用 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 模組,定義 Schema,創建和實現查詢與變更,以及實現複雜查詢,開發者可以充分利用 GraphQL 的強大功能來構建高效靈活的 API。最後,我們還展示了如何在前端專案中使用 Apollo Client 與 GraphQL API 進行互動,發起查詢和變更請求,並處理返回的資料。通過這些示例,我們可以在 NestJS 中創建自己的 GraphQL API,並與前端專案無縫集成,滿足不同的業務需求。