๐Ÿ› ๏ธ VitNode is still in development! You can try it out, but it is not recommended to use it now in production.
๐Ÿ”Œ Plugins
GraphQL

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 {}