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行目を変更)。
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認証は不要ですが、実際にはよく使うと思われるため付け加えておきました。
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'
}
}