GoのREST APIをNestJSへ置き換える作業メモ
今GoでホスティングしているREST APIをNestとGraphQLに置き換えようと思う。
日々の運動を記録するアプリ。
構成は、App RunnerとSQLite with Litestream。昔記事を書いた。
App RunnerとAurora Serverless V1構成でそこまで高くならなそうなので、ゆくゆくはそちらにホスティング先を変えたい。
直でDB更新できるのがメリット。AppRunner + SQLite構成は、App Runnerコンテナに直接exec出来ないので、DBを直で触れないという問題がある。まぁその分低コストなんだけどね、、
一番のモチベは、Auroraと仲良くなりたいだけなんだけどね。
userモジュールの作成
npx nest generate module users
npx nest generate class users/user
npx nest generate resolver users
npx nest generate service users
mkdir src/users/dto && touch src/users/dto/dto/newUser.input.ts
要素 | 説明 | コマンド | 生成されるファイル | 更新されるファイル |
---|---|---|---|---|
module | npx nest generate module users |
src/users/users.module.ts |
src/app.module.ts |
|
class | npx nest generate class users/user |
src/users/user.spec.ts src/users/user.ts
|
||
resolver | npx nest generate resolver users |
src/users/users.resolver.spec.ts src/users/users.resolver.ts
|
src/users/users.module.ts |
|
service | npx nest generate service users |
src/users/users.service.spec.ts src/users/users.service.ts
|
src/users/users.module.ts |
|
DTO | mkdir src/users/dto && touch src/users/dto/newUser.input.ts |
src/users/dto/newUser.input.ts |
.gql
ファイル更新タイミング
@Filed
をつけた値が、.gql
ファイルのスキーマに出力される。ただこの設定だけでは、.gql
ファイルに設定は反映されない。src/users/users.resolver.spec.ts
でQueryを定義する必要がある。
import { Field, ID, ObjectType } from '@nestjs/graphql';
import { Column, Entity } from 'typeorm';
@Entity()
@ObjectType()
export class User {
@Field((type) => ID)
id: number;
@Field()
@Column()
name: string;
@Field()
@Column()
createdAt: number;
@Field()
@Column()
modified: number;
}
定義したclassでテーブルを作成する場合
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),
TypeOrmModule.forRoot({
type: 'mysql',
host: host,
port: 3306,
username: username,
password: password,
database: dbname,
entities: [Book, User], // 👈ここに作成したクラスを定義する
synchronize: true,
}),
BooksModule,
UsersModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
つまりテーブル定義はアプリ側でなく、SQLで作成するという方法も可能。そうしたい人も多そう。(descしないと何が作られたか確認できないし...)
Connectionの作り方がわからない。ここら辺が参考になりそう
普通に公式に載っているパターン
関係ないけど後で見る
最終的にはこちらで解決 ResolveFiled
を使う
他にも参考になりそうなスクラップ発見
workOuts
のようなCamelCaseだとwork-out
のようなスネークケースに変換されて出力される
npx nest generate module workOuts
npx nest generate class workOuts/workOut
npx nest generate resolver workOuts
npx nest generate service workOuts
mkdir src/work-outs/dto && touch src/work-outs/dto/newWorkOut.input.ts
以下のような場合、JOINしてリクエストすれば1回で済むのにアプリの作りとして2回リクエストが必要。解決策を模索中。
- getUserでユーザー詳細取得
- userIdでユーザーに紐づく複数の概念の取得
@Resolver((of) => User)
export class UsersResolver {
constructor(private usersService: UsersService) {}
// ユーザーに1:複で紐づく概念
@ResolveField((type) => UserWorkoutConnection)
userHoge(@Parent() { id }: User, @Args() args?: ConnectionArgs): any {
// getUserで返却された値のidが@Parent経由で取得可能
// TODO: idを元に複数の概念の取得
return {
edges: [],
nodes: [],
totalCount: 0,
hasNextPage: false,
};
}
@Query(() => User)
getUser(
@Args({ name: 'userId', type: () => String }) userId: string,
): Promise<User> {
// TODO: Userの詳細情報取得
return Promise.resolve({
id: userId,
name: 'shuntaka',
createdAt: 1665972123,
modifiedAt: 1665972123,
});
}
}
前項の問題は、N+1にはならないけど、dataloader使えばうまくいきそう(?)
この例も人と記事をJOINすれば、1回で取れるからこの問題解決できなそうだなぁ。
ユーザー詳細のみを取得した場合、と複数概念取得したい場合で柔軟性もたさられるから許容するのがいいのかなぁ、、
N+1になるケース以下の例とかかな。
- ユーザー取得
- ユーザーIDを元に複数概念を取得
- ユーザー分繰り返す(2でN+1発生)
今回のは多くてもリクエスト2回だから今のところは許容するかー。
usersクエリを作るところは、dataloader活用しよう。
まさにこれで、不要な場合もJOINしちゃうんだよね。
不要な Post を取得してしまいます。クエリが重くなる可能性がありますし、何より「必要なデータのみ取得して返す」GraphQL の基本的な考え方にも背いているように見えます
dataloader魔法味がある