8
2

More than 3 years have passed since last update.

Nest.jsでWebサービスを呼び出す方法

Last updated at Posted at 2020-04-20

Nest.jsでWebサービスを呼び出す方法のメモです。このサンプルプログラムはGitHubに置いています。

環境

種類 バージョン 備考
OS Ubuntu18.04.01 LTS 仮想で動かしています
nvm 0.35.3 もっと良さそうなのもあるかもしれないですが、特にこだわりなく使っています
Node.js 12.16.2 2020/4時点最新のLTSを使っています
npm 6.14.4 2020/4時点最新
nest cli 7.1.2 2020/4時点最新
NEST 7.0.0 2020/4時点最新
$ nest info

 _   _             _      ___  _____  _____  _     _____
| \ | |           | |    |_  |/  ___|/  __ \| |   |_   _|
|  \| |  ___  ___ | |_     | |\ `--. | /  \/| |     | |
| . ` | / _ \/ __|| __|    | | `--. \| |    | |     | |
| |\  ||  __/\__ \| |_ /\__/ //\__/ /| \__/\| |_____| |_
\_| \_/ \___||___/ \__|\____/ \____/  \____/\_____/\___/


[System Information]
OS Version     : Linux 4.15
NodeJS Version : v12.16.2
NPM Version    : 6.14.4 

[Nest CLI]
Nest CLI Version : 7.1.2 

[Nest Platform Information]
platform-express version : 7.0.0
common version           : 7.0.0
core version             : 7.0.0

手順

1. 開発

1.1. プロジェクト作成

プロジェクト作成予定のディレクトリからnest new <プロジェクト名>でプロジェクト作成。途中プロンプトでプロジェクト名"node-call-rest"を入力し、パッケージマネージャーに"npm"を選択。
git cloneでリポジトリをcloneした後に実行するとgit情報が上書きされてしまいました。 --skip-gitオプションを使ってもだめでした(まるで調べていないので、やり方が悪いだけかも)。

# From Node root directory
$ nest new node-call-rest
⚡  We will scaffold your app in a few seconds..

? What name would you like to use for the new project? node-call-rest
CREATE node-call-rest/.eslintrc.js (599 bytes)
CREATE node-call-rest/.prettierrc (51 bytes)
CREATE node-call-rest/README.md (3370 bytes)
CREATE node-call-rest/nest-cli.json (64 bytes)
CREATE node-call-rest/package.json (1902 bytes)
CREATE node-call-rest/tsconfig.build.json (97 bytes)
CREATE node-call-rest/tsconfig.json (336 bytes)
CREATE node-call-rest/src/app.controller.spec.ts (617 bytes)
CREATE node-call-rest/src/app.controller.ts (274 bytes)
CREATE node-call-rest/src/app.module.ts (249 bytes)
CREATE node-call-rest/src/app.service.ts (142 bytes)
CREATE node-call-rest/src/main.ts (208 bytes)
CREATE node-call-rest/test/app.e2e-spec.ts (630 bytes)
CREATE node-call-rest/test/jest-e2e.json (183 bytes)

? Which package manager would you ❤️  to use? npm
✔ Installation in progress... ☕

🚀  Successfully created project node-call-rest
👉  Get started with the following commands:

$ cd node-call-rest
$ npm run start
              Thanks for installing Nest 🙏
     Please consider donating to our open collective
            to help us maintain this package.


   🍷  Donate: https://opencollective.com/nest

1.2. プロジェクト作成の動作確認

うまく作れているか実行して確認します。デフォルトでルートに"Hello World!"を返すようになっているので、それを使って確認します。

# From Project root directory
$ npm run starg:debug
[1:27:21 PM] File change detected. Starting incremental compilation...

[1:27:21 PM] Found 0 errors. Watching for file changes.

Debugger listening on ws://127.0.0.1:9229/c6466736-e752-434a-a7be-a105e6731a7f
For help, see: https://nodejs.org/en/docs/inspector
[Nest] 11160   - 04/20/2020, 1:27:22 PM   [NestFactory] Starting Nest application...
[Nest] 11160   - 04/20/2020, 1:27:22 PM   [InstanceLoader] AppModule dependencies initialized +12ms
[Nest] 11160   - 04/20/2020, 1:27:22 PM   [RoutesResolver] AppController {}: +5ms
[Nest] 11160   - 04/20/2020, 1:27:22 PM   [RouterExplorer] Mapped {, GET} route +3ms
[Nest] 11160   - 04/20/2020, 1:27:22 PM   [NestApplication] Nest application successfully started +2ms

Curlで確認。Hello Worldが返ってきます。

$ curl localhost:3000
Hello World!

1.3. app.module.ts変更

src/app.module.tsを変更。HttpModuleをインポートします(1行目と7行目を変更)。

app.module.ts
import { Module, HttpModule} from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { RestController } from './rest/rest.controller';

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

1.4. コントローラー作成

NEST cliでコントローラーrestを作成。

$ nest g controller rest
CREATE src/rest/rest.controller.spec.ts (479 bytes)
CREATE src/rest/rest.controller.ts (97 bytes)
UPDATE src/app.module.ts (322 bytes)

作成されたsrc/rest/rest.controller.tsを変更。
今回はGetリクエストを受けて、"httpbin.org"というサービスに対してPOSTリクエストを投げます。このサービスにBASIC認証は不要ですが、実際にはよく使うと思われるため付け加えておきました。

src/rest/rest.controller.ts
import { Controller, Get, HttpService } from '@nestjs/common';

@Controller('rest')
export class RestController {

    constructor(private httpService: HttpService) { }

    @Get()
    async callRest() {

        const url = "http://httpbin.org/post";

        //authは今回は不要だが、入れるとしたらこんな形式
        const auth = {
            auth: {
                username: 'user name',
                password: 'password'
            }
        };
        const body = { "contents": "test" };
        // APIコール
        try {
            var result = await this.httpService.post(url, body, auth).toPromise();
        }
        catch (e) {
            console.log(e.response);
        }
        console.log(result);
        return result.data;
    }
}

2. 動作確認

2.1. Node.js起動

スクリプトを使ってNode.jsを起動します。

# From Project root directory
$ npm run starg:debug

2.2. Getリクエストを投げる

curlを使ってlocalhost:3000/restにGetリクエストを投げます。"httpbin.org"へのpostリクエストの結果が返ってきます。見にくいので改行・インデントをして結果を整形しました。json以下にPostリクエストBodyがjsonで入っています。

$ curl localhost:3000/rest
{
    "args": {},
    "data": "{\"contents\":\"test\"}",
    "files": {},
    "form": {},
    "headers": {
        "Accept": "application/json, text/plain, */*",
        "Authorization": "Basic dXNlciBuYW1lOnBhc3N3b3Jk",
        "Content-Length": "19",
        "Content-Type": "application/json;charset=utf-8",
        "Host": "httpbin.org",
        "User-Agent": "axios/0.19.2",
        "X-Amzn-Trace-Id": "Root=1-5e9d303c-3f9f4b3c5621034ae617c097"
    },
    "json": {
        "contents": "test"
    },
    "origin": "<my ip address>",
    "url": "http://httpbin.org/post"
}

npm run starg:debugを実行したターミナルでは、ログにもっと細かい情報を出しています。

[2:16:40 PM] File change detected. Starting incremental compilation...

[2:16:41 PM] Found 0 errors. Watching for file changes.

Debugger listening on ws://127.0.0.1:9229/2c52a7c8-dc62-485c-90c9-94b91200de8e
For help, see: https://nodejs.org/en/docs/inspector
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [NestFactory] Starting Nest application...
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [InstanceLoader] HttpModule dependencies initialized +16ms
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [InstanceLoader] AppModule dependencies initialized +2ms
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [RoutesResolver] AppController {}: +5ms
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [RouterExplorer] Mapped {, GET} route +3ms
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [RoutesResolver] RestController {/rest}: +1ms
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [RouterExplorer] Mapped {/rest, GET} route +1ms
[Nest] 12063   - 04/20/2020, 2:16:41 PM   [NestApplication] Nest application successfully started +4ms
{
  status: 200,
  statusText: 'OK',
  headers: {
    date: 'Mon, 20 Apr 2020 05:16:44 GMT',
    'content-type': 'application/json',
    'content-length': '537',
    connection: 'close',
    server: 'gunicorn/19.9.0',
    'access-control-allow-origin': '*',
    'access-control-allow-credentials': 'true'
  },
  config: {
    url: 'http://httpbin.org/post',
    method: 'post',
    data: '{"contents":"test"}',
    headers: {
      Accept: 'application/json, text/plain, */*',
      'Content-Type': 'application/json;charset=utf-8',
      'User-Agent': 'axios/0.19.2',
      'Content-Length': 19
    },
    auth: { username: 'user name', password: 'password' },
    transformRequest: [ [Function: transformRequest] ],
    transformResponse: [ [Function: transformResponse] ],
    timeout: 0,
    adapter: [Function: httpAdapter],
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    validateStatus: [Function: validateStatus],
    cancelToken: CancelToken { promise: [Promise], reason: [Cancel] }
  },
  request: ClientRequest {
    _events: [Object: null prototype] {
      socket: [Function],
      abort: [Function],
      aborted: [Function],
      error: [Function],
      timeout: [Function],
      prefinish: [Function: requestOnPrefinish]
    },
    _eventsCount: 6,
    _maxListeners: undefined,
    outputData: [],
    outputSize: 0,
    writable: true,
    _last: true,
    chunkedEncoding: false,
    shouldKeepAlive: false,
    useChunkedEncodingByDefault: true,
    sendDate: false,
    _removedConnection: false,
    _removedContLen: false,
    _removedTE: false,
    _contentLength: null,
    _hasBody: true,
    _trailer: '',
    finished: true,
    _headerSent: true,
    socket: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'httpbin.org',
      _readableState: [ReadableState],
      readable: true,
      _events: [Object: null prototype],
      _eventsCount: 6,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: false,
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: null,
      _server: null,
      parser: null,
      _httpMessage: [Circular],
      [Symbol(asyncId)]: 15,
      [Symbol(kHandle)]: [TCP],
      [Symbol(kSetNoDelay)]: false,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0
    },
    connection: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'httpbin.org',
      _readableState: [ReadableState],
      readable: true,
      _events: [Object: null prototype],
      _eventsCount: 6,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: false,
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: null,
      _server: null,
      parser: null,
      _httpMessage: [Circular],
      [Symbol(asyncId)]: 15,
      [Symbol(kHandle)]: [TCP],
      [Symbol(kSetNoDelay)]: false,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0
    },
    _header: 'POST /post HTTP/1.1\r\n' +
      'Accept: application/json, text/plain, */*\r\n' +
      'Content-Type: application/json;charset=utf-8\r\n' +
      'User-Agent: axios/0.19.2\r\n' +
      'Content-Length: 19\r\n' +
      'Host: httpbin.org\r\n' +
      'Authorization: Basic dXNlciBuYW1lOnBhc3N3b3Jk\r\n' +
      'Connection: close\r\n' +
      '\r\n',
    _onPendingData: [Function: noopPendingOutput],
    agent: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      defaultPort: 80,
      protocol: 'http:',
      options: [Object],
      requests: {},
      sockets: [Object],
      freeSockets: {},
      keepAliveMsecs: 1000,
      keepAlive: false,
      maxSockets: Infinity,
      maxFreeSockets: 256,
      [Symbol(kCapture)]: false
    },
    socketPath: undefined,
    method: 'POST',
    insecureHTTPParser: undefined,
    path: '/post',
    _ended: true,
    res: IncomingMessage {
      _readableState: [ReadableState],
      readable: false,
      _events: [Object: null prototype],
      _eventsCount: 3,
      _maxListeners: undefined,
      socket: [Socket],
      connection: [Socket],
      httpVersionMajor: 1,
      httpVersionMinor: 1,
      httpVersion: '1.1',
      complete: true,
      headers: [Object],
      rawHeaders: [Array],
      trailers: {},
      rawTrailers: [],
      aborted: false,
      upgrade: false,
      url: '',
      method: null,
      statusCode: 200,
      statusMessage: 'OK',
      client: [Socket],
      _consuming: true,
      _dumped: false,
      req: [Circular],
      responseUrl: 'http://user%20name:password@httpbin.org/post',
      redirects: [],
      [Symbol(kCapture)]: false
    },
    aborted: false,
    timeoutCb: null,
    upgradeOrConnect: false,
    parser: null,
    maxHeadersCount: null,
    reusedSocket: false,
    _redirectable: Writable {
      _writableState: [WritableState],
      writable: true,
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      _options: [Object],
      _redirectCount: 0,
      _redirects: [],
      _requestBodyLength: 19,
      _requestBodyBuffers: [],
      _onNativeResponse: [Function],
      _currentRequest: [Circular],
      _currentUrl: 'http://user%20name:password@httpbin.org/post',
      [Symbol(kCapture)]: false
    },
    [Symbol(kCapture)]: false,
    [Symbol(kNeedDrain)]: false,
    [Symbol(corked)]: 0,
    [Symbol(kOutHeaders)]: [Object: null prototype] {
      accept: [Array],
      'content-type': [Array],
      'user-agent': [Array],
      'content-length': [Array],
      host: [Array],
      authorization: [Array]
    }
  },
  data: {
    args: {},
    data: '{"contents":"test"}',
    files: {},
    form: {},
    headers: {
      Accept: 'application/json, text/plain, */*',
      Authorization: 'Basic dXNlciBuYW1lOnBhc3N3b3Jk',
      'Content-Length': '19',
      'Content-Type': 'application/json;charset=utf-8',
      Host: 'httpbin.org',
      'User-Agent': 'axios/0.19.2',
      'X-Amzn-Trace-Id': 'Root=1-5e9d303c-3f9f4b3c5621034ae617c097'
    },
    json: { contents: 'test' },
    origin: 'my ip address',
    url: 'http://httpbin.org/post'
  }
}
8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2