🙆

NestJsの環境構築をしてPlaygroundを起動するまで

2023/01/15に公開

Nest CLIでプロジェクト作成

npm i -g @nestjs/cl
nest n nestjs_sample

パッケージマネージャーは今回はnpmを選択する。

サンプルの削除

  1. サンプルファイルの削除
rm ./src/app.controller.ts ./src/app.controller.spec.ts ./src/app.service.ts
  1. controllers,providersから削除したファイルの設定を削除
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: `.env.${process.env.NODE_ENV}`,
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule { }

GraphQLの設定

  1. GraphQL関連のライブラリのインストール
npm i graphql @nestjs/graphql @nestjs/apollo class-validator
  1. Apollo Serverの設定+スキーマファイルを自動生成するように設定
app.module.ts
import { ApolloDriverConfig, ApolloDriver } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';

@Module({
  imports: [
+    GraphQLModule.forRoot<ApolloDriverConfig>({
+     driver: ApolloDriver,
+     autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
+     sortSchema: true,
+   }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule { }

スキーマのmoduleを作成する

今回はschema配下に各moduleを追加していく方針にする。そのため、まずschema.module.tsを作成する。

  1. schema.module.tsを作成する。
nest g module schema

schema.module.tsが作成されると、自動でapp.module.tsに設定を追加してくれる。

app.module.ts
import { ApolloDriverConfig, ApolloDriver } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
import { SchemaModule } from './schema/schema.module';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
      sortSchema: true,
    }),
+   SchemaModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

DTOを作成する

以下の構成でDTOを作成する。

src
└── schema
    └── users
        └── dto
            ├── args
            │   ├── user.args.ts
            │   └── users.args.ts
            ├── input
            │   ├── createUser.input.ts
            │   └── deleteUser.input.ts
            └── models
                └── user.model.ts
  • model:返却値
  • args:Queryの場合の引数
  • input:Mutationsの場合の引数

model

mkdir -p ./src/schema/users/dto/models/
touch ./src/schema/users/dto/models/user.model.ts
user.model.ts
import { Field, Int, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class User {
  @Field(() => Int)
  id: number;

  @Field({ nullable: true })
  firstName?: string;

  @Field({ nullable: true })
  lastName?: string;
}

args

mkdir -p ./src/schema/users/dto/args/
touch ./src/schema/users/dto/args/user.args.ts
touch ./src/schema/users/dto/args/users.args.ts
user.args.ts
import { ArgsType, Field } from '@nestjs/graphql';
import { IsNumber } from 'class-validator';

@ArgsType()
export class UserArgs {
  @Field({ nullable: true, description: 'ID' })
  @IsNumber()
  id: number;
}
users.args.ts
import { ArgsType, Field } from '@nestjs/graphql';
import { IsNotEmpty, MaxLength } from 'class-validator';

@ArgsType()
export class UsersArgs {
  @Field({ nullable: true, description: '名' })
  @IsNotEmpty()
  @MaxLength(30)
  firstName?: string;

  @Field({ nullable: true, description: '姓' })
  @IsNotEmpty()
  @MaxLength(10)
  lastName?: string;
}

input

mkdir -p ./src/schema/users/dto/input/
touch ./src/schema/users/dto/input/createUser.input.ts
touch ./src/schema/users/dto/input/deleteUser.input.ts
createUser.input.ts
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty, MaxLength } from 'class-validator';

@InputType()
export class CreateUserInput {
  @Field({ nullable: false, description: '名' })
  @IsNotEmpty()
  @MaxLength(30)
  firstName?: string;

  @Field({ nullable: false, description: '姓' })
  @IsNotEmpty()
  @MaxLength(10)
  lastName?: string;
}
deleteUser.input.ts
import { Field, InputType } from '@nestjs/graphql';
import { IsNumber } from 'class-validator';

@InputType()
export class DeleteUserInput {
  @Field({ nullable: false, description: 'ID' })
  @IsNumber()
  id: number;
}

サンプルのmodule,service,resolverを作成する

nest g module schema/users
nest g service schema/users
nest g resolver schema/users

コマンドを実行すると以下のようにファイルが作成される。

src
└── schema
    └── users
        ├── users.module.ts
        ├── users.resolver.spec.ts
        ├── users.resolver.ts
        ├── users.service.spec.ts
        └── users.service.ts

~spec.tsはテストクラスなので今回はノータッチ。

また、schema.module.tsに自動でapp.module.tsに設定を追加してくれる。

schema.module.tsが作成されると、自動でschema.module.tsに設定を追加してくれる。

schema.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';

@Module({
+ imports: [UsersModule]
})
export class SchemaModule {}

resolver

users.resolver.ts
import {
  Args,
  Mutation,
  Query,
  Resolver,
} from '@nestjs/graphql';
import { UserArgs } from './dto/args/user.args';
import { UsersArgs } from './dto/args/users.args';
import { CreateUserInput } from './dto/input/createUser.input';
import { DeleteUserInput } from './dto/input/deleteUser.input';
import { User } from './dto/models/user.model';
import { UsersService } from './users.service';

@Resolver()
export class UsersResolver {
  constructor(private usersService: UsersService) {}

  @Query(() => User, { description: 'ユーザーを取得します。' })
  async user(@Args() userArgs: UserArgs) {
    const { id } = userArgs;
    return this.usersService.findOneById(id);
  }

  @Query(() => [User], { description: 'ユーザー一覧を取得します。' })
  async users(@Args() usersArgs: UsersArgs) {
    return this.usersService.findAll(usersArgs);
  }

  @Mutation(() => User, { description: 'ユーザーを登録します。' })
  async createUser(@Args("input") createUserInput: CreateUserInput) {
    return this.usersService.create(createUserInput);
  }

  @Mutation(() => Boolean, { description: 'ユーザーを削除します。' })
  async deleteUser(@Args("input") deleteUserInput: DeleteUserInput) {
    const { id } = deleteUserInput;
    return this.usersService.delete(id);
  }
}

service

サンプルなので返却値は固定。

users.service.ts
import { Injectable } from '@nestjs/common';
import { UsersArgs } from './dto/args/users.args';
import { CreateUserInput } from './dto/input/createUser.input';
import { User } from './dto/models/user.model';

const user: User = {
  id: 1,
  firstName: "太郎",
  lastName: "山田"
}

@Injectable()
export class UsersService {
  async create(input: CreateUserInput): Promise<User> {
    return {
      id: 1,
      firstName: input.firstName,
      lastName: input.lastName
    };
  }

  async findOneById(id: number): Promise<User> {
    return user;
  }

  async findAll(userArgs: UsersArgs): Promise<User[]> {
    return [user] as User[];
  }

  async delete(id: number): Promise<boolean> {
    return true;
  }
}

GraphQL Playgroundを起動する

package.jsonのscriptsを確認すると、buildlintなどのスクリプトが並んでいる。

package.json
{
  "scripts": {
    "prebuild": "rimraf dist",
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  },
}

このうち、start~が起動のスクリプトなのでそちらを使用する。今回はstart:devを使用する。

npm start start:dev

上記のコマンドを実行するとGraphQL Playgroundが立ち上がるので以下のURLにアクセスする。

http://localhost:3000/graphql

左ペインでクエリを実行すると、右ペインに実行結果が表示される。

また、schemaファイルの自動生成の設定がされているので、起動時にschema.gqlが作成される。

query,mutationを実行する

今回はuser,users,createUser,deleteUserの4つがあるので、以下のように実行すると、それぞれ実行結果が得られる。

user
query {
  user(id:1) {
    id
    firstName
    lastName
  }
}
users
query {
  users {
    id
    firstName
    lastName
  }
}
createUser
mutation {
  createUser(input: {
   firstName: "太郎",
   lastName: "山田"
  }) {
    firstName
    lastName
  }
}
deleteUser
mutation {
  deleteUser(input: {
   id: 1
  })
}

Discussion