Using GraphQL in NestJS

  • 1193Words
  • 6Minutes
  • 12 Jul, 2024

GraphQL is a specification for an API query language and server-side runtime that can meet clients’ exact data needs. This article will introduce the basic concepts of GraphQL and explain how to integrate and use GraphQL in NestJS, including schema definition, complex queries, and mutations.

Basic Concepts of GraphQL

GraphQL is a query language for APIs developed by Facebook. It provides a flexible and efficient alternative to REST APIs. GraphQL allows clients to request the exact data they need, no more, no less, and retrieve data from multiple resources in a single request.

The main concepts include:

  • Schema: Defines all data types available for query in the API and their relationships.
  • Query: A request to read data.
  • Mutation: A request to modify data.
  • Resolver: Functions that handle query and mutation requests.

GraphQL Module in NestJS

NestJS provides a powerful GraphQL module supporting both code-first and schema-first approaches to define GraphQL schemas.

Using GraphQL in NestJS

Project Directory Structure

When using GraphQL, we need to create specific files and directories to organize the code:

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

Installation and Configuration

  1. Install the necessary packages:
Terminal window
1
npm install @nestjs/graphql @nestjs/apollo graphql apollo-server-express @nestjs/typeorm typeorm mysql2
  1. Configure the GraphQL module and database connection:

In src/app.module.ts, configure the GraphQL module and TypeORM module:

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"), // Automatically generate schema.gql file
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 vs. Schema-First

Code-First

In the code-first approach, the schema is defined using decorators, and the GraphQL schema file is automatically generated during compilation. This approach allows developers to define and manage the schema directly through code.

Example:

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

In the schema-first approach, the schema file is manually written, and types and resolvers are generated by parsing the schema file. This approach allows developers to directly edit and manage the GraphQL schema file.

Example:

First, write the schema file:

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
}

Then, configure NestJS to use this schema file:

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

Define Schema

In the code-first approach, we have already defined the schema using decorators.

Create and Use Resolver

Create a resolver in src/user/user.resolver.ts to handle queries and mutations:

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
}

Implement Complex Queries

Implement complex queries in 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
}

Integration with Frontend

Configure Frontend Environment

In the frontend project, Apollo Client is usually used to interact with the GraphQL API. First, we need to install the Apollo Client dependencies:

Terminal window
1
npm install @apollo/client graphql

Making GraphQL Requests

Configure Apollo Client in the frontend project:

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;

Handling Queries and Mutations

Use Apollo Client to make queries and mutations:

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;

Handling mutation requests:

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;

Use these components in the frontend application:

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;

Summary

This article introduced the basic concepts of GraphQL and explained how to integrate and use GraphQL in a NestJS project. By configuring the GraphQL module, defining the schema, creating and implementing queries and mutations, and implementing complex queries, developers can leverage the powerful features of GraphQL to build efficient and flexible APIs. Finally, we demonstrated how to use Apollo Client in a frontend project to interact with the GraphQL API, make queries and mutations, and handle the returned data. Through these examples, we can create our own GraphQL API in NestJS and seamlessly integrate it with frontend projects to meet various business needs.