NestJSで、Stripe APIを呼び出すGraphQL APIを作る方法

この記事は、[GraphQL Advent Calendar 2022](https://qiita.com/advent-calendar/2022/graphql) 17日目の記事です。

Stripeのデータ取得は、基本的にREST APIを通して行います。

しかし、複数のデータソース（商品と料金など）を横断したいケースや、必要なデータのみ取得するクエリを書きたい場合など、GraphQL APIでの実装が向いているパターンも存在します。

今回の記事では、NestJSを利用して、Stripeのデータを取得するGraphQL APIを作る方法を紹介します。

## プロジェクトのセットアップ

NestJSでプロジェクトを作成する場合は、NestJS CLIを利用します。

```bash
$ npm i -g @nestjs/cli
$ nest new project-name
```

### Stripe SDKを利用する準備

続いて、Stripe SDKをNestJSから呼び出す準備をしましょう。

```bash
$ npm install nestjs-stripe @nestjs/config

// 環境変数でStripeのシークレット/制限付きAPIキーを設定
$ echo "STRIPE_SECRET_API_KEY=rk_test_xxx" > .env.local 
```

設定内容の詳細については、以下の記事をご一読ください。

https://qiita.com/hideokamoto/items/8fd630bd45effd397a5e

### GraphQL

最後にGraphQL APIを実装するためのライブラリをインストールします。

```bash
$ npm i @nestjs/graphql @nestjs/apollo graphql apollo-server-express
```

### `app.module.ts`に設定を追加する

インストールしたモジュールなどを利用できるようにしましょう。

```ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
import { StripeModule } from 'nestjs-stripe';
import { ConfigModule, ConfigService } from '@nestjs/config';


@Module({
  imports: [
    // 環境変数の読み込み
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: ['.env.local']
    }),

    // Stripe SDKの読み込み
    StripeModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        apiKey: configService.get('STRIPE_SECRET_API_KEY'),
        apiVersion: '2022-08-01'
      })
    }),

    // GraphQLの読み込み
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      debug: false,
      playground: true,
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
      sortSchema: true
    })
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```

## GraphQL APIを実装する

準備ができましたので、実装を開始します。

### GraphQLのデータモデルを作成しよう

GraphQLのスキーマやNestJSでの実装のベースになるデータモデルを作成しましょう。

```bash
$ nest generate class model
CREATE src/model/model.spec.ts (143 bytes)
CREATE src/model/model.ts (22 bytes)
```

`src/model/model.ts`に、Product(商品)とPrice(料金)の型を簡単に定義します。

```ts
import { Field, ID, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class Price {
    @Field(type => ID)
    id: string;

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

    @Field()
    currency: string;
    
    @Field()
    unit_amount: number
}

@ObjectType()
export class Product {
    @Field(type => ID)
    id: string;

    @Field({ nullable: true })
    name: string;

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

    @Field(() => [Price], { nullable: true})
    prices?: Price[]
}
```

https://docs.nestjs.com/graphql/resolvers#object-types

### Stripe APIを呼び出すServiceを作成する

Stripe SDKを呼び出す処理は、Serviceとして分離しておきましょう。

```bash
$ nest generate service stripe-api
CREATE src/stripe-api/stripe-api.service.spec.ts (482 bytes)
CREATE src/stripe-api/stripe-api.service.ts (93 bytes)
UPDATE src/app.module.ts (254 bytes)
```

商品一覧の取得処理と、商品のIDから料金を取得する処理を追加しました。

```ts
import { Injectable } from '@nestjs/common';
import { InjectStripe } from 'nestjs-stripe';
import Stripe from 'stripe';

@Injectable()
export class StripeApiService {
    constructor(
        @InjectStripe() private readonly stripe: Stripe
    ) {}

    public async listProducts() {
        return this.stripe.products.list()
    }
    public async findPricesByProductId(productId: string) {
        return this.stripe.prices.list({
            product: productId
        })
    }
}
```

### Resolverを作成しよう

GraphQLのAPIリクエストは、Resolverを通して処理されます。

NestJSのCLIで、Resolverを作成しましょう。

```bash
$ nest generate resolver stripe-api
CREATE src/stripe-api/stripe-api.resolver.spec.ts (492 bytes)
CREATE src/stripe-api/stripe-api.resolver.ts (91 bytes)
UPDATE src/app.module.ts (254 bytes)
```

Resolverから、先ほど作成したServiceを呼び出して処理を実装しましょう。

```ts
import { Resolver, Query, ResolveField, Parent } from '@nestjs/graphql';
import { Product } from './model/model';
import { StripeApiService } from './stripe-api.service';

@Resolver(of => Product)
export class StripeApiResolver {
    constructor(
        private readonly stripeAPIService: StripeApiService
    ) {}

    @Query(() => [Product])
    async findProducts() {
        const { data:products } = await this.stripeAPIService.listProducts()
        return products
    }

    @ResolveField()
    async prices(@Parent() product) {
        const { id } = product
        const { data:prices } = await  this.stripeAPIService.findPricesByProductId(id)
        return prices
    }
}
```

`ResolveField`を利用して、`findProducts`クエリーから料金データの取得を行なっています。

https://docs.nestjs.com/graphql/resolvers


## GraphQL Playgroundで動作を確認する

NestJSで作成したAPIは、REST API同様に`npm start:dev`でローカル環境で立ち上げることができます。

また、GraphQLの場合、`http://localhost:3000/graphql`へアクセスすると、Playgroundが表示されます。

![スクリーンショット 2022-11-08 19.31.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/b4e21fdd-21b1-8ce2-9e75-a71bc249ceae.png)

Playgroundを利用して、意図したデータが取得できているかなどを確認しましょう。

## 終わりに

今回の記事では、Stripe APIを呼び出すGraphQL APIの実装方法を紹介しました。

実際のユースケースでは、AWSやGCPなどにホストされた別のデータベース情報なども含めたデータの取得を行う可能性もあります。

NestJSとGraphQLの機能を使いこなして、柔軟なAPI開発を進めましょう。