diff --git a/__old_docs/pages/docs/plugins/database/pagination.mdx b/__old_docs/pages/docs/plugins/database/pagination.mdx deleted file mode 100644 index a3e0b8c50..000000000 --- a/__old_docs/pages/docs/plugins/database/pagination.mdx +++ /dev/null @@ -1,350 +0,0 @@ -import { Callout } from 'nextra/components'; - -# Pagination - -VitNode provide a pagination system using `cursor` pagination to help you retrieve data from the `database`. - - - VitNode doesn't support the `offset` pagination. - - -Here is an example of how to use the pagination system based on `blog_categories` table. - -## Data Transfer Object (DTO) - -We will create a `DTO` to create arguments and return values for the query. - -import { Steps } from 'nextra/components'; - - - -### Arguments - -File: `show/dto/show.args.ts` - -Arguments used as container for fields to create query in GraphQL schema. - -```ts -import { ArgsType } from '@nestjs/graphql'; - -import { PaginationArgs } from '@/types/database/pagination.type'; - -@ArgsType() -export class ShowBlogCategoriesArgs extends PaginationArgs {} -``` - -#### InputType - -If you want to use `InputType` you can use `PaginationInput` from `pagination.type`. - -```ts -import { ArgsType } from '@nestjs/graphql'; - -import { PaginationInput } from '@/types/database/pagination.type'; - -@InputType() -export class ShowBlogCategoriesArgs extends PaginationInput {} -``` - -### Object - -File: `show/dto/show.obj.ts` - -Object used as container for fields to create return values query in GraphQL schema. - -```ts -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; -} -``` - -`ShowBlogCategories` object is used to create a list of `blog_categories` objects. - - - -## Query GraphQL - - -### Service - -File: `show/show.service.ts` - -Inside service file we will create a `show` method that will return a `ShowBlogCategoriesObj` object. - -```ts -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 databaseServices: DatabaseService) {} - - async show({ - cursor, - first, - last, - }: ShowBlogCategoriesArgs): Promise {} -} -``` - -### Initial values for pagination - -We will use `inputPaginationCursor()` to create initial values for pagination. - -```ts {7-9, 20-35} -import { Injectable } from '@nestjs/common'; - -import { ShowBlogCategoriesArgs } from './dto/show.args'; -import { ShowBlogCategoriesObj } from './dto/show.obj'; - -import { DatabaseService } from '@/plugins/database/database.service'; -import { inputPaginationCursor } from '@/functions/database/pagination'; -import { blog_categories } from '../../admin/database/schema/categories'; -import { SortDirectionEnum } from '@/types/database/sortDirection.type'; - -@Injectable() -export class ShowBlogCategoriesService { - constructor(private databaseServices: DatabaseService) {} - - async show({ - cursor, - first, - last, - }: ShowBlogCategoriesArgs): Promise { - const pagination = await inputPaginationCursor({ - cursor, - database: blog_categories, - databaseService: this.databaseService, - first, - last, - primaryCursor: { - direction: SortDirectionEnum.asc, - column: 'id', - schema: blog_categories.id, - }, - defaultSortBy: { - direction: SortDirectionEnum.asc, - column: 'position', - }, - }); - } -} -``` - -### Query from database - -We will use `findMany()` method to get the data from the database with `with` argument to get the related data. - -```ts {27-29} -@Injectable() -export class ShowBlogCategoriesService { - constructor(private databaseServices: DatabaseService) {} - - async show({ - cursor, - first, - last, - }: ShowBlogCategoriesArgs): Promise { - const pagination = await inputPaginationCursor({ - cursor, - database: blog_categories, - databaseService: this.databaseService, - first, - last, - primaryCursor: { - direction: SortDirectionEnum.asc, - column: 'id', - schema: blog_categories.id, - }, - defaultSortBy: { - direction: SortDirectionEnum.asc, - column: 'position', - }, - }); - - const edges = await this.databaseService.db.query.blog_categories.findMany({ - ...pagination, - }); - } -} -``` - -#### Where argument - -If you want to use `where` argument you can pass it to the `findMany()` method using `and()` method from `drizzle-orm`. - -```ts {2, 38} -import { Injectable } from '@nestjs/common'; -import { and, lte } from 'drizzle-orm'; - -import { ShowBlogCategoriesArgs } from './dto/show.args'; -import { ShowBlogCategoriesObj } from './dto/show.obj'; - -import { DatabaseService } from '@/plugins/database/database.service'; -import { inputPaginationCursor } from '@/functions/database/pagination'; -import { blog_categories } from '../../admin/database/schema/categories'; -import { SortDirectionEnum } from '@/types/database/sortDirection.type'; - -@Injectable() -export class ShowBlogCategoriesService { - constructor(private databaseServices: DatabaseService) {} - - async show({ - cursor, - first, - last, - }: ShowBlogCategoriesArgs): Promise { - const pagination = await inputPaginationCursor({ - cursor, - database: blog_categories, - databaseService: this.databaseService, - first, - last, - primaryCursor: { - direction: SortDirectionEnum.asc, - column: 'id', - schema: blog_categories.id, - }, - defaultSortBy: { - direction: SortDirectionEnum.asc, - column: 'position', - }, - }); - - const where = lte(blog_categories.created, new Date()); - - const edges = await this.databaseService.db.query.blog_categories.findMany({ - ...pagination, - where: and(pagination.where, where), - }); - } -} -``` - -### Return values - -We will use `outputPagination()` to create return values for the query. Remember to create a query `totalCount` to get the total count of the query. - -```ts {2, 10, 45-47, 49} -import { Injectable } from '@nestjs/common'; -import { count } from 'drizzle-orm'; - -import { ShowBlogCategoriesArgs } from './dto/show.args'; -import { ShowBlogCategoriesObj } from './dto/show.obj'; - -import { DatabaseService } from '@/plugins/database/database.service'; -import { - inputPaginationCursor, - outputPagination, -} from '@/functions/database/pagination'; -import { blog_categories } from '../../admin/database/schema/categories'; -import { SortDirectionEnum } from '@/types/database/sortDirection.type'; - -@Injectable() -export class ShowBlogCategoriesService { - constructor(private databaseServices: DatabaseService) {} - - async show({ - cursor, - first, - last, - }: ShowBlogCategoriesArgs): Promise { - const pagination = await inputPaginationCursor({ - cursor, - database: blog_categories, - databaseService: this.databaseService, - first, - last, - primaryCursor: { - direction: SortDirectionEnum.asc, - column: 'id', - schema: blog_categories.id, - }, - defaultSortBy: { - direction: SortDirectionEnum.asc, - column: 'position', - }, - }); - - const edges = await this.databaseService.db.query.blog_categories.findMany({ - ...pagination, - }); - - const totalCount = await this.databaseService.db - .select({ count: count() }) - .from(blog_categories); - - return outputPagination({ edges, totalCount, first, cursor, last }); - } -} -``` - -If you have `where` argument you need to pass it to the `count()` method. - -```ts {4} -const totalCount = await this.databaseService.db - .select({ count: count() }) - .from(blog_categories) - .where(where); -``` - -#### Modyfy return values - -If you want to modify the return values you can use `map()` method to modify the return values. - -```ts {2-6} -return outputPagination({ - edges: edges.map(edge => ({ - ...edge, - name: edge.title, - description: edge.description, - })), - totalCount, - first, - cursor, - last, -}); -``` - - - -## Output - -```JSON -{ - "edges": [], - "pageInfo": { - "hasNextPage": false, - "startCursor": "", - "endCursor": "", - "totalCount": 0, - "count": 0 - } -} -``` diff --git a/apps/docs/content/docs/dev/database-migrations.mdx b/apps/docs/content/docs/dev/database-migrations.mdx index 70a42016f..b9a38cb84 100644 --- a/apps/docs/content/docs/dev/database-migrations.mdx +++ b/apps/docs/content/docs/dev/database-migrations.mdx @@ -9,7 +9,7 @@ You don't need to worry about migration when you're using VitNode, we will handl When you install, update or remove a plugin, VitNode will automatically generate and apply the migration to your database. -### Manual +## Manual If you're making a plugins and you don't want to restart the server, you can generate and apply the migration manually. diff --git a/apps/docs/content/docs/dev/database-pagination.mdx b/apps/docs/content/docs/dev/database-pagination.mdx new file mode 100644 index 000000000..0b782135a --- /dev/null +++ b/apps/docs/content/docs/dev/database-pagination.mdx @@ -0,0 +1,156 @@ +--- +title: Pagination +description: How to use cursor based pagination with database. +--- + + + VitNode doesn't support the `offset` pagination. You can create your own + offset pagination. + + +## Data Transfer Object (DTO) + +We will create a `DTO` to create arguments and return values for the query. + +### Object + +Object used as container for fields to create return values query in GraphQL schema. + +```ts title="show/dto/show.args.ts" +import { Field, Int, ObjectType } from '@nestjs/graphql'; +import { PageInfo } from 'vitnode-backend'; // [!code highlight] + +@ObjectType() +export class ShowTest { + @Field(() => Int) + id: number; + + @Field(() => String) + name: string; +} + +@ObjectType() +export class ShowTestObj { + @Field(() => [ShowTest]) + edges: ShowTest[]; + + @Field(() => PageInfo) // [!code highlight] + pageInfo: PageInfo; // [!code highlight] +} +``` + +### Arguments + +Arguments used as container for fields to create query in GraphQL schema. + +```ts title="show/dto/show.args.ts" +import { ArgsType } from '@nestjs/graphql'; +import { PaginationArgs } from 'vitnode-backend'; // [!code highlight] + +@ArgsType() +// [!code word:PaginationArgs] +export class ShowTestArgs extends PaginationArgs {} +``` + +## Service + +Inside service file we will create a `show` method that will return a `ShowTestObj` object. + +```ts title="show/show.service.ts" +import { Injectable } from '@nestjs/common'; + +import { ShowTestArgs } from './dto/show.args'; +import { ShowTestObj } from './dto/show.obj'; + +@Injectable() +export class ShowTestService { + async show({ cursor, first, last }: ShowTestArgs): Promise {} +} +``` + +### Initial values for pagination + +Use `inputPaginationCursor()` to create initial values for pagination. + +```ts title="show/show.service.ts" +import { Injectable } from '@nestjs/common'; +// [!code word:inputPaginationCursor] +import { inputPaginationCursor, SortDirectionEnum } from 'vitnode-backend'; + +import { ShowTestArgs } from './dto/show.args'; +import { ShowTestObj } from './dto/show.obj'; + +import { test_table } from '../../admin/database/schema/test'; +import { DatabaseService } from '@/database/database.service'; + +@Injectable() +export class ShowTestService { + constructor(private readonly databaseService: DatabaseService) {} + + async show({ cursor, first, last }: ShowTestObj): Promise { + const pagination = await inputPaginationCursor({ + cursor, + database: test_table, + databaseService: this.databaseService, + first, + last, + primaryCursor: { + column: 'id', + schema: test_table.id, + }, + defaultSortBy: { + direction: SortDirectionEnum.asc, + column: 'created', + }, + }); + } +} +``` + +### Query from database + +Use `findMany()` method to get the data from the database with `with` argument to get the related data. + +```ts title="show/show.service.ts" +const edges = await this.databaseService.db.query.test_table.findMany({ + ...pagination, +}); +``` + +#### Where argument + +If you want to use `where` argument you can pass it to the `findMany()` method using `and()` method from `drizzle-orm`. + +```ts title="show/show.service.ts" +const where = lte(test_table.created, new Date()); // [!code highlight] + +const edges = await this.databaseService.db.query.test_table.findMany({ + ...pagination, + where: and(pagination.where, where), // [!code highlight] +}); +``` + +### Return values + +We will use `outputPagination()` to create return values for the query. + + + Remember to create a query `totalCount` to get the total count of the query. + + +```ts title="show/show.service.ts" +const totalCount = await this.databaseService.db + .select({ count: count() }) + .from(test_table); + +return outputPagination({ edges, totalCount, first, cursor, last }); +``` + +If you have `where` argument you need to pass also to the `count()` query. + +```ts {4} +const totalCount = await this.databaseService.db + .select({ count: count() }) + .from(test_table) + .where(where); // [!code highlight] +``` diff --git a/apps/docs/content/docs/dev/database-service.mdx b/apps/docs/content/docs/dev/database-service.mdx index 507bdf096..34360e23e 100644 --- a/apps/docs/content/docs/dev/database-service.mdx +++ b/apps/docs/content/docs/dev/database-service.mdx @@ -3,10 +3,106 @@ title: Database in Service description: How to use database in service --- +To get access to the database in a service, you need to inject the `DatabaseService` into the service constructor. + +```ts title="show.service.ts" +import { Injectable } from '@nestjs/common'; + +import { DatabaseService } from '@/database/database.service'; + +@Injectable() +export class ExampleService { + constructor(private readonly database: DatabaseService) {} // [!code highlight] +} +``` + ## Query -VitNode supports [query by Drizzle ORM](https://orm.drizzle.team/docs/rqb) in service. +VitNode supports [query by Drizzle ORM](https://orm.drizzle.team/docs/rqb) in service to get data with relations from the database. ```ts title="show.service.ts" +import { Injectable } from '@nestjs/common'; + +import { DatabaseService } from '@/database/database.service'; + +@Injectable() +export class ExampleService { + constructor(private readonly database: DatabaseService) {} + + async create(): Promise { + // [!code word:this.database.db.query.core_test_table.findMany] + const data = await this.database.db.query.core_test_table.findMany({ + where: (table, { eq }) => eq(table.id, 1), + }); + } +} +``` + +Check [Drizzle ORM documentation - Query](https://orm.drizzle.team/docs/rqb) for more information. + +## Select + +```ts +await this.database.db.select().from(core_test_table); +``` + +Check [Drizzle ORM documentation - Select](https://orm.drizzle.team/docs/select) for more information. + +## Insert + +```ts +await this.database.db.insert(core_test_table).values({ name: 'Andrew' }); +``` +Check [Drizzle ORM documentation - Insert](https://orm.drizzle.team/docs/insert) for more information. + +## Update + +```ts +await this.database.db + .update(core_test_table) + .set({ name: 'Mr. Dan' }) + .where(eq(core_test_table.name, 'Dan')); +``` + +Check [Drizzle ORM documentation - Update](https://orm.drizzle.team/docs/update) for more information. + +## Delete + +```ts +await this.database.db + .delete(core_test_table) + .where(eq(core_test_table.name, 'Dan')); +``` + +Check [Drizzle ORM documentation - Delete](https://orm.drizzle.team/docs/delete) for more information. + +## Operations + +[Drizzle ORM documentation - Operations](https://orm.drizzle.team/docs/operators) supports many operations like `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `like`, `ilike`, `is`, `isNot`, `between`, `notBetween`, etc. + +## Joins + +```ts +await this.database.db + .select() + .from(core_test_table) + .join(core_test_table2) + .on(eq(core_test_table.id, core_test_table2.id)); ``` + +Check [Drizzle ORM documentation - Joins](https://orm.drizzle.team/docs/delete) for more information. + +## Magic SQL + +You can use magic SQL to create raw SQL query with Drizzle ORM. + +```ts +import { sql } from 'drizzle-orm'; +const id = 69; +await db.execute( + sql`select * from ${usersTable} where ${usersTable.id} = ${id}`, +); +``` + +Check [Drizzle ORM documentation - Magic SQL](https://orm.drizzle.team/docs/sql) for more information. diff --git a/apps/docs/content/docs/dev/meta.json b/apps/docs/content/docs/dev/meta.json index 9b9d11eef..0b786c07d 100644 --- a/apps/docs/content/docs/dev/meta.json +++ b/apps/docs/content/docs/dev/meta.json @@ -14,6 +14,7 @@ "database", "database-migrations", "database-service", + "database-pagination", "--- GraphQL ---", "graphql", "modules", diff --git a/packages/backend/src/functions/pagination.ts b/packages/backend/src/functions/pagination.ts index 6d37ad469..0ab65b95f 100644 --- a/packages/backend/src/functions/pagination.ts +++ b/packages/backend/src/functions/pagination.ts @@ -165,6 +165,6 @@ export async function inputPaginationCursor({ return { where, orderBy, - limit: first || last ? ((last ? last + 1 : first) ?? 0 + 1) : undefined, + limit: first || last ? ((last ? last + 1 : first) ?? 0) + 1 : undefined, }; }