
Czym jest GraphQL i NestJS warto żeby wiedział każdy programista. Co powstanie z ich połączenia? Jak stworzyć aplikację serwerową przy użyciu GraphQL w frameworku NestJS?
GraphQL to standard tworzenia API zaprojektowany i stworzony przez programistów Facebook’a. Jest to sposób komunikacji z serwerem jako alternatywa REST’a. Dostarcza on jeden endpoint, który służy do zwracania lub modyfikowania danych.
NestJS to framework napisany w Node.js w celu szybkiego oraz wydajnego tworzenia aplikacji serwerowych. Stworzony został w 2018 roku przez naszego rodaka Kamila Myśliwca. Nest.js posiada zalety Javascript z pełnym wsparciem Typescript oraz dodatkowo łączy OOP – programowanie zorientowane obiektowo wraz z FP – programowaniem funkcyjnym.
W poniższym przykładzie użyjemy również bazy MySQL z użyciem TypeORM, aby zapisać wprowadzane dane.
W celu zapoznania się z działaniem tych elementów stworzymy moduł CRUD dla użytkowników, gdzie jeden użytkownik będzie miał imię, nazwisko oraz email. Dodatkowo dodamy odpowiednią walidację danych.
Gotowy? W takim razie zaczynajmy…
Od czego zacząć?
W celu rozpoczęcia potrzebne będzie oczywiście środowisko Node.js. Jeśli jeszcze go nie zainstalowałeś, to odsyłam Cię na oficjalną stronę w celu instalacji.
Rozpocząć projekt można na dwa sposoby. Pierwszym z nich jest instalacja Nest CLI, a następnie stworzenie nowego projektu poprzez wywołanie odpowiedniej komendy.
Drugi to sklonowanie plików z oficjalnego repozytorium i zainstalowanie bibliotek node. Obydwa sposoby doprowadzą do tego samego rezultatu. Ja skorzystam z pierwszej opcji.
Stworzenie projektu
Instalacja Nest CLI następuje poprzez wpisanie poniższej komendy:
npm i -g @nestjs/cli
Po zainstalowaniu zyskujemy dostęp do komendy nest. Jeśli po jej wpisaniu otrzymujesz poniższy wynik, to znaczy, że proces instalacji przebiegł pomyślnie.

Stworzenie projektu to tak naprawdę wpisanie poniższej komendy.
nest new graphql-article

Podczas tworzenia należy wybrać jakiego systemu do paczkowania chcemy używać – npm lub yarn. Ja użyłem npm’a. Jak już pewnie zauważyłeś „graphql-article” to tak naprawdę nazwa katalogu pod którym stworzył się projekt. Dostęp do wszystkich komend znajdziesz na oficjalnej stronie autora.
Następnie w celu przetestowania czy wszystko przebiegło pomyślnie, należy przejść do katalogu ze stworzonym projektem oraz wpisać komendę:
npm start

Przechodząc pod adres http://localhost:3000 zauważymy, jakże sławne Witaj Świecie!

