Resolvers¶
Resolvers are like rest controllers but they provide mapping to graphql operations: query, mutation into data. Big difference is that in REST you invoke for example 5 rest resource to do create,read,update,delete operations where in graphql we can execute 5 different operations on same graphql request resource.
Queries¶
Queries are used to fetch data and are meant to be read only operations, inside query operation we can implement custom filters. We need to use @Resolver
decorator so that type-graphql can recognize that current class is resolver type.
import {Inject, Injectable} from "@typeix/resty";
import {Query, Resolver} from "type-graphql";
import {UserService} from "~/services/user.service";
import {User} from "~/data/user.entity";
@Injectable()
@Resolver(User)
export class UserResolver {
@Inject() private userService: UserService;
@Query(()=> [User])
users(): Array<User> {
return this.userService.find();
}
}
@CreateProvider({
provide: "GraphqlConfigSchema",
useFactory: async () => {
return await buildSchema({
resolvers: [UserResolver],
container: ({context}) => context.container
});
},
providers: []
})
@Controller({
path: "/graphql",
providers: [UserResolver],
interceptors: []
})
Arguments¶
Usually, queries have some arguments - it might be the id of a resource, a search phrase or pagination settings. TypeGraphQL allows you to define arguments in two ways.
Using @Arg()
decorator we can inject value, we need to provide argument name and options if nullable or defaultValue for example.
@Injectable()
@Resolver(User)
export class UserResolver {
@Inject() private userService: UserService;
@Inject() private logger: Logger;
@Query(()=> [User])
users(
@Arg("firstName", { nullable: true }) firstName: string
): Array<User> {
this.logger.debug(`Injected argument: ${firstName}`);
return this.userService.findByName(firstName);
}
}
@ArgsType()
export class GetUserArgs {
@Field({ nullable: true }) firstName?: string;
@Field({ nullable: true }) lastName?: string;
@Field({ nullable: true }) age?: number;
}
@Args()
decorator and with destructuring syntax we gain access to single arguments as variables. @Injectable()
@Resolver(User)
export class UserResolver {
@Inject() private userService: UserService;
@Inject() private logger: Logger;
@Query(()=> [User])
users(@Args() {firstName, lastName, age}: GetUserArgs): Array<User> {
this.logger.debug(`Injected argument: ${firstName}`);
return this.userService.findByName(firstName);
}
}
Interceptors¶
With power of typeix dependency injection we can implement interceptors which are working with any method as long as object is created with Injector. Interceptors can be used to implement any custom logic and it can change result or behavior of method, for example Logging, Authorization.
In following example we are going to implement logger Interceptor:
import {createMethodInterceptor, Inject, Injectable, Interceptor, Logger, Method} from "@typeix/resty";
@Injectable()
class LoggerInterceptor implements Interceptor {
@Inject() private logger: Logger;
async invoke(method: Method): Promise<any> {
let result = await method.invoke();
this.logger.debug([
`Injected arguments: ${this.prettyPrint(method.methodArgs)}`,
`Result ${this.prettyPrint(result)}`,
`With decorator args: ${this.prettyPrint(method.decoratorArgs)}`
].join("\n"));
return result;
}
private prettyPrint(value: any): string {
return JSON.stringify(value, null, " ");
}
}
export function LogOutput(value) {
return createMethodInterceptor(LogOutput, LoggerInterceptor, {value});
}
@LogOutput()
decorator. import {Inject, Injectable} from "@typeix/resty";
import {Arg, Query, Resolver} from "type-graphql";
import {UserService} from "~/services/user.service";
import {User} from "~/data/user.entity";
import {LogOutput} from "~/controllers/graphql/logger.interceptor";
@Injectable()
@Resolver(User)
export class UserResolver {
@Inject() private userService: UserService;
@Query(()=> [User])
@LogOutput("Logging UserResolver Output")
users(@Args() {firstName, lastName, age}: GetUserArgs): Array<User> {
return this.userService.findByName(firstName);
}
}
[1649358777779] DEBUG (): Injected arguments: [
null
]
Result [
{
"permissions": [],
"id": 1,
"firstName": "John",
"lastName": "Doe",
"age": 30
},
{
"permissions": [],
"id": 2,
"firstName": "Tifany",
"lastName": "Doe",
"age": 31
}
]
With decorator args: {
"value": "Logging UserResolver Output"
}
Field Resolvers¶
Using @FieldResolver()
provides you a way to extend current entity field with custom logic, for example we have database link, custom computed calculation like age from date of birth in User entity or to deliver permissions as lazy link
In following example we can see how permissions are delivered as link to users, for each user it will invoke permissions resolver to deliver permissions data to user.
import {Inject, Injectable, Logger} from "@typeix/resty";
import {Arg, FieldResolver, Query, Resolver, Root} from "type-graphql";
import {UserService} from "~/services/user.service";
import {User} from "~/data/user.entity";
import {LogOutput} from "~/controllers/graphql/logger.interceptor";
import {PermissionService} from "~/services/permission.service";
import {Permission} from "~/data/permission.entity";
@Injectable()
@Resolver(User)
export class UserResolver {
@Inject() private userService: UserService;
@Inject() private permissionService: PermissionService;
@Inject() private logger: Logger;
@Query(()=> [User])
@LogOutput("Logging UserResolver.users")
users(
@Arg("firstName", { nullable: true }) firstName: string
): Array<User> {
return this.userService.findByName(firstName);
}
@FieldResolver()
@LogOutput("Logging UserResolver.users.permissions")
permissions(@Root() user: User): Array<Permission> {
return this.permissionService.find(user);
}
}
query GetUsersAndPermissions {
users {
id,
firstName,
lastName,
age,
permissions {
id,
action
}
}
permissions {
id,
action
}
}
import {Field, ID, ObjectType} from "type-graphql";
import {Permission} from "~/data/permission.entity";
@ObjectType()
export class User {
@Field(() => ID) id: number;
@Field() firstName: string;
@Field() lastName: string;
@Field() age: number;
@Field(() => [Permission]) permissions: Array<Permission> = [];
}