GraphQL
VitNode use GraphQL (opens in a new tab) to query and mutate data and Codegen (opens in a new tab) to generate typescript types. It's a powerful tool that allows you to query only the data you need and get the data in the shape you want.
Before we start, we're recomending you to read the GraphQL documentation (opens in a new tab) to understand the basics of the language and NestJS GraphQL documentation (opens in a new tab) to understand how we're using it in VitNode.
In this tutorial we're create a simple query and mutation with the categories data and Internationalization (i18n) from the blog plugin.
As an example, we're going to create a query to get all the categories from the blog plugin.
Folder for Feature
To save consistency and make it easier to maintain, we're going to create a module to store all the queries and mutations involvin the categories of the blog. Each sub-module should have its own module.
For example if you have blogs_categories
and blogs_articles
in the database, you should have two modules, one for each feature.
Let's create a new file in the backend/plugins/blog/categories/categories.module.ts
file and add the following code:
import { Module } from "@nestjs/common";
@Module({})
export class BlogCategoriesModule {}
Name class whatever you want, but it's a good practice to name it after the feature you're working on.
Import to the main module
Now we need to import the module we just created to the main module of the blog plugin. Open the backend/plugins/blog/blog.module.ts
file and add the following code:
import { Module } from "@nestjs/common";
import { AdminBlogModule } from "./admin/admin.module";
import { BlogCategoriesModule } from "./categories/categories.module";
@Module({
imports: [AdminBlogModule, BlogCategoriesModule]
})
export class BlogModule {}
Folder for Action
Each action should have its own folder. For example if you have a query to get all the categories, you should create a folder called show
or if you have a mutation to create a category, you should create a folder called create
inside the backend/plugins/blog/categories
folder.
Now, create a folder called show
inside the backend/plugins/blog/categories
folder.
Service
Create a Service
Create a file called show.service.ts
inside the backend/plugins/blog/categories/show
folder and add the following code:
import { Injectable } from "@nestjs/common";
@Injectable()
export class ShowBlogCategoriesService {}
Class name should be whatever you want, but it's a good practice to name {Action}{Plugin}{Module}Service
.
Inject the database
If you want to get access to database, you should inject the DatabaseService
in the constructor of the service.
import { Injectable } from "@nestjs/common";
import { DatabaseService } from "@/plugins/database/database.service";
@Injectable()
export class ShowBlogCategoriesService {
constructor(private databaseService: DatabaseService) {}
}
You can still get data from core
tables, but avoid modyfing other tables
from outside the plugin.
Import Service to the module
Now we need to inject the service to the module. Open the backend/plugins/blog/categories/categories.module.ts
file and add the following code:
import { Module } from "@nestjs/common";
import { ShowBlogCategoriesService } from "./show/show.service";
@Module({
providers: [ShowBlogCategoriesService]
})
export class BlogCategoriesModule {}
Mapped Types (DTO)
DTO (Data Transfer Object) is a class that defines how the data will be sent over the network. See NestJS Mapped Types (opens in a new tab) for more information.
Create folder for DTO
Create the backend/plugins/blog/categories/show/dto
folder.
NestJS GraphQL automaticly generates schema from the DTO classes. Make sure to each class has a unique name.
Arguments DTO (Input)
Create a file called show.args.ts
inside the backend/plugins/blog/categories/show/dto
folder and add the following code:
import { ArgsType, Field, Int } from "@nestjs/graphql";
@ArgsType()
export class ShowBlogCategoriesArgs {
@Field(() => Int, { nullable: true })
cursor: number | null;
@Field(() => Int, { nullable: true })
first: number | null;
@Field(() => Int, { nullable: true })
last: number | null;
}
Class name should be unique. For example {Action}{Plugin}{Module}Args
.
Objects DTO (Output)
Create a file called show.obj.ts
inside the backend/plugins/blog/categories/show/dto
folder and add the following code:
import { Field, Int, ObjectType } from "@nestjs/graphql";
import { PageInfo } from "@/types/database/pagination.type";
import { TextLanguage } from "@/types/database/text-language.type";
@ObjectType()
export class ShowBlogCategories {
@Field(() => Int)
id: number;
@Field(() => [TextLanguage])
name: TextLanguage[];
@Field(() => [TextLanguage], { nullable: true })
description: TextLanguage[] | null;
}
@ObjectType()
export class ShowBlogCategoriesObj {
@Field(() => [ShowBlogCategories])
edges: ShowBlogCategories[];
@Field(() => PageInfo)
pageInfo: PageInfo;
}
Class name should be unique. For example {Action}{Plugin}{Module}Obj
.
In this example we use Pagination with Database.
Import DTO to the Service
Now import the ShowBlogCategoriesArgs
and ShowBlogCategoriesObj
to the ShowBlogCategoriesService
.
import { Injectable } from "@nestjs/common";
import { ShowBlogCategoriesArgs } from "./dto/show.args";
import { ShowBlogCategoriesObj } from "./dto/show.obj";
import { DatabaseService } from "@/plugins/database/database.service";
@Injectable()
export class ShowBlogCategoriesService {
constructor(private databaseService: DatabaseService) {}
async show({
cursor,
first,
last
}: ShowBlogCategoriesArgs): Promise<ShowBlogCategoriesObj> {
...
}
}
If you want to know how to implement show
method, see Pagination with Database.
Resolver
Create a Resolver
Create a file called show.resolver.ts
inside the backend/plugins/blog/categories/show
folder and add the following code:
import { Args, Query, Resolver } from "@nestjs/graphql";
import { ShowBlogCategoriesService } from "./show.service";
import { ShowBlogCategoriesArgs } from "./dto/show.args";
import { ShowBlogCategoriesObj } from "./dto/show.obj";
@Resolver()
export class ShowBlogCategoriesResolver {
constructor(private readonly service: ShowBlogCategoriesService) {}
@Query(() => ShowBlogCategoriesObj)
async blog_categories__show(
@Args() args: ShowBlogCategoriesArgs
): Promise<ShowBlogCategoriesObj> {
return await this.service.show(args);
}
}
You can name the resolver whatever you want, but it's a good practice to name {Action}{Plugin}{Module}Resolver
.
Inject Service
import { Resolver } from "@nestjs/graphql";
import { ShowBlogCategoriesService } from "./show.service";
@Resolver()
export class ShowBlogCategoriesResolver {
constructor(private readonly service: ShowBlogCategoriesService) {}
}
Create method
import { Args, Query, Resolver } from "@nestjs/graphql";
import { ShowBlogCategoriesService } from "./show.service";
import { ShowBlogCategoriesArgs } from "./dto/show.args";
import { ShowBlogCategoriesObj } from "./dto/show.obj";
@Resolver()
export class ShowBlogCategoriesResolver {
constructor(private readonly service: ShowBlogCategoriesService) {}
@Query(() => ShowBlogCategoriesObj)
async blog_categories__show(
@Args() args: ShowBlogCategoriesArgs
): Promise<ShowBlogCategoriesObj> {
return await this.service.show(args);
}
}
The name of the query should be unique. Good practice is to name {plugin}_{module}__{action}
.
If query or mutation is in the admin folder, you should name it admin__{plugin}_{module}__{action}
.
Mutation
If you want to create a mutation, you should create a method in the resolver with the @Mutation
decorator.
import { Args, Mutation, Resolver } from "@nestjs/graphql";
import { CreateBlogCategoriesService } from "./create.service";
import { CreateBlogCategoriesArgs } from "./dto/create.args";
import { CreateBlogCategoriesObj } from "./dto/create.obj";
@Resolver()
export class CreateBlogCategoriesResolver {
constructor(private readonly service: CreateBlogCategoriesService) {}
@Mutation(() => CreateBlogCategoriesObj)
async blog_categories__create(
@Args() args: CreateBlogCategoriesArgs
): Promise<CreateBlogCategoriesObj> {
return await this.service.create(args);
}
}
If you want to add authorization to query, please follow Authorization tutorial.
Import Resolver to the Module
Now we need to inject the resolver to the module. Open the backend/plugins/blog/categories/categories.module.ts
file and add the following code:
import { Module } from "@nestjs/common";
import { ShowBlogCategoriesService } from "./show/show.service";
import { ShowBlogCategoriesResolver } from "./show/show.resolver";
@Module({
providers: [ShowBlogCategoriesService, ShowBlogCategoriesResolver]
})
export class BlogCategoriesModule {}