Типизация общения фронта и бэка через GraphQL SDK
TemaВ данной статье я покажу как можно на этапе написания кода типизировать общение фронта и бэка, без необходимости поднимать какие-либо сервисы. Этот метод хорош тем, что код, в котором есть ошибки на уровне запросов с фронта до сервера, больше не будет попадать в Git. При настроенном воркфлоу, разработчик не сможет запушить нерабочий код, а у фронта всегда будет свежая SDK схема GraphQL
Этот метод работает только в монорепе, когда фронт и бэк лежат в одном репозитории
В оффициальной доке NestJS в разделе Generating SDL можно найти код, в котором генерируется схема, на основе переданных туда резолверов
async function generateSchema() {
const app = await NestFactory.create(GraphQLSchemaBuilderModule);
await app.init();
const gqlSchemaFactory = app.get(GraphQLSchemaFactory);
const schema = await gqlSchemaFactory.create([RecipesResolver]);
console.log(printSchema(schema));
}
Но тут есть проблема: нужно вручную передавать каждый резолвер. На практике, я забывал это делать почти всегда. Как-то я рылся в исходниках неста и нашел код, который позволяет выцепить все резолверы, которые подключены в проекте на лету. За это отвечает ResolversExplorerService. С помощью него можно выцепить все резолверы и передавать в фабрику для генерации схемы
// generate-graphql.ts
import { NestFactory } from '@nestjs/core'
import { GraphQLSchemaFactory } from '@nestjs/graphql'
import { ResolversExplorerService } from '@nestjs/graphql/dist/services'
import { writeFileSync } from 'fs'
import { printSchema } from 'graphql'
import { join } from 'lodash'
import { exit } from 'process'
import { AppModule } from './app/app.module'
export async function generateSchema() {
const app = await NestFactory.create(AppModule)
const graphQLFactory = app.get(GraphQLSchemaFactory)
const explorer = app.get(ResolversExplorerService)
const schema = await graphQLFactory.create(explorer.getAllCtors())
writeFileSync(join(process.cwd(), '/schema.graphql'), printSchema(schema))
console.log('Graphql schema was generated successfuly!')
exit(0)
}
generateSchema()
Кайф, теперь у нас генерируется схема и сохраняется локально файл. Осталось добавить скрипт для фронта. Я использую genql т.к он лучше всего генерит схему и работает с ApolloClient.
genql --schema ./schema.graphql --output {projectRoot}/src/sdk
Интеграция с Nx
Имея две папки: server и client мы можем добавить nx команды для автоматизации этого дела.
Здесь мы добавляем две команды для билда и запуска скрипта, который будет создавать GraphQL схему
{
"name": "server",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/server/src",
"projectType": "application",
"tags": [],
"targets": {
"graphql:prepare": {
"executor": "@nx/webpack:webpack",
"dependsOn": [],
"outputs": ["{options.outputPath}"],
"options": {
"target": "node",
"compiler": "tsc",
"outputPath": "dist/packages/graphql-generate",
"main": "packages/server/src/lib/generate-graphql.ts",
"tsConfig": "packages/server/tsconfig.app.json",
"webpackConfig": "packages/server/webpack.config.js"
}
},
"graphql:generate": {
"executor": "@nx/js:node",
"options": {
"watch": false,
"buildTarget": "server:graphql:prepare"
}
}
}
}
А на уровне клиента мы добавим команду, которая генерирует из схемы SDK. В зависимостях команды проставим запуск генерации GraphQL схемы.
{
"name": "client",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/client",
"projectType": "application",
"tags": [],
"targets": {
"sdk:generate": {
"executor": "nx:run-commands",
"dependsOn": ["server:graphql:generate"],
"outputs": ["{projectRoot}/src/sdk"],
"options": {
"command": "genql --schema ./schema.graphql --output {projectRoot}/src/sdk"
}
}
}
}
Дальше достаточно запустить
nx run client:sdk:generate