Instalacja bibliotek
W celu stworzenia aplikacji z założonych wyżej wymagań potrzebne będą biblioteki, które nie są dołączone w bazowym projekcie. Należy uruchomić poniższą komendę w celu instalacji.
npm i –save @nestjs/graphql [email protected] [email protected]@[email protected]/typeorm typeorm mysqlclass-validator
Następnie należy skonfigurować następujące biblioteki. Uruchomiamy projekt w wybranym edytorze. Ja osobiście używam PHPStorm’a.
//app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import {GraphQLModule} from '@nestjs/graphql';
import {TypeOrmModule} from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type : 'mysql',
host : 'localhost',
port : 3306,
username : 'username',
password : 'password',
database : 'graphql-article',
entities : [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
charset : 'utf8mb4_unicode_ci',
logging : true
}),
GraphQLModule.forRoot({
debug : true,
playground : true,
autoSchemaFile: 'schema.gql'
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Wskazówka: Dobrą praktyką jest dodawanie wszystkich wrażliwych danych do zmiennych środowiskowych.
Należy również stworzyć bazę danych o nazwie „graphql-article”. Oczywiście dane do połączenia z bazą danych należy podać własne.
Stworzenie modułu użytkowników
W module użytkowników stworzymy serwis, resolver, repozytorium, encję oraz użytkownika graphql, który będzie odpowiadał za model danych. Dodatkowo stworzymy dwie klasy: schemat odpowiadający za pojedynczego użytkownika oraz schemat odpowiadający za manipulację rekordami listy. Obie te klasy odnoszą się do DTO – Data Transfer Object.
Tworzenie modułów w Nest.js można przeprowadzić poprzez ręczne stworzenie plików bądź poprzez wywołane komendy. Ja polecam ten drugi sposób. A więc zaczynajmy.
Moduł
nest generate module users

Encja
//users/user.entity.rs
import {Column, Entity, PrimaryGeneratedColumn} from 'typeorm';
@Entity()
export class UserEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({nullable: true})
firstName: string | null;
@Column({nullable: true})
lastName: string | null;
@Column({nullable: true, unique: true, length: 191})
email: string;
}
Repozytorium
//users.repository.ts
import {EntityRepository, Repository} from 'typeorm';
import {UserEntity} from './user.entity';
@EntityRepository(UserEntity)
export class UsersRepository extends Repository<UserEntity> {
}
Użytkownik graphql
//user.graphql.ts
import {Field, ObjectType} from '@nestjs/graphql';
@ObjectType()
export class UserGraphql {
@Field(type =>Number)
id: number;
@Field({nullable: true})
firstName: string | null;
@Field({ nullable: true })
lastName: string | null;
@Field()
email: string;
}
Schemat odpowiadający za pojedynczego użytkownika
//users/dto/user.input.ts
import {Field, InputType} from '@nestjs/graphql';
import {IsEmail, IsOptional, IsString} from 'class-validator';
@InputType()
export class UserInput {
@Field({ nullable: true })
@IsString()
@IsOptional()
firstName?: string;
@Field({ nullable: true })
@IsOptional()
@IsString()
lastName?: string;
@Field()
@IsEmail()
email: string;
}
Schemat odpowiadający za manipulację rekordami listy
//users/dto/users.args.ts
import {ArgsType, Field, Int} from '@nestjs/graphql';
import {Max, Min} from 'class-validator';
@ArgsType()
export class UsersArgs {
@Field(type =>Int, {defaultValue: 0, description: "Define how many records to skip"})
@Min(0)
skip: number = 0;
@Field(type =>Int, {defaultValue: 25, description: "Define how many record to take"})
@Min(1)
@Max(50)
take: number = 25;
}
Serwis
nest generate service users
Po wygenerowaniu serwisu implementujemy poniższe metody
//users/users.service.ts
import { Injectable } from '@nestjs/common';
import {UserEntity} from './user.entity';
import {UserInput} from './dto/user.input';
import {UsersRepository} from './users.repository';
@Injectable()
export class UsersService {
constructor(
private readonly usersRepository: UsersRepository,
) {
}
async create(userInput: UserInput): Promise<UserEntity>{
const anotherUserWithGivenMail = await this.usersRepository.findOne({email: userInput.email});
if(anotherUserWithGivenMail) {
throw new BadRequestException('Another user with given email already exists');
}
const user = new UserEntity();
this.fillUser(userInput, user);
return this.usersRepository.save(user);
}
async update(id: number, userInput: UserInput): Promise<UserEntity>{
const anotherUserWithGivenMail = await this.usersRepository.findOne({
where: {
id: Not(Equal(id)),
email: Equal(userInput.email)
}
});
if(anotherUserWithGivenMail) {
throw new BadRequestException('Another user with given email already exists');
}
const user = await this.usersRepository.findOneOrFail({id});
this.fillUser(userInput, user);
return this.usersRepository.save(user);
}
fillUser(userInput: UserInput, user: UserEntity) : void{
user.firstName = userInput.firstName;
user.lastName = userInput.lastName;
user.email = userInput.email;
}
}
Resolver
nest generate resolver users
Po wygenerowaniu resolver’a następuje implementacja poniższych metod
//users/users.resolver.ts
import {Args, Mutation, Query, Resolver} from '@nestjs/graphql';
import {UsersArgs} from './dto/users.args';
import {UserEntity} from './user.entity';
import {UserGraphql} from './user.graphql';
import {UsersService} from './users.service';
import {UserInput} from './dto/user.input';
import {UsersRepository} from './users.repository';
@Resolver('Users')
export class UsersResolver {
constructor(
private readonly usersService: UsersService,
private readonly usersRepository: UsersRepository
) {
}
@Query(returns => [UserGraphql], {
description: "Get all users"
})
async getUsers(
@Args() usersArgs: UsersArgs
): Promise<UserEntity[]> {
return this.usersRepository.find({
take: usersArgs.take,
skip: usersArgs.skip,
});
}
@Query(returns => UserGraphql)
async getUser(
@Args('id') id: number,
): Promise<UserEntity> {
return this.usersRepository.findOne({id});
}
@Mutation(returns => UserGraphql)
async createUser(
@Args('user') userInput: UserInput,
): Promise<UserEntity> {
return await this.usersService.create(userInput);
}
@Mutation(returns => UserGraphql)
async updateUser(
@Args('id') id: number,
@Args('user') userInput: UserInput
): Promise<UserEntity> {
return await this.usersService.update(id, userInput);
}
@Mutation(returns =>Boolean)
async removeUser(@Args('id') id: number) {
await this.usersRepository.delete(id);
return true;
}
}
Ostatecznie należy jeszcze skonfigurować plik modułu.
//users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersResolver } from './users.resolver';
import {TypeOrmModule} from '@nestjs/typeorm';
import {UsersRepository} from './users.repository';
@Module({
imports: [
TypeOrmModule.forFeature([UsersRepository])
],
providers: [UsersService, UsersResolver]
})
export class UsersModule {}
Finalnie struktura modułu użytkowników wygląda następująco:

W celu użycia stworzonego modułu należy go załączyć w pliku „app.module.ts”, który ostatecznie wygląda tak:
//app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import {GraphQLModule} from '@nestjs/graphql';
import {TypeOrmModule} from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';
@Module({
imports: [
TypeOrmModule.forRoot({
type : 'mysql',
host : 'localhost',
port : 3306,
username : 'username',
password : 'password',
database : 'graphql-article',
entities : [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
charset : 'utf8mb4_unicode_ci',
logging : true
}),
GraphQLModule.forRoot({
debug : true,
playground : true,
autoSchemaFile: 'schema.gql'
}),
UsersModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Gratulacje, przeszedłeś przez cały proces konfiguracji oraz implementacji wymaganych metod.
Testy
Wskazówka: Jeśli jeszcze tego nie zrobiłeś, to stwórz bazę danych o nazwie “graphql-article”.
Uruchamiamy projekt komendą:
npm start
Po uruchomieniu zostaje stworzona tabela w bazie danych na podstawie skonfigurowanej encji użytkownika „user.entity”. Ja w celu zobrazowania sytuacji użyję klienta bazy danych o nazwie MySQL Workbench.
Środowisko do testowania znajduje się pod adresem http://localhost:3000/graphql

A więc do dzieła stwórzmy użytkownika oraz zwróćmy jego dane!

Jak widać w bazie danych pojawił się nowy wpis.

Następnie przeprowadźmy edycję stworzonego wcześniej użytkownika.


Jak zauważyłeś proces edycji przebiegł pomyślnie.
Usunięcie użytkownika


Jak widzisz wpis o użytkowniku został usunięty z bazy danych.
Natomiast co z pobieraniem użytkowników?
W tym celu stworzyłem kilka rekordów w bazie danych.

Pobranie pojedynczego użytkownika

Pobranie listy użytkowników.

Podsumowując użycie GraphQL w Nest.js to uważam, że narzędzia jakie są dostarczane przez te dwie technologie pozwalają na szybkie i wygodne budowanie aplikacji serwerowej. Sam GraphQL daję więcej możliwości dla frontend’owców, dzięki łatwiejszej manipulacji zwracanych danych oraz jednolitej generowanej dokumentacji. Natomiast połączenie z Nest.js pozwala na czytelną oraz przyjazną strukturę aplikacji.
A Ty co sądzisz o połączeniu GraphQL z Nest.js? Czy Twoim zdaniem jest to przyszłość budowania aplikacji serwerowych? Chętnie z Tobą podyskutuję na ten temat.
Resources