После нескольких лет создания и использования REST API я наконец попробовал Graphql, и он мне сразу же понравился.

Я решил написать эту статью, потому что я не видел много руководств по graphql api с type-orm, typegraphql и MongoDB.

Прежде чем мы начнем

Если вы читаете это, предполагайте, что вы каким-то образом знакомы с graphql, поэтому я не буду подробно рассказывать об основах типов, преобразователях, ... Если вы хотите начать работу с этой технологией, я действительно советую эту ссылку (https: // www .howtographql.com / )

Давайте начнем !

Пример приложения, который мы собираемся создать, представляет собой простое приложение для работы с товарами в скейтшопе.

Во-первых, нам нужно настроить новый проект, открыть новый терминал в папке проекта и ввести:

npm init -y

Затем мы можем добавить зависимости:

yarn add express apollo-server-express graphql mongodb reflect-metadata type-graphql typeorm

и, наконец, зависимости разработчиков

yarn add -D @types/node @types/express ts-node nodemon typescript

Когда все будет загружено, мы можем добавить сценарий разработчика в наш package.json:

"scripts": {
  "dev": "nodemon --exec ts-node src/index.ts"
}

он запустит машинописный текст в режиме просмотра, поэтому не нужно каждый раз перезагружать консоль

Настройка подключения к базе данных Mongodb

Для этого урока я буду использовать локальную базу данных https://www.mongodb.com.

Поэтому для этого урока мы будем использовать TYPEORM, который представляет собой orm, написанный на typescrypt, и он упростит манипуляции с базой данных и подключение.

Итак, в корневой папке вашего проекта создайте новый файл с именем ormconfig.json.

и вставьте внутрь следующее:

{
 "type": "mongodb",
 "host": "localhost",
 "username": "db_username or remove if you don't need",
 "password": "db_password or remove if you don't need",
 "database": "you_db_name",
 "port": "27017",
 "synchronize": true,
 "entities": ["./src/entities/*.ts"]
}

Теперь вы можете сохранить файл, и мы можем перейти к следующему разделу.

Настройка машинописного текста и сервера graphql

Теперь, когда мы настроили соединение с базой данных, мы можем приступить к настройке среды машинописного текста и сервера.

Сначала создайте новый файл в корне вашего проекта с именем tsconfig.json.

и наклеить на него следующее:

{
"compilerOptions": {
 "target": "es6",
 "module": "commonjs",
 "lib": ["dom","es6","es2017","esnext.asynciterable"],
 "sourceMap": true,
 "outDir": "./dist",
 "moduleResolution": "node", 
 "removeComments": true,
 "noImplicitAny": true,
 "strictNullChecks": true,
 "strictFunctionTypes": true, 
 "noImplicitThis": true,
 "noUnusedLocals": true,
 "noUnusedParameters": true,
 "noImplicitReturns": true,
 "noFallthroughCasesInSwitch": true,
 "allowSyntheticDefaultImports": true,
 "esModuleInterop": true,
 "emitDecoratorMetadata": true,
 "experimentalDecorators": true,
 "resolveJsonModule": true,
 "baseUrl": "."
},
 "exclude": ["node_modules"],
 "include": ["./src/**/*.tsx", "./src/**/*.ts"]
}

Вы также можете использовать свой собственный tsconfig, но убедитесь, что для emitDecoratorMetadata и экспериментальныхDecorators установлено значение true.

Теперь мы можем создать новую папку src /, эта папка является корневой папкой нашего кода.

Теперь мы можем создать новый файл с именем index.ts и вставить в него следующее:

import 'reflect-metadata';
import { ApolloServer } from 'apollo-server-express';
import Express from 'express';
import { buildSchema } from 'type-graphql';
import { createConnection } from "typeorm";
const startServer = async () => {
 await createConnection(); // 1
 const schema = await buildSchema({ // 2
   ... we are using this part in the next section
 })
 const app = Express() //3
 const apolloServer = new ApolloServer({schema}) //4
 apolloServer.applyMiddleware({app}); // 5
 app.listen(4000, () => {
  console.log('server started');
 })
}
startServer();

Теперь позвольте мне объяснить вам этот код:

1:

Здесь мы создаем новое соединение после файла ormconfig.json, который мы создали ранее.

2 :

Здесь мы создаем схему приложения graphql (в следующем разделе я расскажу подробнее)

3:

Создаем новый экземпляр Express

4:

Создаем новый экземпляр apollo

5:

Мы передаем appolo наш экспресс-экземпляр, чтобы установить связь между express и сервером graphql.

А теперь перейдем к следующему разделу!

Создаем нашу первую сущность

Итак, наша сущность будет иметь две роли: представлять таблицу / запись базы данных и тип Graphql. Для создания сущности воспользуемся декоратором. Чтобы продолжить, создайте новую папку entity, затем создайте новый файл с именем Product.ts и вставьте в него следующее:

import { ObjectType, Field, ID, Float, Int } from "type-graphql";
import { Entity, BaseEntity, ObjectIdColumn, Column } from "typeorm";
import { ObjectID } from "mongodb";
@ObjectType() //1
@Entity() //2
export class Product extends BaseEntity { //3
@Field(() => ID) //4
@ObjectIdColumn() //5
id: ObjectID;
@Field() 
@Column() //6
productName: string;
@Field()
@Column()
description: string;
@Field(() => Float)
@Column()
price: number;
@Field(() => Int)
@Column()
numberInStock: number
}

1:

Декоратор ObjectType, чтобы сообщить, что наш класс представляет тип объекта Graphql.

2:

Декоратор сущностей, чтобы сообщить, что наш класс представляет сущность базы данных.

3:

Мы делаем наш класс расширяющимся до baseEntity, так что мы можем получить доступ к методу вроде (find, findOne,…)

4:

Декоратор поля, представляет поле Graphql нашего типа объекта graphql (здесь мы указали () = ›ID, чтобы сообщить graphql, что это фактический идентификатор объекта)

5:

Специальный декоратор, сообщающий, что этот столбец представляет собой уникальный сгенерированный идентификатор.

6:

Здесь мы говорим, что это свойство представляет собой столбец базы данных.

Теперь, когда мы создали наш тип / объект Gql, нам нужно создать различный преобразователь, чтобы фактически создать нашу CRUD-систему.

Создайте наш первый преобразователь запросов

Итак, для начала мы можем создать новые распознаватели вызовов папки и внутри него создать новый вызов файла ProductResolver.ts и добавить следующий код в:

import { Resolver, Query} from "type-graphql";
import { Product} from "../entities/Product";
@Resolver()//1
export class ProductResolver{
@Query(() => [Product]) //2
 async products(): Promise<Product[]> { 
  return await Product.find()// 3
 }
}

1:

Скажите, что этот класс представляет собой преобразователь

2:

Сообщите, что эта функция представляет преобразователь запросов, «() =› [User] »определяет тип возвращаемого значения этой операции, здесь это массив продуктов.

4:

.find () - это метод TYPEORM для поиска всех записей в базе данных, связанных с нашими продуктами.

Теперь давайте добавим этот преобразователь на наш сервер graphql. Вернитесь к индексному файлу и добавьте этот код в схему const:

import { ProductResolver } from './resolvers/ProductResolver';
...
const schema = await buildSchema({
 resolvers: [ProductResolver]
});
...

Теперь мы можем ввести команду yarn / npm dev и перейти на localhost: 4000 / graphql.

Вы должны увидеть что-то подобное. Как видите, схема была автоматически сгенерирована с использованием нашего типа продукта и преобразователя, который мы создали ранее.

Теперь давайте проверим наш запрос

Как видите, взамен мы получили какой-либо продукт, потому что у нас нет никаких записей, давайте теперь создадим новый преобразователь, который будет обрабатывать создание продукта.

Создайте нашу первую Мутацию

Теперь мы собираемся создать новый преобразователь для обработки создания продукта, отредактировать файл ProductResolver.ts и добавить эти строки:

@Mutation(() => Product!) //  1
async addProduct(
 @Arg('productName') productName: string, // 2
 @Arg('description') description: string,
 @Arg('price') price: number,
 @Arg('numberInStock') numberInStock: number
): Promise<Product> {
 const product = Product.create({ // 3
  productName,
  description,
  price,
  numberInStock
 })
 return await product.save() // 4
}

1:

Создайте новую мутацию, которая вернет пользователя

2:

@ Arg () определяет новый аргумент для запроса (здесь мы добавляем по одному для каждого свойства сущности продукта)

3:

Мы создаем новую запись продукта со всеми полями args

4:

Сохраняем наш новый товар в базу и возвращаем результат

Теперь, если мы хотим протестировать наш новый преобразователь, вернитесь на площадку Graphql и введите следующее:

Теперь, если мы вернемся к распознавателю запросов продукта, мы должны увидеть следующее:

и на MongoDB Compas:

Теперь, когда у нас есть чтение и создание частей нашего приложения, давайте добавим другие преобразователи:

findProductByID:

@Query(() => Product!, {nullable: true})
async findProductByID(
@Arg("productID") productID: string
): Promise<Product | undefined | null> {
 return await Product.findOne(productID)
}

здесь я добавляю новую опцию {nullable: true}, например, если преобразователь не найдет никакой соответствующей записи, мы можем вернуть null без каких-либо ошибок

deleteProductByID:

@Mutation(() => Product!, {nullable: true})
async deleteProductByID(
 @Arg("productID") productID: string
): Promise<Product | undefined | null> {
 const allProduct = await getRepository(Product)
 const product = await allProduct.findOne(productID)
 if(product) {
  await allProduct.delete(productID);
  return product;
 }
  return null;
}

updateProduct:

@Mutation(() => Product!)
async updateProduct(
 @Arg("productID") productID: string,
 @Arg('productName') productName: string,
 @Arg('description') description: string,
 @Arg('price') price: number,
 @Arg('numberInStock') numberInStock: number,
): Promise<Product | null> {
 let product = await Product.findOne(productID);
 if(product) {
  product.productName = productName;
  product.description = description;
  product.price = price;
  product.numberInStock = numberInStock;
  await getRepository(Product).update(
   productID,
   product
  )
  return product
}
return null;
}

Заключение:

Итак, вот и мы, наше полное приложение CRUD. Я надеюсь, что эта статья будет полезна для создания вашего следующего Graphql api.

В следующих статьях я расскажу о:

- Рефакторинг нашего Graphql api и отношений между таблицами

- Аутентификация и авторизация с использованием JWT

Личная ссылка / контакт:

Telegram (если есть вопросы): @wedzyou

Мой гитхаб: https://github.com/Thomascogez