NestJSをAPI Gateway + AWS Lambdaで構成する（その３）- Controllersコントローラー

#
- [その１ NestJSやLambdaでのnode.jsの話し](https://qiita.com/ssugimoto/items/988320d34022555401fb)
- [その２ NestJSのサンプルを動かす、Hello World.](https://qiita.com/ssugimoto/items/557e7a7cce5111bf5de7)
- その３ コントローラーを追加する
- [その４ DTOを使う](https://qiita.com/ssugimoto/items/613b8ab54bd6f8de043c)
- [その５ CRUD generatorを使い modules,controller, service, dto を生成する](https://qiita.com/ssugimoto/items/64a9c3cd5cb33e06f4c9)
- [その６ インターセプター](https://qiita.com/ssugimoto/items/dce542a78fdfbaf1e434)
- [その７ ORMでRDBデータベースを扱う](https://qiita.com/ssugimoto/items/45e23d1b2879b083267c)
- [その８ NestJSをAPI Gateway＋AWS Lambdaとしてデプロイ](https://qiita.com/ssugimoto/items/74c26a8219a944d4acc4)

# Controllers（コントローラー）
## 公式 
https://8-0-0--docs-nestjs.netlify.app/controllers

## コントロールはざっくり
- サーバー側としてHttp リクエストを処理する起点となるところ
- リクエストされるURLの部分と処理するclassを紐付けるのはNestJSのフレームワーク側でやってくれる

`aws-node-typescript-nest/src/app.controller.ts`をみてみると、

```aws-node-typescript-nest/src/app.controller.ts
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) { }

  @Get('hello')
  getHello(): string {
    return this.appService.getHello();
  }
}
```
- `@Get('hello')` が http://localhost:3000/hello にマッピングされる
- `/hello` にリクエストがきたら、`@Get('hello')` を指定する getHello() が動作する

### Get, Post リクエスト
- `@Get('hello')` は Getメソッドリクエスト対応なので、http://localhost:3000/hello に対して Postメソッドリクエストを送ると対応するHttpメソッドがないため404になります。

404 Not Foundの場合のレスポンス
```json
{
    "statusCode": 404,
    "error": "Not Found",
    "message": "Cannot POST /hello"
}
```

serverless.yml では、 methdoはanyなのでGetでもPostでも受付る
```aws-node-typescript-nest/serverless.yml
functions:
  main:
    handler: src/main.handler
    events:
      - http:
          method: any
          path: /{proxy+}
```

- Postメソッドリクエストで動作するようにするには、以下のようにすれば良い
```ts
  @Post('hello')
  getHello(): string {
    return this.appService.getHello();
  }
```

## コントローラーの追加

usage:
```
$ nest g controller [コントローラー名]
```

### 実行例
```shell
nest g controller cats
```

以下が生成される
- src/cats/cats.controller.ts
- src/cats/cats.controller.spec.ts   ←テスト用ファイル

以下が更新される、追加した catsがmoduleに追加され リクエストを受けるエンドポイントとして認識されるようになる
src/app.module.ts

```ts
...
import { CatsController } from './cats/cats.controller';

...

@Module({
  imports: [],
  controllers: [AppController, CatsController],
  providers: [AppService],
})
export class AppModule {}
```

### 追加したコントローラーにリクエストを送る

リクエスト
```shell
$ curl -X "GET"  http://localhost:3000/dev/cats
```

レスポンス
```shell
This action returns all cats
```

- 末尾のスラッシュ有無では違いなし
```
$ curl -X "GET"  http://localhost:3000/dev/cats
$ curl -X "GET"  http://localhost:3000/dev/cats/
```

### 追加したコントローラーにリクエストパラメータ付きのリクエストを送る


#### a=1というパラメータをつける
- CatsControllerでは、`console.log({ request });` を追加してログを出力する

```
$ curl -X "GET"  http://localhost:3000/dev/cats/?a=1
```

- console.logの内容を確認
```log
[Nest] 2014   - 7/19/2023, 1:07:29 PM   [NestApplication] Nest application successfully started +1ms
{
  request: <ref *1> IncomingMessage {
    _readableState: ReadableState {
      objectMode: false,
      highWaterMark: 16384,
      buffer: BufferList { head: null, tail: null, length: 0 },
      length: 0,
      pipes: [],
      flowing: null,
      ended: true,
      endEmitted: false,
      reading: false,
      constructed: true,
      sync: true,
      needReadable: false,
      emittedReadable: false,
      readableListening: false,
      resumeScheduled: false,
      errorEmitted: false,
      emitClose: true,
      autoDestroy: true,
      destroyed: false,
      errored: null,
      closed: false,
      closeEmitted: false,
      defaultEncoding: 'utf8',
      awaitDrainWriters: null,
      multiAwaitDrain: false,
      readingMore: true,
      dataEmitted: false,
      decoder: null,
      encoding: null,
      [Symbol(kPaused)]: null
    },
    _events: [Object: null prototype] { end: [Function: clearRequestTimeout] },
    _eventsCount: 1,
    _maxListeners: undefined,
    socket: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 8,
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: true,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: [Server],
      _server: [Server],
      parser: [HTTPParser],
      on: [Function: socketListenerWrap],
      addListener: [Function: socketListenerWrap],
      prependListener: [Function: socketListenerWrap],
      setEncoding: [Function: socketSetEncoding],
      _paused: false,
      _httpMessage: [ServerResponse],
      [Symbol(async_id_symbol)]: 29,
      [Symbol(kHandle)]: [Pipe],
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kSetNoDelay)]: false,
      [Symbol(kSetKeepAlive)]: false,
      [Symbol(kSetKeepAliveInitialDelay)]: 0,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(RequestTimeout)]: undefined
    },
    httpVersionMajor: 1,
    httpVersionMinor: 1,
    httpVersion: '1.1',
    complete: true,
    rawHeaders: [
      'Host',
      'localhost:3000',
      'User-Agent',
      'curl/7.74.0',
      'Accept',
      '*/*',
      'x-apigateway-event',
      '%7B%22headers%22%3A%7B%22Host%22%3A%22localhost%3A3000%22%2C%22User-Agent%22%3A%22curl%2F7.74.0%22%2C%22Accept%22%3A%22*%2F*%22%7D%2C%22httpMethod%22%3A%22GET%22%2C%22isBase64Encoded%22%3Afalse%2C%22multiValueHeaders%22%3A%7B%22Host%22%3A%5B%22localhost%3A3000%22%5D%2C%22User-Agent%22%3A%5B%22curl%2F7.74.0%22%5D%2C%22Accept%22%3A%5B%22*%2F*%22%5D%7D%2C%22multiValueQueryStringParameters%22%3A%7B%22a%22%3A%5B%221%22%5D%7D%2C%22path%22%3A%22%2Fcats%22%2C%22pathParameters%22%3A%7B%22proxy%22%3A%22cats%22%7D%2C%22queryStringParameters%22%3A%7B%22a%22%3A%221%22%7D%2C%22requestContext%22%3A%7B%22accountId%22%3A%22offlineContext_accountId%22%2C%22apiId%22%3A%22offlineContext_apiId%22%2C%22authorizer%22%3A%7B%22principalId%22%3A%22offlineContext_authorizer_principalId%22%7D%2C%22domainName%22%3A%22offlineContext_domainName%22%2C%22domainPrefix%22%3A%22offlineContext_domainPrefix%22%2C%22extendedRequestId%22%3A%22b411061f-de1b-4be9-a6a4-a982476a36bf%22%2C%22httpMethod%22%3A%22GET%22%2C%22identity%22%3A%7B%22accessKey%22%3Anull%2C%22accountId%22%3A%22offlineContext_accountId%22%2C%22apiKey%22%3A%22offlineContext_apiKey%22%2C%22apiKeyId%22%3A%22offlineContext_apiKeyId%22%2C%22caller%22%3A%22offlineContext_caller%22%2C%22cognitoAuthenticationProvider%22%3A%22offlineContext_cognitoAuthenticationProvider%22%2C%22cognitoAuthenticationType%22%3A%22offlineContext_cognitoAuthenticationType%22%2C%22cognitoIdentityId%22%3A%22offlineContext_cognitoIdentityId%22%2C%22cognitoIdentityPoolId%22%3A%22offlineContext_cognitoIdentityPoolId%22%2C%22principalOrgId%22%3Anull%2C%22sourceIp%22%3A%22127.0.0.1%22%2C%22user%22%3A%22offlineContext_user%22%2C%22userAgent%22%3A%22curl%2F7.74.0%22%2C%22userArn%22%3A%22offlineContext_userArn%22%7D%2C%22path%22%3A%22%2Fcats%22%2C%22protocol%22%3A%22HTTP%2F1.1%22%2C%22requestId%22%3A%2292886438-c883-471c-859b-86b9fa2434ab%22%2C%22requestTime%22%3A%2219%2FJul%2F2023%3A13%3A07%3A28%20%2B0000%22%2C%22requestTimeEpoch%22%3A1689772048766%2C%22resourceId%22%3A%22offlineContext_resourceId%22%2C%22resourcePath%22%3A%22%2Fdev%2F%7Bproxy*%7D%22%2C%22stage%22%3A%22dev%22%7D%2C%22resource%22%3A%22%2F%7Bproxy%2B%7D%22%2C%22stageVariables%22%3Anull%7D',
      'x-apigateway-context',
      '%7B%22awsRequestId%22%3A%224e598571-6fa8-45fa-b8c3-0d0b5263a1a1%22%2C%22callbackWaitsForEmptyEventLoop%22%3Atrue%2C%22clientContext%22%3Anull%2C%22functionName%22%3A%22serverless-nest-example-dev-main%22%2C%22functionVersion%22%3A%22%24LATEST%22%2C%22invokedFunctionArn%22%3A%22offline_invokedFunctionArn_for_serverless-nest-example-dev-main%22%2C%22logGroupName%22%3A%22offline_logGroupName_for_serverless-nest-example-dev-main%22%2C%22logStreamName%22%3A%22offline_logStreamName_for_serverless-nest-example-dev-main%22%2C%22memoryLimitInMB%22%3A%221024%22%7D',
      'Connection',
      'close'
    ],
    rawTrailers: [],
    aborted: false,
    upgrade: false,
    url: '/cats?a=1',
    method: 'GET',
    statusCode: null,
    statusMessage: null,
    client: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 8,
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: true,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: [Server],
      _server: [Server],
      parser: [HTTPParser],
      on: [Function: socketListenerWrap],
      addListener: [Function: socketListenerWrap],
      prependListener: [Function: socketListenerWrap],
      setEncoding: [Function: socketSetEncoding],
      _paused: false,
      _httpMessage: [ServerResponse],
      [Symbol(async_id_symbol)]: 29,
      [Symbol(kHandle)]: [Pipe],
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kSetNoDelay)]: false,
      [Symbol(kSetKeepAlive)]: false,
      [Symbol(kSetKeepAliveInitialDelay)]: 0,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(RequestTimeout)]: undefined
    },
    _consuming: false,
    _dumped: false,
    next: [Function: next],
    baseUrl: '',
    originalUrl: '/cats?a=1',
    _parsedUrl: Url {
      protocol: null,
      slashes: null,
      auth: null,
      host: null,
      port: null,
      hostname: null,
      hash: null,
      search: '?a=1',
      query: 'a=1',
      pathname: '/cats',
      path: '/cats?a=1',
      href: '/cats?a=1',
      _raw: '/cats?a=1'
    },
    params: {},
    query: { a: '1' },
    res: ServerResponse {
      _events: [Object: null prototype],
      _eventsCount: 1,
      _maxListeners: undefined,
      outputData: [],
      outputSize: 0,
      writable: true,
      destroyed: false,
      _last: false,
      chunkedEncoding: false,
      shouldKeepAlive: false,
      maxRequestsOnConnectionReached: false,
      _defaultKeepAlive: true,
      useChunkedEncodingByDefault: true,
      sendDate: true,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      _contentLength: null,
      _hasBody: true,
      _trailer: '',
      finished: false,
      _headerSent: false,
      _closed: false,
      socket: [Socket],
      _header: null,
      _keepAliveTimeout: 5000,
      _onPendingData: [Function: bound updateOutgoingData],
      req: [Circular *1],
      _sent100: false,
      _expect_continue: false,
      locals: [Object: null prototype] {},
      [Symbol(kCapture)]: false,
      [Symbol(kNeedDrain)]: false,
      [Symbol(corked)]: 0,
      [Symbol(kOutHeaders)]: [Object: null prototype],
      [Symbol(kUniqueHeaders)]: null
    },
    apiGateway: { event: [Object], context: [Object] },
    body: {},
    route: Route { path: '/cats', stack: [Array], methods: [Object] },
    [Symbol(kCapture)]: false,
    [Symbol(kHeaders)]: {
      host: 'localhost:3000',
      'user-agent': 'curl/7.74.0',
      accept: '*/*',
      connection: 'close'
    },
    [Symbol(kHeadersCount)]: 12,
    [Symbol(kTrailers)]: null,
    [Symbol(kTrailersCount)]: 0,
    [Symbol(RequestTimeout)]: undefined
  }
}
(λ: main) RequestId: 4e598571-6fa8-45fa-b8c3-0d0b5263a1a1  Duration: 271.70 ms  Billed Duration: 272 ms
```


#### a=あいうえお というパラメータをつける

- リクエストする時にはURLエンコードを行う
```
$ curl -G http://localhost:3000/dev/cats/ --data-urlencode "a=あいうえお"
```

- console.logの内容を確認
```
ANY /dev/cats (λ: main)
{
  request: <ref *1> IncomingMessage {
    _readableState: ReadableState {
      objectMode: false,
      highWaterMark: 16384,
      buffer: BufferList { head: null, tail: null, length: 0 },
      length: 0,
      pipes: [],
      flowing: null,
      ended: true,
      endEmitted: false,
      reading: false,
      constructed: true,
      sync: true,
      needReadable: false,
      emittedReadable: false,
      readableListening: false,
      resumeScheduled: false,
      errorEmitted: false,
      emitClose: true,
      autoDestroy: true,
      destroyed: false,
      errored: null,
      closed: false,
      closeEmitted: false,
      defaultEncoding: 'utf8',
      awaitDrainWriters: null,
      multiAwaitDrain: false,
      readingMore: true,
      dataEmitted: false,
      decoder: null,
      encoding: null,
      [Symbol(kPaused)]: null
    },
    _events: [Object: null prototype] { end: [Function: clearRequestTimeout] },
    _eventsCount: 1,
    _maxListeners: undefined,
    socket: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 8,
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: true,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: [Server],
      _server: [Server],
      parser: [HTTPParser],
      on: [Function: socketListenerWrap],
      addListener: [Function: socketListenerWrap],
      prependListener: [Function: socketListenerWrap],
      setEncoding: [Function: socketSetEncoding],
      _paused: false,
      _httpMessage: [ServerResponse],
      [Symbol(async_id_symbol)]: 159,
      [Symbol(kHandle)]: [Pipe],
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kSetNoDelay)]: false,
      [Symbol(kSetKeepAlive)]: false,
      [Symbol(kSetKeepAliveInitialDelay)]: 0,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(RequestTimeout)]: undefined
    },
    httpVersionMajor: 1,
    httpVersionMinor: 1,
    httpVersion: '1.1',
    complete: true,
    rawHeaders: [
      'Host',
      'localhost:3000',
      'User-Agent',
      'curl/7.74.0',
      'Accept',
      '*/*',
      'x-apigateway-event',
      '%7B%22headers%22%3A%7B%22Host%22%3A%22localhost%3A3000%22%2C%22User-Agent%22%3A%22curl%2F7.74.0%22%2C%22Accept%22%3A%22*%2F*%22%7D%2C%22httpMethod%22%3A%22GET%22%2C%22isBase64Encoded%22%3Afalse%2C%22multiValueHeaders%22%3A%7B%22Host%22%3A%5B%22localhost%3A3000%22%5D%2C%22User-Agent%22%3A%5B%22curl%2F7.74.0%22%5D%2C%22Accept%22%3A%5B%22*%2F*%22%5D%7D%2C%22multiValueQueryStringParameters%22%3A%7B%22a%22%3A%5B%22%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A%22%5D%7D%2C%22path%22%3A%22%2Fcats%22%2C%22pathParameters%22%3A%7B%22proxy%22%3A%22cats%22%7D%2C%22queryStringParameters%22%3A%7B%22a%22%3A%22%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A%22%7D%2C%22requestContext%22%3A%7B%22accountId%22%3A%22offlineContext_accountId%22%2C%22apiId%22%3A%22offlineContext_apiId%22%2C%22authorizer%22%3A%7B%22principalId%22%3A%22offlineContext_authorizer_principalId%22%7D%2C%22domainName%22%3A%22offlineContext_domainName%22%2C%22domainPrefix%22%3A%22offlineContext_domainPrefix%22%2C%22extendedRequestId%22%3A%22c264f026-ebf6-4d0c-9163-34e44e3de765%22%2C%22httpMethod%22%3A%22GET%22%2C%22identity%22%3A%7B%22accessKey%22%3Anull%2C%22accountId%22%3A%22offlineContext_accountId%22%2C%22apiKey%22%3A%22offlineContext_apiKey%22%2C%22apiKeyId%22%3A%22offlineContext_apiKeyId%22%2C%22caller%22%3A%22offlineContext_caller%22%2C%22cognitoAuthenticationProvider%22%3A%22offlineContext_cognitoAuthenticationProvider%22%2C%22cognitoAuthenticationType%22%3A%22offlineContext_cognitoAuthenticationType%22%2C%22cognitoIdentityId%22%3A%22offlineContext_cognitoIdentityId%22%2C%22cognitoIdentityPoolId%22%3A%22offlineContext_cognitoIdentityPoolId%22%2C%22principalOrgId%22%3Anull%2C%22sourceIp%22%3A%22127.0.0.1%22%2C%22user%22%3A%22offlineContext_user%22%2C%22userAgent%22%3A%22curl%2F7.74.0%22%2C%22userArn%22%3A%22offlineContext_userArn%22%7D%2C%22path%22%3A%22%2Fcats%22%2C%22protocol%22%3A%22HTTP%2F1.1%22%2C%22requestId%22%3A%222acb3594-7a11-439a-a451-7bd8478e84c6%22%2C%22requestTime%22%3A%2219%2FJul%2F2023%3A13%3A21%3A44%20%2B0000%22%2C%22requestTimeEpoch%22%3A1689772904950%2C%22resourceId%22%3A%22offlineContext_resourceId%22%2C%22resourcePath%22%3A%22%2Fdev%2F%7Bproxy*%7D%22%2C%22stage%22%3A%22dev%22%7D%2C%22resource%22%3A%22%2F%7Bproxy%2B%7D%22%2C%22stageVariables%22%3Anull%7D',
      'x-apigateway-context',
      '%7B%22awsRequestId%22%3A%228ef4d650-7c2f-4c57-ba0b-9504e0160346%22%2C%22callbackWaitsForEmptyEventLoop%22%3Atrue%2C%22clientContext%22%3Anull%2C%22functionName%22%3A%22serverless-nest-example-dev-main%22%2C%22functionVersion%22%3A%22%24LATEST%22%2C%22invokedFunctionArn%22%3A%22offline_invokedFunctionArn_for_serverless-nest-example-dev-main%22%2C%22logGroupName%22%3A%22offline_logGroupName_for_serverless-nest-example-dev-main%22%2C%22logStreamName%22%3A%22offline_logStreamName_for_serverless-nest-example-dev-main%22%2C%22memoryLimitInMB%22%3A%221024%22%7D',
      'Connection',
      'close'
    ],
    rawTrailers: [],
    aborted: false,
    upgrade: false,
    url: '/cats?a=%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A',
    method: 'GET',
    statusCode: null,
    statusMessage: null,
    client: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 8,
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: true,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: [Server],
      _server: [Server],
      parser: [HTTPParser],
      on: [Function: socketListenerWrap],
      addListener: [Function: socketListenerWrap],
      prependListener: [Function: socketListenerWrap],
      setEncoding: [Function: socketSetEncoding],
      _paused: false,
      _httpMessage: [ServerResponse],
      [Symbol(async_id_symbol)]: 159,
      [Symbol(kHandle)]: [Pipe],
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kSetNoDelay)]: false,
      [Symbol(kSetKeepAlive)]: false,
      [Symbol(kSetKeepAliveInitialDelay)]: 0,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(RequestTimeout)]: undefined
    },
    _consuming: false,
    _dumped: false,
    next: [Function: next],
    baseUrl: '',
    originalUrl: '/cats?a=%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A',
    _parsedUrl: Url {
      protocol: null,
      slashes: null,
      auth: null,
      host: null,
      port: null,
      hostname: null,
      hash: null,
      search: '?a=%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A',
      query: 'a=%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A',
      pathname: '/cats',
      path: '/cats?a=%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A',
      href: '/cats?a=%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A',
      _raw: '/cats?a=%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A'
    },
    params: {},
    query: { a: 'あいうえお' },
    res: ServerResponse {
      _events: [Object: null prototype],
      _eventsCount: 1,
      _maxListeners: undefined,
      outputData: [],
      outputSize: 0,
      writable: true,
      destroyed: false,
      _last: false,
      chunkedEncoding: false,
      shouldKeepAlive: false,
      maxRequestsOnConnectionReached: false,
      _defaultKeepAlive: true,
      useChunkedEncodingByDefault: true,
      sendDate: true,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      _contentLength: null,
      _hasBody: true,
      _trailer: '',
      finished: false,
      _headerSent: false,
      _closed: false,
      socket: [Socket],
      _header: null,
      _keepAliveTimeout: 5000,
      _onPendingData: [Function: bound updateOutgoingData],
      req: [Circular *1],
      _sent100: false,
      _expect_continue: false,
      locals: [Object: null prototype] {},
      [Symbol(kCapture)]: false,
      [Symbol(kNeedDrain)]: false,
      [Symbol(corked)]: 0,
      [Symbol(kOutHeaders)]: [Object: null prototype],
      [Symbol(kUniqueHeaders)]: null
    },
    apiGateway: { event: [Object], context: [Object] },
    body: {},
    route: Route { path: '/cats', stack: [Array], methods: [Object] },
    [Symbol(kCapture)]: false,
    [Symbol(kHeaders)]: {
      host: 'localhost:3000',
      'user-agent': 'curl/7.74.0',
      accept: '*/*',
      connection: 'close'
    },
    [Symbol(kHeadersCount)]: 12,
    [Symbol(kTrailers)]: null,
    [Symbol(kTrailersCount)]: 0,
    [Symbol(RequestTimeout)]: undefined
  }
}
(λ: main) RequestId: 8ef4d650-7c2f-4c57-ba0b-9504e0160346  Duration: 3.44 ms  Billed Duration: 4 ms
```

ログ内の
```
%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A
```
をデコードすると、「あいうえお」になる

また、ログ内の以下ではGETをデコード済である
```
query: { a: 'あいうえお' },
```

なお、以下のようにURLエンコードしないと文字化けします
```
curl -X "GET"  http://localhost:3000/dev/cats/?a=あいうえお
```

- 文字化け時のconsole.logの結果を一部抜粋
```
    url: '/cats?a=%C3%A3%C2%81%C2%82%C3%A3%C2%81%C2%84%C3%A3%C2%81%C2%86%C3%A3%C2%81%C2%88%C3%A3%C2%81%C2%8A',
    method: 'GET',
    originalUrl: '/cats?a=%C3%A3%C2%81%C2%82%C3%A3%C2%81%C2%84%C3%A3%C2%81%C2%86%C3%A3%C2%81%C2%88%C3%A3%C2%81%C2%8A',
```

URLエンコードされたと仮定の想定箇所では
```
a=%C3%A3%C2%81%C2%82%C3%A3%C2%81%C2%84%C3%A3%C2%81%C2%86%C3%A3%C2%81%C2%88%C3%A3%C2%81%C2%8A
```
となっており、デコードすると以下のように文字化けしてますね・・・

```
ããããã
```


## ちょっと寄り道
### REST API
- 上記で Get,Postについて記載したが、あくまでもHTTPメソッドに対しての指定方法の説明で、REST APIではない
- Web API 全般
     - ざっくりクライアントとサーバのやりとり
     - Web上に公開されていて、外部から呼び出して利用が可能なAPI、SOAPとREST
     - URIの考え方がオレオレもある
          - /User/create
          - /User/update
          - /User/delete
          - /CreateUser
          - /UpdateUser
          - /DeleteUser

- REST APIは
     - リソース、リソースの参照、リソース操作が統一されている
     - 各リソースに対してGET, POST, PUT, DELETEでリクエストを送信し、レスポンスをXMLやjsonなどで受け取る
     - RESTの原則（大きく４つ）に従って実装されているシステムのことを、RESTfulと呼びます。
     - Getメソッド データ取得
     - POSTメソッド  データ作成
     - PUTメソッド  データ更新
     - DELETEメソッド  データ削除
     - /usersの場合を例にすると
          - /users のGet ユーザー一覧
          - /users のPost ユーザー作成
          - /users のPut ユーザー更新
          - /users のDelete ユーザーさくっと


 - GraphQLだとエンドポイント１つで、複数メソッドのような動きなのでRESTとは別なので混同しないようにする
 - 例えば、GraphQLの AWS AppSync の定義 schema.graphql が以下で、エンドポイント１つで操作はクエリーとして提供する。例 https://gist.github.com/ssugimoto/83acb96bce057043bc80c7c43fe7c2a0
```schema.graphqls

type Query {
    getNode: [Note]
}

type Mutation {
    addNote(id: ID!, name: String, description: String, image: String): Note
}


type Note @model {
  id: ID!
  name: String!
  description: String
  image: String
}
```