предисловие
В настоящее время работает над проектомBFF
, nest.js
а такжеGraphQL
Эти два стека технологий являются «новой» попыткой, хотяGraphQL
существует15
год
вышел, но сnest.js
В сочетании, благодаря хорошей авторской инкапсуляции, произошла замечательная химическая реакция
Конечно, это не гидрология, которая приклеивает официальные документы и потом учит, как ими пользоваться, а гидрология, основанная на опыте добычи полезных ископаемых.
плечи гигантов
-
type-graphql
Будуtypescript
определение превращается вgraphql
изschema
-
@nestjs/graphql
автор вapollo-serverНа основе 2 инкапсуляций -
data-loader
Агрегация и кэширование данныхresolver (n+1)
Эта проблема
Запись приложения
Здесь мы беремUserModule
Например
в состоянии пройти
query UserList() {
users {
id
name
}
}
получать
{
data: {
users: [{
id: "1",
name: '名字'
}]
}
}
import { Module } from '@nestjs/common'
import { GraphQLModule } from '@nestjs/graphql'
import { UserModule } from './user.module'
@Module({
imports: [
GraphQLModule.forRoot({
path: '/graphql',
typePaths: ['./**/*.graphql'],
definitions: {
path: join(process.cwd(), 'src/graphql.ts'),
outputAs: 'class',
},
}),
UserModule,
]
})
export class AppModule
Здесь каждый раз, когда приложение запускается, оно будет проходить всеgraphql
генерация файла схемыgraphql.ts
Например
type User {
id: ID!
name: String
}
будет генерировать
export class User {
id: string
name?: string
}
Затем мы пишемresolver
а такжеservice
можно использовать, когдаgraphql.ts
Создайте хорошее определение типа, но этот метод немного неудобен и немного несовместим с привычками программирования.
Если вы хотите написатьtypescript
определение, генерироватьgraphql
изschema
файл, затем используйтеtype-graphql
охватывать
import { Module } from '@nestjs/common'
import { GraphQLModule } from '@nestjs/graphql'
import { UserModule } from './user.module'
import { resolve } from 'path'
const schema = resolve(__dirname, 'schema.gql')
@Module({
imports: [
GraphQLModule.forRoot({
path: '/graphql',
autoSchemaFile: schema,
typePaths: [schema],
}),
UserModule,
]
})
export class AppModule
Наконец, просто напишите соответствующийmodel
Только что
import { Field, ID } from 'type-graphql'
export class User {
@Field(() => ID, { nullable: false })
id: string
@Field({ nullable: false })
name?: string
}
тут понятноgraphql
метафора схемы,@Field
Карты декоратораschema
вid
тип
Class User
идентификатор описанts
тип
Стоит отметить, чтоstring
| boolean
Равный базовый тип@Field
можно опустить, ноnumber
По умолчанию он будет преобразован вfloat
, поэтому вам нужно отобразить утверждение, которое довольно жалко
Другой момент заключается в том, что если это перечисление, вам нужно использоватьregisterEnumType
Зарегистрируйтесь один раз
import { registerEnumType } from 'type-graphql'
export enum Enum {
a,
b
}
registerEnumType(Enum, {
name: 'RolesEnum'
})
// 使用
export class User {
@Field(() => Enum, { nullable: false })
name?: Enum
}
Resolver
существуетnest.js
в однойGraphql
модуль поresolver
а такжеservice
сочинение
import { Module } from '@nestjs/common'
import { UserResolver } from './user.resolver'
import { UserService } from './user.service'
@Module({
providers: [
UserResolver,
UserService,
]
})
export class UserModule {}
import { Args, Resolver, Query } from '@nestjs/graphql'
import { UserService } from './user.service'
@Resolver()
export class UserResolver {
constructor(private readonly userService: UserService)
@Query(() => User[], {
name: 'users'
})
public async users(): Promise<User[]> {
this.userService.xxxxx()
}
}
каждый@Query
Декоратор, соответствующий методу, по умолчанию будет использовать имя функции в качестве имени запроса, которое можно указать явно с помощью имени.
Таким образом, при запускеQuery
когда соответствующийResolver
позвонит в соответствующийservice
Логику обработки можно
query users {
id
name
}
Если вы хотите запросить третье полеage
ноage
не здесьUser
Например, если вы хотите вызвать другой интерфейс для запроса, вы можете
использовать@ResolveProperty
import { Args, Resolver, ResolveProperty } from '@nestjs/graphql'
...
@Resolver()
export class UserResolver {
constructor(private readonly userService: UserService)
@ResolveProperty(() => number)
public async age(): Promise<number> {
this.userService.getAge()
}
}
Но не забывайтеmodel
добавить внутрьage
поле
import { Field, ID } from 'type-graphql'
export class User {
@Field(() => ID, { nullable: false })
id: string
@Field({ nullable: false })
name?: string
@Field(()=> Number, { nullable: false })
age?: number
}
При таком запросеResolver
поможет вам совместить
query users {
id
name
age
}
{
id: '1',
name: 'xx',
age: 18
}
DateLoader
из-заResolver
изN+1запрос вопрос
как вышеthis.userService.getAge()
, будет выполняться несколько раз, если он выполняется какое-тоsql
Могут быть проблемы с производительностью и пустая трата ресурсов, но это не большая проблема,
мы используемdataloader
Для решения этой проблемы
import DataLoader from 'dataloader'
@Injectable()
export class UserService {
loader = new DataLoader(()=>{
return 一些查询操作
})
getAge() {
this.loader.load()
// 查询多个 this.loader.loadMany()
}
}
Принцип, вероятно, состоит в том, чтобы поместить запрос текущего цикла событий вprocess.nextTick
выполнить
Развертывание докера
из-заdocker
В нем нет разрешения на запись файлов, что вызовет проблему, потому что при запуске приложения
...
RUN node dist/index.js
будет автоматически сгенерированschema
файл, то естьfs.writeFile
Это приведет кdocker
Не запускается, поэтому требуется небольшая доработка.GraphqlModule
Конфигурация
- метод 1:
import { Module } from '@nestjs/common'
import { GraphQLModule } from '@nestjs/graphql'
import { UserModule } from './user.module'
const schema = resolve(__dirname, 'schema.gql')
@Module({
imports: [
GraphQLModule.forRoot({
path: '/graphql',
autoSchemaFile: process.env.NODE_ENV === 'development' ? schema : false,
typePaths: [schema],
}),
UserModule,
]
})
export class AppModule
существуетdevelopment
будет сгенерировано, когдаschema.gql
, существуетproduction
Отключить автоматическую генерацию в среде
Также укажитеtypePaths
дляschema.gql
Это можно решить
- Способ 2:
...
COPY schema.gql /dist
RUN node dist/index.js
первое использованиеtype-graphql
который предоставилbuildSchema
Фактическиnest.js
изGraphqlModule
Этот метод также используется для его автоматического создания.
import { buildSchema } from "type-graphql";
async function bootstrap() {
const schema = await buildSchema({
resolvers: [__dirname + "/**/*.resolver.ts"],
});
// other initialization code, like creating http server
}
bootstrap();
Вы можете копировать этот файл в него каждый раз, когда создаете образ.
АСД
существуетexpress
Может быть перехвачен средним ключомrequest
сделать проверку разрешения, вnest.js
Это очень удобно использоватьGuards
выполнить
import { Args, Resolver, ResolveProperty } from '@nestjs/graphql'
import { AuthGuard } from './auth.guard'
...
@Resolver()
@UseGuards(AuthGuard)
export class UserResolver {
constructor(private readonly userService: UserService)
@ResolveProperty(() => number)
public async age(): Promise<number> {
this.userService.getAge()
}
}
из-заGraphql
есть одинcontext
Концепция может быть реализована черезcontext
получить текущийrequest
// auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'
import { GqlExecutionContext } from '@nestjs/graphql'
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context).getContext()
const request = context.switchToHttp().getRequest()
// 做一些权限验证
// jwt 验证
// request.headers.authorization
}
}
преобразовать ответ об ошибке
из-за использованияapollo-server
, каждый разQuery
илиMutation
Когда сообщается об ошибке, она отправляется во внешний интерфейс错误
иерархия будет очень глубокой,
Если вы хотите настроить, вы можете использоватьformatError
а такжеformatResponse
, но так как эти два поляnest.js
не дает соответствующего подробного определения
Может надо глянутьapollo-server
, хотя в документации TMD всего несколько строк
import { Module } from '@nestjs/common'
import { GraphQLModule } from '@nestjs/graphql'
import { UserModule } from './user.module'
const schema = resolve(__dirname, 'schema.gql')
@Module({
imports: [
GraphQLModule.forRoot({
path: '/graphql',
autoSchemaFile: process.env.NODE_ENV === 'development' ? schema : false,
typePaths: [schema],
context(ctx) {
// 在 context 里面 添加一些 东西 ctx.req
ctx.xx = 1
return ctx
}
formatError(error) {
return error
},
formatResponse(response, {context}){
// 这里进行重写
// data, errors 是 graphql 的规范 无法覆盖
return {
errors: {}
}
// ❌ 这样是不行的
return {
name: 1,
age: 18
}
// ✅
return {
data: {
name: 1,
age: 18
}
}
}
}),
UserModule,
]
})
export class AppModule
тестовое задание
вы можете немного написать单元测试
илиe2e测试
, документы все есть, я не буду тут носильщиком
наконец
Конечно, грусть от того, что я наступил на яму, - это гораздо больше, чем просто этот небольшой текст. На этот раз я также многое приобрел. Продолжайте в том же духе