
Nestjs-queryでGraphQL APIを爆速で立ち上げる
2021/09/20
Nestjs-queryというGraphQLエンドポイントをいい感じで用意してくれるライブラリを使って、GraphQL APIを爆速?で立ち上げたいと思います 😏
Nestjs-queryはORMと併用して使う必要がありますが、ここではTypeOrmを採用したいと思います。
ORMはTypeOrm以外にsequelize, mongoose, typegooseが使用可能です。
この記事はNestjs-query公式サイトのExampleをベースに少しアレンジしたものになります。
Nestjs-queryに関してより詳細が知りたい方はそちらをご参考ください。
※ この記事の最終コードは以下のリポジトリにあげています。
https://github.com/highbridge326/nestjs-query-example
1. NestJSをset up
NestJSのcliを使ってプロジェクトを作成します。
$ npm i -g @nestjs/cli
$ nest new nestjs-query-example2. 依存パッケージをインストール
GraphQL APIを立ち上げる上で必要なパッケージを事前にインストールします。
Core
$ yarn add @nestjs-query/core @nestjs/common @nestjs/config apollo-server-express mysql2 class-transformergraphql関連
$ yarn add @nestjs-query/query-graphql @nestjs/graphql graphql graphql-subscriptions class-validator dataloadertypeorm関連
$ yarn add @nestjs-query/query-typeorm @nestjs/typeorm typeorm3. Moduleを作成
プロジェクト直下で以下のコマンドを実行し、Moduleを作成します。
$ nest g module todo-item4. Entityを定義
Moduleと同様にプロジェクト直下で以下のコマンドを実行し、Entityファイルを作成します。
$ nest g class todo-item.entity todo-item生成されたファイル内でTypeOrmを使ってEntityを定義していきます。
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity()
export class TodoItemEntity {
@PrimaryGeneratedColumn()
id!: string;
@Column()
title!: string;
@Column()
completed!: boolean;
@CreateDateColumn()
created!: Date;
@UpdateDateColumn()
updated!: Date;
}5. DTOを定義
以下のコマンドでDTOファイルを作成。
$ nest g class todo-item.dto todo-itemDTOを定義します。
import { FilterableField, IDField } from '@nestjs-query/query-graphql';
import { ObjectType, GraphQLISODateTime, Field, ID } from '@nestjs/graphql';
@ObjectType('TodoItem')
export class TodoItemDTO {
@IDField(() => ID)
id!: number;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@Field(() => GraphQLISODateTime)
created!: Date;
@Field(() => GraphQLISODateTime)
updated!: Date;
}6. Moduleに登録
作成したEntity、DTOをModuleに登録します。
import { NestjsQueryGraphQLModule } from '@nestjs-query/query-graphql';
import { NestjsQueryTypeOrmModule } from '@nestjs-query/query-typeorm';
import { Module } from '@nestjs/common';
import { TodoItemDTO } from './todo-item.dto';
import { TodoItemEntity } from './todo-item.entity';
@Module({
imports: [
NestjsQueryGraphQLModule.forFeature({
imports: [NestjsQueryTypeOrmModule.forFeature([TodoItemEntity])],
resolvers: [{ DTOClass: TodoItemDTO, EntityClass: TodoItemEntity }],
}),
],
})
export class TodoItemModule {}ここでのNestjsQueryGraphQLModuleがEntityとDTOをもとにいい感じにResolverを自動生成してくれます(生成されるGraphQLエンドポイントに関してはこの記事の最後にまとめています)。
7. DBのset up
最後に肝心のDBがまだset upできていませんので、docker-composeを利用してDBのset upを行います。
一例として以下のdocker-compose.ymlを用意します。
(DBにはMySQL(MariaDB)を採用しています。)
version: '3'
services:
db:
container_name: db
image: mariadb
restart: always
volumes:
- ./mariadb:/var/lib/mysql
env_file: .env
ports:
- $DB_PORT:$DB_PORT
adminer:
container_name: adminer
image: adminer
restart: always
ports:
- 8080:8080DB設定用の.envファイルもプロジェクト直下に用意します。
DB_HOST=localhost
DB_PORT=3306
MYSQL_ROOT_PASSWORD=password
MYSQL_DATABASE=nestjs-query-example
MYSQL_USER=user
MYSQL_PASSWORD=password最後にapp.module.tsにDB接続情報を記載して、すべての準備は完了です。
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { GraphQLModule } from '@nestjs/graphql';
import { TypeOrmModule } from '@nestjs/typeorm';
import { join } from 'path';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TodoItemModule } from './todo-item/todo-item.module';
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: '.env',
}),
GraphQLModule.forRoot({
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
type: 'mysql',
host: configService.get('DB_HOST'),
port: configService.get<number>('DB_PORT'),
username: configService.get('MYSQL_USER'),
password: configService.get('MYSQL_PASSWORD'),
database: configService.get('MYSQL_DATABASE'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
inject: [ConfigService],
}),
TodoItemModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}8. GraphQL APIの立ち上げ
docker-compose up -dでDBを立ち上げた後
yarn startでGraphQL APIが立ち上がります。
うまくいっていれば、http://localhost:3000/graphqlにアクセスするとgraphql playgroundが使えるようになっているはずです。
補足1:生成されるGraphQLエンドポイント
nestjs-queryによって生成されるエンドポイントは以下のとおりです。
findOne
{
todoItem(id: 1) {
id
title
completed
created
updated
}
}findMany
{
todoItems(filter: { completed: { is: true } }) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
node {
id
title
completed
created
updated
}
cursor
}
}
}createOne
mutation {
createOneTodoItem(
input: { todoItem: { title: "Create One Todo Item", completed: false } }
) {
id
title
completed
created
updated
}
}createMany
mutation {
createManyTodoItems(
input: {
todoItems: [
{ title: "Create Many Todo Items - 1", completed: false }
{ title: "Create Many Todo Items - 2", completed: true }
]
}
) {
id
title
completed
created
updated
}
}updateOne
mutation {
updateOneTodoItem(input: { id: 3, update: { completed: false } }) {
id
title
completed
created
updated
}
}updateMany
mutation {
updateManyTodoItems(
input: { filter: { id: { in: [1, 2] } }, update: { completed: true } }
) {
updatedCount
}
}
deleteOne
mutation {
deleteOneTodoItem(input: { id: 1 }) {
id
title
completed
created
updated
}
}deleteMany
mutation {
deleteManyTodoItems(
input: { filter: { title: { like: "Create Many Todo Items%" } } }
) {
deletedCount
}
}補足2:Relation
Entity同士にRelationがある場合はnestjs-queryでどう書けるか紹介します。
todo-itemに以下のようなsub-taskEntityが1対多で紐づく場合を考えてみます。
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ObjectType,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { TodoItemEntity } from '../todo-item/todo-item.entity';
@Entity()
export class SubTaskEntity {
@PrimaryGeneratedColumn()
id!: string;
@Column()
title!: string;
@Column()
completed!: boolean;
@Column({ nullable: false })
todoItemId!: string;
@ManyToOne(
(): ObjectType<TodoItemEntity> => TodoItemEntity,
(td) => td.subTasks,
{ onDelete: 'CASCADE', nullable: false },
)
@JoinColumn()
todoItem!: TodoItemEntity;
@CreateDateColumn()
created!: Date;
@UpdateDateColumn()
updated!: Date;
}import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
OneToMany,
} from 'typeorm';
import { SubTaskEntity } from '../sub-task/sub-task.entity';
@Entity()
export class TodoItemEntity {
@PrimaryGeneratedColumn()
id!: string;
@Column()
title!: string;
@Column()
completed!: boolean;
@OneToMany(() => SubTaskEntity, (subTask) => subTask.todoItem)
subTasks!: SubTaskEntity[];
@CreateDateColumn()
created!: Date;
@UpdateDateColumn()
updated!: Date;
}※ @OneToMany, @ManyToOneがtypeormでのRelationの定義になります。
次にこれまでの手順と同様にsub-taskのDTO、Moduleも以下の通り用意します。
import {
FilterableField,
IDField,
Relation,
} from '@nestjs-query/query-graphql';
import { ObjectType, ID, GraphQLISODateTime } from '@nestjs/graphql';
import { TodoItemDTO } from '../todo-item/todo-item.dto';
@ObjectType('SubTask')
@Relation('todoItem', () => TodoItemDTO, { disableRemove: true })
export class SubTaskDTO {
@IDField(() => ID)
id!: string;
@FilterableField()
title!: string;
@FilterableField()
completed!: boolean;
@FilterableField(() => GraphQLISODateTime)
created!: Date;
@FilterableField(() => GraphQLISODateTime)
updated!: Date;
@FilterableField()
todoItemId!: string;
}import { NestjsQueryGraphQLModule } from '@nestjs-query/query-graphql';
import { NestjsQueryTypeOrmModule } from '@nestjs-query/query-typeorm';
import { Module } from '@nestjs/common';
import { SubTaskDTO } from './sub-task.dto';
import { SubTaskEntity } from './sub-task.entity';
@Module({
imports: [
NestjsQueryGraphQLModule.forFeature({
imports: [NestjsQueryTypeOrmModule.forFeature([SubTaskEntity])],
resolvers: [{ DTOClass: SubTaskDTO, EntityClass: SubTaskEntity }],
}),
],
})
export class SubTaskModule {}上記のDTOclassでは@Relationデコレータが追記されています。
nestjs-queryではこの@RelationデコレータをDTOに追記するだけでsub-taskからtodo-itemを呼び出すことができるようになります。
sub-taskと同様にtodo-itemのDTOにも類似のデコレータを追記してRelationの設定は完了です。
@ObjectType('TodoItem')
@UnPagedRelation('subTasks', () => SubTaskDTO, { disableRemove: true })※ Relation先が復数ある場合は@Relationデコレータではなく@UnPagedRelationなどを使います。
おわり
これぐらいだったら爆速って言ってもいいよね...? 🤔
