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:
1src/2├── app.module.ts3├── main.ts4├── user/5│ ├── user.model.ts6│ ├── user.input.ts7│ ├── user.resolver.ts8│ ├── user.service.ts
Installation and Configuration
- Install the necessary packages:
1npm install @nestjs/graphql @nestjs/apollo graphql apollo-server-express @nestjs/typeorm typeorm mysql2
- Configure the GraphQL module and database connection:
In src/app.module.ts
, configure the GraphQL module and TypeORM module:
1import { Module } from "@nestjs/common";2import { GraphQLModule } from "@nestjs/graphql";3import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";4import { TypeOrmModule } from "@nestjs/typeorm";5import { join } from "path";6import { UserModule } from "./user/user.module";7import { 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 file14 }),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})28export 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:
1import { ObjectType, Field, Int } from "@nestjs/graphql";2import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";3
4@Entity()5@ObjectType()6export 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:
1type User {2 id: Int!3 name: String!4 email: String!5}6
7type Query {8 getUsers: [User]9}10
11type Mutation {12 createUser(name: String!, email: String!): User13}
Then, configure NestJS to use this schema file:
1import { Module } from "@nestjs/common";2import { GraphQLModule } from "@nestjs/graphql";3import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";4import { join } from "path";5import { 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})16export 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:
1import { Resolver, Query, Mutation, Args } from "@nestjs/graphql";2import { User } from "./user.model";3import { CreateUserInput } from "./user.input";4import { UserService } from "./user.service";5
6@Resolver(() => User)7export 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
:
1import { Resolver, Query, Args, Int } from "@nestjs/graphql";2import { User } from "./user.model";3import { UserService } from "./user.service";4
5@Resolver(() => User)6export 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.ts26import { Injectable } from "@nestjs/common";27import { InjectRepository } from "@nestjs/typeorm";28import { Repository } from "typeorm";29import { User } from "./user.model";30import { CreateUserInput } from "./user.input";31
32@Injectable()33export 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.usersRepository58 .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:
1npm install @apollo/client graphql
Making GraphQL Requests
Configure Apollo Client in the frontend project:
1import { ApolloClient, InMemoryCache } from "@apollo/client";2
3const client = new ApolloClient({4 uri: "http://localhost:3000/graphql",5 cache: new InMemoryCache(),6});7
8export default client;
Handling Queries and Mutations
Use Apollo Client to make queries and mutations:
1import React from "react";2import { useQuery, gql } from "@apollo/client";3
4const GET_USERS = gql`5 query GetUsers {6 getUsers {7 id8 name9 email10 }11 }12`;13
14function 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
29export default Users;
Handling mutation requests:
1import React, { useState } from "react";2import { useMutation, gql } from "@apollo/client";3
4const CREATE_USER = gql`5 mutation CreateUser($name: String!, $email: String!) {6 createUser(input: { name: $name, email: $email }) {7 id8 name9 email10 }11 }12`;13
14function 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 <input30 value={name}31 onChange={(e) => setName(e.target.value)}32 placeholder="Name"33 />34 <input35 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
48export default AddUser;
Use these components in the frontend application:
1import React from "react";2import { ApolloProvider } from "@apollo/client";3import client from "./apollo-client";4import Users from "./components/Users";5import AddUser from "./components/AddUser";6
7function 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
19export 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.