GraphQL API с помощью NestJS
Nest предлагает два метода создания API-интерфейсов GraphQL: сначала код и сначала схема . Подход, ориентированный на код, включает использование классов и декораторов TypeScript для создания схем GraphQL. При таком подходе вы можете повторно использовать свой класс модели данных в качестве схемы и украсить его декоратором, @ObjectType()а Nest автоматически сгенерирует схему из вашей модели. Подход, ориентированный на схему, включает определение схемы с использованием языка определения схем GraphQL (SDL) , а затем реализацию службы путем сопоставления определений в схеме.
Добавление GraphQL
Под капотом Nest использует реализацию сервера Apollo GraphQL для использования GraphQL с приложениями Nest. Чтобы добавить API в наш проект Nest, нам нужно установить Apollo Server и другие зависимости GraphQL.
$ npm i --save @nestjs/graphql graphql-tools graphql apollo-server-express
Теперь, когда зависимости установлены, вы можете импортировать GraphQLModule
файл AppModule
.
//src/app.module.ts import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; @Module({ imports: [ GraphQLModule.forRoot({}), ], }) export class AppModule {}
GraphQLModule
является оболочкой над сервером Apollo. Он предоставляет статический метод для настройки базового экземпляра Apollo. Метод принимает список параметров , который передается конструктору сервера Apollo.
Добавление базы данных
Nest не зависит от базы данных, он позволяет интегрироваться с любой базой данных, Object Document Mapper (ODM) или Object Relational Mapper (ORM). Для целей этого руководства мы будем использовать PostgreSQL и TypeORM .
Команда Nest рекомендует использовать TypeORM с Nest, потому что это самая зрелая ORM, доступная для TypeScript. Поскольку он написан на TypeScript, он хорошо интегрируется с платформой Nest. Nest предоставляет пакет для работы с TypeORM.
Давайте установим эти зависимости
$ npm install --save @nestjs/typeorm typeorm pg
После завершения процесса установки мы можем подключиться к базе данных с помощью файла TypeOrmModule
.
// src/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: [ GraphQLModule.forRoot({ autoSchemaFile: 'schema.gql' }), TypeOrmModule.forRoot({ type: 'postgres', host: 'localhost', port: 5432, username: 'godwinekuma', password: '', database: 'invoiceapp', entities: ['dist/**/*.model.js'], synchronize: false, }), ], controllers: [AppController], providers: [AppService], }) export class AppModule { }
Резольверы
Резолверы предоставляют инструкции по превращению операции GraphQL (запрос, изменение или подписка) в данные. Они либо возвращают тип данных, который мы указываем в нашей схеме, либо обещание для этих данных. Пакет автоматически создает карту преобразователя, используя метаданные, предоставленные декораторами, используемыми для аннотирования классов. Чтобы продемонстрировать, как использовать функции пакета для создания GraphQL API, мы создадим простой API для выставления счетов.
Для создания нашего API мы будем использовать подход «сначала код».
Типы объектов
Типы объектов — это самые основные компоненты схемы GraphQL. Это набор полей, которые вы можете получить из своего сервиса, причем каждое поле объявляет тип. Каждый определенный тип объекта представляет объект домена в вашем API. Например, наш образец API счетов должен иметь возможность получать список клиентов и их счета, поэтому мы должны определить типы объектов Customer
и Invoice
для поддержки этой функциональности.
Поскольку мы используем подход «сначала код», мы определим схемы с помощью классов TypeScript, а затем воспользуемся декораторами TypeScript для аннотирования полей этих классов.
// src/invoice/customer.model.ts import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm'; import { ObjectType, Field } from '@nestjs/graphql'; import { InvoiceModel } from '../invoice/invoice.model'; @ObjectType() @Entity() export class CustomerModel { @Field() @PrimaryGeneratedColumn('uuid') id: string; @Field() @Column({ length: 500, nullable: false }) name: string; @Field() @Column('text', { nullable: false }) email: string; @Field() @Column('varchar', { length: 15 }) phone: string; @Field() @Column('text') address: string; @Field(type => [InvoiceModel], { nullable: true }) @OneToMany(type => InvoiceModel, invoice => invoice.customer) invoices: InvoiceModel[] @Field() @Column() @CreateDateColumn() created_at: Date; @Field() @Column() @UpdateDateColumn() updated_at: Date; }
Обратите внимание, что мы украсили класс атрибутом from . Декоратор сообщает Nest, что класс является классом объектов.
Поле
Каждое свойство в нашем CustomerModel
классе выше украшено декоратором. Nest требует, чтобы мы явно использовали декоратор в наших классах определения схемы для предоставления метаданных о типе GraphQL и необязательности каждого поля.
Тип GraphQL поля может быть либо скалярным, либо другим типом объекта. GraphQL поставляется с набором скалярных типов по умолчанию: Int
, String
, ID
, Float
и Boolean
. Декоратор принимает необязательную функцию типа (например, type => Int) и необязательный объект параметров.@Field()
Когда поле представляет собой массив, мы должны вручную указать тип массива в функции типа декоратора, как показано ниже.
@Field(type => [InvoiceModel]) invoices: InvoiceModel[]
Теперь, когда мы создали CustomerModel
тип объекта, давайте определим InvoiceModel
тип объекта.
// src/invoice/invoice.model.ts import { CustomerModel } from './../customer/customer.model'; import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, JoinColumn, ManyToOne, ChildEntity } from 'typeorm'; import { ObjectType, Field } from '@nestjs/graphql'; export enum Currency { NGN = "NGN", USD = "USD", GBP = "GBP", EUR = " EUR" } export enum PaymentStatus { PAID = "PAID", NOT_PAID = "NOT_PAID", } @ObjectType() export class Item{ @Field() description: string; @Field() rate: number; @Field() quantity: number } @ObjectType() @Entity() export class InvoiceModel { @Field() @PrimaryGeneratedColumn('uuid') id: string; @Field() @Column({ length: 500, nullable: false }) invoiceNo: string; @Field() @Column('text') description: string; @Field(type => CustomerModel) @ManyToOne(type => CustomerModel, customer => customer.invoices) customer: CustomerModel; @Field() @Column({ type: "enum", enum: PaymentStatus, default: PaymentStatus.NOT_PAID }) paymentStatus: PaymentStatus; @Field() @Column({ type: "enum", enum: Currency, default: Currency.USD }) currency: Currency; @Field() @Column() taxRate: number; @Field() @Column() issueDate: string; @Field() @Column() dueDate: string; @Field() @Column('text') note: string; @Field( type => [Item]) @Column({ type: 'jsonb', array: false, default: [], nullable: false, }) items: Item[]; @Column() @Field() taxAmount: number; @Column() @Field() subTotal: number; @Column() @Field() total: string; @Column({ default: 0 }) @Field() amountPaid: number; @Column() @Field() outstandingBalance: number; @Field() @Column() @CreateDateColumn() createdAt: Date; @Field() @Column() @UpdateDateColumn() updatedAt: Date; }
Специальные типы объектов GraphQL
Мы видели, как определять типы объектов с помощью Nest. Однако в GraphQL есть два специальных типа: Query
и Mutation
. Они служат родительскими объектами для других типов объектов и являются особыми, поскольку определяют точку входа для других объектов. Каждый GraphQL API имеет Query
тип и может иметь или не иметь Mutation
тип.
Наш API для выставления счетов должен иметь запрос, подобный приведенному ниже.
type Query { customer: CustomerModel invoice: InvoiceModel }
Создав объекты, которые должны существовать в нашем графе, теперь мы можем определить наш класс преобразователя, чтобы дать нашему клиенту возможность взаимодействовать с нашим API. В методе code-first класс распознавателя одновременно определяет функции распознавателя и генерирует Query
тип.
Чтобы создать преобразователя, мы создадим класс с функциями преобразователя в качестве методов и украсим класс декоратором .
//src/customer/customer.resolver.ts import { InvoiceModel } from './../invoice/invoice.model'; import { InvoiceService } from './../invoice/invoice.service'; import { CustomerService } from './customer.service'; import { CustomerModel } from './customer.model'; import { Resolver, Mutation, Args, Query, ResolveField, Parent } from '@nestjs/graphql'; import { Inject } from '@nestjs/common'; @Resolver(of => CustomerModel) export class CustomerResolver { constructor( @Inject(CustomerService) private customerService: CustomerService, @Inject(InvoiceService) private invoiceService: InvoiceService ) { } @Query(returns => CustomerModel) async customer(@Args('id') id: string): Promise<CustomerModel> { return await this.customerService.findOne(id); } @ResolveField(returns => [InvoiceModel]) async invoices(@Parent() customer) { const { id } = customer; console.log(customer); return this.invoiceService.findByCustomer(id); } @Query(returns => [CustomerModel]) async customers(): Promise<CustomerModel[]> { return await this.customerService.findAll(); } }
Декоратор принимает необязательный аргумент, который используется для указания родителя функции разрешения полей. В нашем примере выше мы создали , который определяет одну функцию разрешения запросов и одну функцию разрешения полей.
Чтобы указать, что метод является обработчиком запросов, мы аннотировали метод с помощью декоратора . Раньше мы аннотировали метод, который разрешает поле файла . Декоратор используется для извлечения аргументов из запроса для использования в обработчике запросов.
GraphQL игровая площадка
Теперь, когда мы создали точку входа в наш сервис графов, мы можем просматривать наш GraphQL API через игровую площадку. Игровая площадка представляет собой графическую интерактивную интегрированную среду разработки GraphQL в браузере, доступную по умолчанию по тому же URL-адресу, что и сам сервер GraphQL. Чтобы получить доступ к игровой площадке, нам нужно, чтобы наш сервер GraphQL работал.
Выполните следующую команду, чтобы запустить сервер.
npm start
Когда сервер запущен, откройте веб-браузер и перейдите к игровой площадке.http://localhost:3000/graphql
Мутации
Мы рассмотрели, как получить данные с сервера GraphQL, но как насчет изменения данных на стороне сервера? Методы мутации используются для изменения данных на стороне сервера в GraphQL.
Технически Query
можно реализовать для добавления данных на стороне сервера. Но общее соглашение состоит в том, чтобы аннотировать любой метод, который вызывает запись данных с помощью декоратора . Декоратор сообщает Nest, что такой метод предназначен для модификации данных.@Mutations()
Давайте добавим новый в наш преобразователь.createCustomer()
@Mutation(returns => CustomerModel) async createCustomer( @Args('name') name: string, @Args('email') email: string, @Args('phone', { nullable: true }) phone: string, @Args('address', { nullable: true }) address: string, ): Promise<CustomerModel> { return await this.customerService.create({ name, email, phone, address }) }
createCustomer()
был украшен, чтобы указать, что он изменяет или добавляет новые данные.
Если мутация должна принимать объект в качестве аргумента, нам нужно будет создать особый вид вызываемого объекта, InputType
а затем передать его в качестве аргумента методу. Чтобы объявить тип ввода, используйте декоратор.
import { PaymentStatus, Currency, Item } from "./invoice.model"; import { InputType, Field } from "@nestjs/graphql"; @InputType() class ItemDTO{ @Field() description: string; @Field() rate: number; @Field() quantity: number } @InputType() export class CreateInvoiceDTO{ @Field() customer: string; @Field() invoiceNo: string; @Field() paymentStatus: PaymentStatus; @Field() description: string; @Field() currency: Currency; @Field() taxRate: number; @Field() issueDate: Date; @Field() dueDate: Date; @Field() note: string; @Field(type => [ItemDTO]) items: Array<{ description: string; rate: number; quantity: number }>; } @Mutation(returns => InvoiceModel) async createInvoice( @Args('invoice') invoice: CreateInvoiceDTO, ): Promise<InvoiceModel> { return await this.invoiceService.create(invoice) }
Заключение
Мы продемонстрировали, как использовать подход «сначала код» для создания GraphQL API с помощью Nest.