Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all 8952 articles
Browse latest View live

初心者がJSDocを調べてみた

$
0
0

免責事項

この記事は初心者視点でザックリとした説明をしています。正確性に欠ける可能性がございますが、ご了承ください。「明らかに違うよ」ということがありましたら、ご指摘くださると幸いです。

環境

OS:最新版ではないMacOS

目次

  1. JSDocとは
  2. JSDocの書き方

1. JSDocとは

JSDocは、JavaScriptのソースコードにアノテーション(注釈)を追加するために使われるマークアップ言語です。

なぜ使うか

複数人で開発を進める場合や、大規模なプログラムを開発する場合に、
変数のデータ型やオブジェクトの種類(配列、関数、コンストラクタ、クラスなど)をコメントとして記述することで、他の人がそれらを見分けることができ、開発の効率が上がります。

加えて、JSDocに対応しているエディターを使うとかなり開発効率が上がると思います。
JSDocに対応しているエディターVSCodeで、以下のように関数を作りJSDocをコメントとして書くと、
スクリーンショット 2019-11-04 21.25.51.png

以下のように別のファイルで関数にカーソルを合わせたときに、JSDocコメントが表示されます。
スクリーンショット 2019-11-04 21.25.03.png

完璧な記述をする必要はありませんが、できるだけ推奨されている書き方で記述すると良いそうです。

2. JSDocの書き方

"/**"で初めて、"*/"で終わります。
その間に記述がある分だけ"*"を書きましょう。

/**
 * [記述]
 */

変数

@typeの後の{}内に変数の型(String, Number, Boolean)を書き、そのあとに変数の説明を書きます。

/**
 * @type {Number} 年齢
 */letage=23;

配列

@typeの後の{}内にArrayと書き、そのあとに配列の説明を書きます。

/**
 * @type {Array} 年齢の配列
 */letageArray=[10,22,30];

連想配列

最初に@typeで連想配列の説明を書きます。連想配列はオブジェクトの扱いなので、{}の中はobjectと書きます。
次に連想配列のオブジェクト内の@typeで連想配列のペアが何を意味しているのかを書きます。{}内は連想配列の値(value)の型を書きます。

/**
 * @type {Object} 会員情報の連想配列です
 */constmemberInfo={/**
   * @type {Number} 会員番号
   */"id":1,/**
   * @type {String} 会員名
   */"name":"taro imo"}

クラス(インスタンス)

はじめにクラス(インスタンス)の説明をします。
@constructorがコンストラクタであることを表します。(なくても良い)
@thisが何を指しているのか{}の中に書きます。
@paramで関数の引数の説明を書きます。{}の中には関数の引数の型を書きます。

/**
 * Personクラスのインスタンスを作成する。
 * @constructor
 * @this {Person}
 * @param {String} name 名前
 * @param {Number} age 年齢
 */letPerson=function(name,age){this.name=name;this.age=age;}lettaroImo=newPerson('taroImo',23);console.log(taroImo.name);// taroImoconsole.log(taroImo.age);// 23

関数

まずコメントの最初に関数の説明を書きます。
次に@paramで関数の引数の説明を書きます。{}の中には関数の引数の型を書きます。
最後に@returnで返り値の説明を書きます。{}の中には返り値の型を書きます。
*関数内の変数のJSDocは省いています。

/**
 * 10年後の年齢を返す
 * @param {Number} age 年齢
 * @return {Number} 10年後の年齢
 */functiontenYearsLater(age){lettenYearsLater=age+10;returntenYearsLater;}

おわりに

この書き方が公式というわけではなく色々な書き方があり、人・企業・組織により書き方は違います。

参考

「N予備校 プログラミングコース」
https://www.nnn.ed.nico/
「Wikipedia JSDoc」
https://ja.wikipedia.org/wiki/JSDoc
「JSDocでJavaScript のコメントを書こう」
https://sterfield.co.jp/designer/jsdoc-で-javascript-のコメントを書こう/
「Google JavaScript Style Guide」
https://google.github.io/styleguide/javascriptguide.xml?showone=Comments#Comments


【2019年版】Node.js + MongoDBでデータベース接続をする【Mac環境】

$
0
0

そろそろReactのバックエンドをNode.jsで書きたいと思ってきた頃なので、最近熱いNoSQLをNode.jsで使う方法をまとめました!

準備体操

HomebrewとNode.jsは入ってますか?

$ brew -v
Homebrew 2.1.15
Homebrew/homebrew-core (git revision 1ea06; last commit 2019-11-04)
Homebrew/homebrew-cask (git revision 932c87a; last commit 2019-11-04)
$ node -v
v11.6.0

コマンドを打っても何も出てこない時は、以下からインストールしてください。
MacにHomebrewとNode.jsをインストール

みんな大好きmacOSで開発していきますよ。

環境構築

まずは、MongoDBをMacにインストールします。

とりあえずホームディレクトリ~/で以下のコマンド

$ brew tap mongodb/brew

MacにMongoDBをインストール

brew install mongodb-community@4.2

mongoコマンドを打ってこんな感じになればOK

$ mongo
MongoDB shell version v4.2.1
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
︙

MongoDBをローカル環境で使うために、データベースを起動しておく必要があります。
以下のコマンドでMongoDBを起動します。

$ mongod --config /usr/local/etc/mongod.conf

ここでは特に出力されないので、新しいターミナルを開いてMongoDBが起動しているか確認。

//新しいターミナル画面で

$ mongo
MongoDB shell version v4.2.1
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
︙
To enable free monitoring, run the following command: db.enableFreeMonitoring()
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---

>

ターミナルが入力待ちになっていたら、成功。
control^ + Cでシェルを終了します。

mongod --config /usr/local/etc/mongod.confaliasに登録しておくと捗ります。
aliasについてはこちらの記事がおすすめです。
【初心者向け】エイリアスの設定方法

私は、mongodbでサーバーが起動するaliasを登録しています。

MongoDBを使用する場合は、サーバーを起動したまま作業をしてください。(mongod --config /usr/local/etc/mongod.confを使用したまま)
たぶん、コマンド打たなくて自動で起動してくれるみたいな便利グッズがありそうですが、気力が持たなかったのでまたいつか調べます。

さあ、準備万端です。
Javascriptを書いていきましょう。

JavascriptとMongoDBを接続

まずは作業ディレクトリを作りましょう。

$ mkdir mongo-curd
$ cd mongo-curd

MongoDBをnode.jsで使えるようにするDriverをnpmでインストールします。

$ npm init -y
$ npm install mongodb --save

実際のnode.jsを書いていくファイルを作成します。

$ touch index.js

index.jsファイルが作成できたら以下のコードをindex.jsに記述してください。

index.js
constMongoClient=require('mongodb').MongoClient;constassert=require('assert');/* 接続先URL */consturl='mongodb://localhost:27017';/* データベース名 */constdbName='myMongo';/* データベース接続 */MongoClient.connect(url,(err,client)=>{/* Errorがあれば処理を中断 */assert.equal(null,err);/* 接続に成功すればコンソールに表示 */console.log('Connected successfully to server');/** DBを取得 */constdb=client.db(dbName);/* DBとの接続切断 */client.close();});

ファイルを実行してみましょう。

$ node index.js

(node:72067) DeprecationWarning: current Server Discovery and Monitoring engine is deprecated, 
and will be removed in a future version. To use the new Server Discover and Monitoring engine, 
pass option { useUnifiedTopology: true } to the MongoClient constructor.
Connected successfully to server

OOPS!

WTF!!

やたらと長ったらしいエラーが吐き出されました。

MongoClientに{ useUnifiedTopology: true }をオプションとして渡せとのことです。
index.jsを以下のように変更しましょう。

index.js
constMongoClient=require('mongodb').MongoClient;constassert=require('assert');/* 接続先URL */consturl='mongodb://localhost:27017';/* データベース名 */constdbName='myMongo';/**
 * 追加オプション
 * MongoClient用オプション設定
 */constconnectOption={useNewUrlParser:true,useUnifiedTopology:true,}/**
 * データベース接続
 * データベース接続用の引数追加
 */MongoClient.connect(url,connectOption,(err,client)=>{/* Errorがあれば処理を中断 */assert.equal(null,err);/* 接続に成功すればコンソールに表示 */console.log('Connected successfully to server');/** DBを取得 */constdb=client.db(dbName);/* DBとの接続切断 */client.close();});

MongoClient.connect(url, connectOption, (err, client))の第二引数にconnectOptionを渡していることに注意してください。

さあ、もう一度、実行してみましょう。

$ node index.js
Connected successfully to server

無事、MongoDBとの接続できました。

次回は、これを使ってもう少し本格的に、データベースには欠かせないCURDの実装していきます。

それでは!

Happy Hacking :sunglasses: !

参考

Install MongoDB Community Edition on macOS

MongoDB Quick Start

windowsでNode.jsインストールをする方法

$
0
0

Node.jsのインストール方法

Node.jsとは?

JavaScriptは、ブラウザ上で動くために開発され、ページに動きを追加することを基本としている。しかし、サーバーサイドを得意としていない。そこでサーバーサイドでも開発できるようにしたのがNode.jsである。

Node.jsのインストール手順

今回は、windowsでインストールを進めることにする。

手順1

ブラウザ上でhttps://nodejs.org/ja/
と検索しNode.jsのホームに入る。
タイトルなし.png

手順2

LTS版と(推奨版)と最新版のボタンが現れる。
LTS版は、サポート版であり、長期的にサポートされる。最新版は、現在リリースされているものの中で最も新しいものである。今回は、LTS版でインストールする。

手順3

LTSボタンを押すとダウンロードが始まり完了するまでしばらく待つ。設定が複数出てくるが、基本デフォルトのままでよく、次へボタンで進める。

手順4

最後にinstallボタンを押すと完了である。

まとめ

以上がNode.jsをインストール手順である。
迷う部分は、LTS版か最新版かどちらかをインストールするだけで設定自体は簡単である。

同期・非同期、マルチスレッド・シングルスレッド、並行処理・並列処理のまとめ

$
0
0

床屋でのコース・・・タスク
一度に行う処理の過程・・・プロセス
コースの中の「髪を切る」「シャンプーをする」「顔を剃る」「ドライヤーをする」1つ1つの処理・・・スレッド

シングルスレッドの例

プロセス中にスレッドが1つある
スクリーンショット 2019-11-05 13.18.12.png

マルチスレッドの例

プロセス中にスレッドが2つある
スクリーンショット 2019-11-05 13.22.41.png

同期(同期処理)

前の処理が終わってから、次の処理が行われる。
順番に処理が行われる。

例) コースが「髪を切る」「シャンプーをする」「顔を剃る」「ドライヤーをする」と順番に行われていく。

非同期(非同期処理)

前の処理が終わるのを待たずに、次の処理が行われる。
前の処理が遅かったら、どんどん早い処理が行われていく。
順番に処理が行われる。

例) コースが「髪を切る」「シャンプーをする」「顔を剃る」「ドライヤーをする」と順番に行われない。「顔を剃る」「髪を切る」「シャンプーをする」「ドライヤーをする」などが起こること。

マルチスレッド

複数の処理が複数または単一の処理機能によって並行して行われること。

例) コースの中で「顔を剃る」「ドライヤーをする」が同時に行われてしまう

シングルスレッド

並列処理などを行わず、単一の処理を順番に実行していくこと。

例) コースの中で1プロセス中1スレッドが守られる。もちろん非同期も起こりうる。

並行処理

処理を順不同に行う処理です.人間にとってみればあたかも同時に行われているように見えてしまう処理。別のプロセスが同時に行われること。

例) 実際には2人美容師がいるのに、1人の美容師により隣の人の顔そりと自分のドライヤーが行われているように感じてしまうこと?

スクリーンショット 2019-11-05 13.38.04.png

並列処理

マルチスレッドで実行し計算負荷を分散させる処理。

例) コースの中で「顔を剃る」「ドライヤーをする」が同時に行われてしまう

めまいと気圧の関係性を調査するLINE Botの作成

$
0
0

概要

プログラムの勉強を始めて4か月ほどの開業医です。

秋は天気がころころ変わるため体調を崩す人が多くなると言われています。他にも台風などで気圧が下がると頭痛がしたり、天気が悪いと古傷が痛んだりするように気象の変化によって症状が出現したり悪化することは以前から指摘されていましたが、最近このような状態が気象病と呼ばれるようになりました。

すでに天気予測と頭痛予防など気象病に関するアプリはありますが、そもそも気象と病気(今回の場合は気圧低下とめまい)は本当に関連あるかどうかという調査研究報告が内耳が専門の耳鼻咽喉科の学会でもほとんどありません。

たしかに、気圧が低下するとめまい(特にメニエール病)の患者さんが多く受診されるので関係性はありそうです。今回、気圧とめまいの関係性を調査できるように、めまい発症時に気圧が低下していたのかどうかがわかるLINE Botを作成しました。

動画

実装

LINE上で質問に答えていくとめまいの状態が記録され、めまい発生時の気圧とめまい発生3時間前の気圧を比較し、めまい発生時に気圧が低下していたのかどうかがわかるLINE Bot。

概念図

node.js expressでOpenWeatherAPIから気象情報を取得し、LINE bot APIと連携しました。

openweather.png

作成方法

1. Botアカウントを作成する

2. Node.jsでBot開発

3. ngrokでトンネリング

上記の1~3を以下の参考記事の通りに行います。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017

4. OpenWeatherMap API(気象情報の取得)
無料で気象情報を取得できるAPIです。
気圧の実測値を取得できるAPIが見つからなかったため、今回はOpenWeatherMapで取得できる3時間毎の気象予報情報を利用しました。
OpenWeatherMap
マイページの「API keys」から自分のAPIキーをメモしてください。

5. プログラム作成

コードを以下のように書き換えます。
今回も、LINE Developersのクリックリプライを使いました。
LINE Developers

'use strict';constexpress=require('express');constline=require('@line/bot-sdk');constaxios=require('axios');constPORT=process.env.PORT||3000;constconfig={channelSecret:'自分のchannelSecretを入力してください',channelAccessToken:'自分のchannelAccessTokenを入力してください'};constapp=express();app.get('/',(req,res)=>res.send('Hello LINE BOT!(GET)'));//ブラウザ確認用(無くても問題ない)app.post('/webhook',line.middleware(config),(req,res)=>{console.log(req.body.events);//ここのif文はdeveloper consoleの"接続確認"用なので後で削除して問題ないです。if(req.body.events[0].replyToken==='00000000000000000000000000000000'&&req.body.events[1].replyToken==='ffffffffffffffffffffffffffffffff'){res.send('Hello LINE BOT!(POST)');console.log('疎通確認用');return;}Promise.all(req.body.events.map(handleEvent)).then((result)=>res.json(result));});constclient=newline.Client(config);asyncfunctiongetPressuer(){constBASE_URL="http://api.openweathermap.org/data/2.5/forecast"constAPI_KEY="自分のOpenWeatherMapのAPIキーを入力してください";// APIキーの指定letPostal_code="調べたい場所の郵便番号を記入してください";//クリニックの郵便番号// APIのURL(5日/ 3時間毎の予測データ)varurl=BASE_URL+"?zip="+Postal_code+",jp&APPID="+API_KEY+"&units=metric";letres=awaitaxios.get(url);item=res.data.list;  //気象データが取得できているか確認用console.log(item[0].dt_txt);console.log(item[0].main.pressure);console.log(item[1].dt_txt);console.log(item[1].main.pressure);console.log(item[2].dt_txt);console.log(item[2].main.pressure);console.log(item[3].dt_txt);console.log(item[3].main.pressure);returnitem;}letprepre;//めまい発生3時間前の気圧letitem;letpressure;letresult;asyncfunctionhandleEvent(event){if(event.type!=='message'||event.message.type!=='text'){returnPromise.resolve(null);}// LINE botのプログラム letmessage=event.message.text;letquestion="";letlabel1,label2,label3="";lettext1,text2,text3="";if(message=="めまい"){question="どんなめまいですか?";label1="くるくる回る";text1="回転性"message=text1;label2="ふらふら揺れる";text2="動揺性";message=text2;label3="その他";text3="その他";message=text3;}elseif(message=="回転性"||message=="動揺性"||message=="その他"){question="めまいはどのくらいの時間続きましたか?";label1="1分以内を何度も繰り返す";text1="1分以内";message=text1;label2="10分未満";text2="10分未満";message=text2;label3="10分以上";text3="10分以上";message=text3;}elseif(message=="1分以内"||message=="10分未満"||message=="10分以上"){question="耳鳴りや聞こえにくさはありますか?";label1="はい";text1="耳鳴り、難聴あり";message=text1;label2="いいえ";text2="耳鳴り、難聴なし";message=text2;label3="以前からある";text3="耳鳴り、難聴以前から";message=text3;}elseif(message=="耳鳴り、難聴あり"||message=="耳鳴り、難聴なし"||message=="耳鳴り、難聴以前から"){question="頭痛はありますか?";label1="はい"text1="頭痛あり";message=text1;label2="いいえ";text2="頭痛なし";message=text2;label3="選択できません";text3="めまい";message=text3;console.log("頭痛");}elseif(message=="頭痛あり"||message=="頭痛なし"){item=awaitgetPressuer();question="めまいはいつ起こりましたか?";label1="6時間ほど前"text1="めまい時の気圧:"+String(item[1].main.pressure);message=text1;label2="3時間ほど前";text2="めまい時の気圧:"+String(item[2].main.pressure);message=text2;label3="今現在";text3="めまい時の気圧:"+String(item[3].main.pressure);message=text3;}elseif(message=="めまい時の気圧:"+String(item[1].main.pressure)){prepre=item[0].main.pressureif(prepre>item[1].main.pressure){// めまい発生3時間前の気圧がめまい発生時の気圧より高ければresult="めまいは気圧低下時に起こっています。"}else{result="めまいは気圧低下時に起こっていません。"}}elseif(message=="めまい時の気圧:"+String(item[2].main.pressure)){prepre=item[1].main.pressureif(prepre>item[2].main.pressure){// めまい発生3時間前の気圧がめまい発生時の気圧より高ければresult="めまいは気圧低下時に起こっています。"}else{result="めまいは気圧低下時に起こっていません。"}}elseif(message=="めまい時の気圧:"+String(item[3].main.pressure)){prepre=item[2].main.pressureif(prepre>item[3].main.pressure){// めまい発生3時間前の気圧がめまい発生時の気圧より高ければresult="めまいは気圧低下時に起こっています。"}else{result="めまいは気圧低下時に起こっていません。"}}if(result=='めまいは気圧低下時に起こっています。'||result=='めまいは気圧低下時に起こっていません。'){client.replyMessage(event.replyToken,{type:'text',text:result,});result='';// resultを初期化return;}else{returnclient.replyMessage(event.replyToken,{"type":"text","text":question,"quickReply":{"items":[{"type":"action","action":{"type":"message","label":label1,"text":text1}},{"type":"action","action":{"type":"message","label":label2,"text":text2}},{"type":"action","action":{"type":"message","label":label3,"text":text3}}]}});}}app.listen(PORT);console.log(`Server running at ${PORT}`);

動作確認

ちゃんと動いています。
クイックリプライボタンで表示されます。

めまい1.PNG

めまい2.PNG

めまい3.PNG
めまい4.PNG
めまい5.PNG
めまい発生時の予想気圧とめまい発生3時間前の予想気圧を比較して判定しています。
めまい6.PNG

考察

OpenWeatherMap APIによる気象情報の取得は調べたい場所の郵便番号で出来ます。今回はクリニックの郵便番号を使い気象情報を取得していますが、利用者がめまいが発生した場所の郵便番号を入力することでその場所での気圧が分かるように改良していきたいと思います。

また、取得した予想気圧と実測気圧との違いがあるかどうかが気になるので、気圧センサーを作って気圧を計測しAPIで取得した値と比較してみたいと思います。精度がよければめまいの患者さんに実際に使って頂いて、本当にめまいと気圧が関連性があるか調査を行いたいと思っています。その調査で気圧とめまいの関連について何か有効なデータが取れればいいなと思います。

QualityForwardのAPIを操作するNode.jsライブラリ

$
0
0

QualityForwardはテスト管理クラウドサービスです。ExcelやGoogleスプレッドシートような一覧形式でテストを一括作成したり、APIから操作もできます。APIはRESTfulなものでシンプルな作りですが、直接URLを叩くような使い方は殆どしないでしょう。

そこで少しでも使いやすくするためにNode.jsライブラリを作り始めました。まだテストスイートの操作しかできませんが、紹介します。

リポジトリ

リポジトリは以下のURLです。ライセンスはMIT Licenseとなっています。

goofmint/qualityforward-node

使い方

まず初期化します。APIキーで初期化します。

import{QualityForward,TestSuite}from'qualityforward-node';constclient=newQualityForward(API_KEY);

テストスイート取得

TypeScriptで書いています。JavaScriptで利用も可能です。

consttestSuites:TestSuite[]=awaitclient.getTestSuites();consttestSuite:TestSuite=testSuites[0];

テストスイート作成、更新

作成と更新は同じsaveメソッドです。

consttestSuite:TestSuite=client.TestSuite();testSuite.name='Test suite name';if(awaittestSuite.save()){// 作成成功}else{// 作成失敗}testSuite.name='新しいテストツイート名';if(awaittestSuite.save()){// 更新成功}else{// 更新失敗}

テストスイート削除

if(awaittestSuite.destroy()){// 削除成功}else{// 削除失敗}

まとめ

ライブラリがあれば、外部システムとの連携であったり、社内データ連係も簡単になるはずです。ぜひお試しください。

QualityForward

[GCP]Cloud StorageトリガーでCloud Functionsを動かす

$
0
0

はじめに

Cloud StorageトリガーでCloud Functionsを動かす方法をまとめています。
記載している内容は、基本的にGoogle Cloudが公開しているチュートリアルの内容に沿っています。

注意事項
Google Cloudの日本語ドキュメントに記載されているサンプルプログラムは実行してもエラーとなるものが多いです。そのためサンプルプログラムを試す際にはLanguageをEnglishにして、そこで表示されるサンプルプログラムを利用してください。

Cloud Functionsで使用するトリガーとは

Cloud Functionsは単体では実行できません。どうすれば実行できるかというと、以下4種類のいずれかから呼び出されることで実行できます。この呼び出し元のことをトリガーと言います。
参考:https://cloud.google.com/functions/docs/concepts/events-triggers?hl=ja#events

  • HTTP
  • Cloud Storage
  • Cloud Pub/Sub
  • Firebase(DB、Storage、アナリティクス、Auth)

今回はCloud Storageを起点としてCloud Functionsを呼び出しますので、Cloud Storageトリガーの詳細を確認します。Cloud Storageトリガーの詳細なトリガーには以下4種類があります。
参考:https://cloud.google.com/functions/docs/calling/storage?hl=ja

  • google.storage.object.finalize(オブジェクト作成など)
  • google.storage.object.delete(オブジェクトの削除)
  • google.storage.object.archive(オブジェクトのアーカイブ)
  • google.storage.object.metadataUpdate(オブジェクトのメタデータ更新)

今回はCloud Storageにファイルが保存されるたびにCloud Functionsを呼び出そうと思うので、google.storage.object.finalizeトリガーを利用することにします。

google.storage.object.finalizeトリガーを使ってみる

以下の手順は、チュートリアルをベースに作成しています。
参考:https://cloud.google.com/functions/docs/tutorials/storage

繰り返しになりますが日本語ドキュメントだと動かないプログラムやコマンドが書かれていますので、英語ドキュメントで確認することを推奨いたします。

1. Cloud Functionsで実行する関数の作成

Cloud shellを起動し適当なフォルダを作成し、作成したフォルダ内に以下を保存します。
この関数はCloud Storageにアップロードされたファイルのファイル名などの情報をログに記録するものです。

index.js
/**
 * Generic background Cloud Function to be triggered by Cloud Storage.
 *
 * @param {object} data The event payload.
 * @param {object} context The event metadata.
 */exports.helloGCSGeneric=(data,context)=>{constfile=data;console.log(`  Event ${context.eventId}`);console.log(`  Event Type: ${context.eventType}`);console.log(`  Bucket: ${file.bucket}`);console.log(`  File: ${file.name}`);console.log(`  Metageneration: ${file.metageneration}`);console.log(`  Created: ${file.timeCreated}`);console.log(`  Updated: ${file.updated}`);};

2. Cloud Storageのバケット作成

Cloud Functionsのトリガーとなるバケットを作成します。

3. デプロイ

以下のコマンドでデプロイします。
YOUR_TRIGGER_BUCKET_NAME には「2. Cloud Storageのバケット作成」で作成したバケット名を入力してください。

gcloud functions deploy helloGCSGeneric --runtime nodejs8 --trigger-resource YOUR_TRIGGER_BUCKET_NAME --trigger-event google.storage.object.finalize

4. Cloud Storageにファイルアップロード

「2. Cloud Storageのバケット作成」で作成したバケットに適当なファイルをアップロードしてください。

5. ログ確認

StackDriverから確認しても良いですが、Cloud shellから以下のコマンドを実行することで最新のログ20件を表示することができます。

gcloud beta functions logs read --limit 20

以下のようなログが記録されていれば成功です。
Metagenerationはメタデータの変更時に更新され、作成時は1になります。

Event 779317620839983
Event Type: google.storage.object.finalize
Bucket: test-bucket
File: upload/test.txt
Metageneration: 1
Created: 2019-11-05T06:10:09.175Z
Updated: 2019-11-05T06:10:09.175Z

なお日本語ドキュメントのプログラムをコピペして実行すると「textPayload: "TypeError: Cannot read property 'xxxxx' of undefined」のエラーがログに記録されました。(2019/11/5時点)

参考情報

イベントパラメータ

https://cloud.google.com/functions/docs/writing/background?hl=ja#event_parameter

プロパティ説明
dataイベントのデータ オブジェクト。オブジェクト
contextイベントのコンテキスト オブジェクト。オブジェクト
context.eventIdイベントの一意の ID。文字列
context.timestampこのイベントが作成された日時。文字列(ISO 8601)
context.eventTypeイベントのタイプ。文字列
context.resourceイベントを発行したリソース。文字列

Visual Studio Onlineを軽く試した

$
0
0

準備

  • Visual Studio Online
  • 上記リンクのGet startedをクリック
  • 前提としてMicrosoftアカウントとAzureの登録が必要
    • とりあえずAzureは無料アカウントで良さそう
    • Azure使ったことないから試しながら使ってる

planを作る

  • 料金プランの選択
    • 一旦無料試用版を選択した
  • Locationを選択
    • ShouthestAsiaを選択した

Screen Shot 2019-11-06 at 0.55.35.png

環境を作成

  • Create environment
    • 環境名
      • 好きな名前
    • Gitリポジトリ
      • github以外は試してない
    • Instance Type
      • とりあえずStandardにした
    • あとは適当

Screen Shot 2019-11-06 at 0.59.58.png

エディタ画面

iPad Pro 11のSafariで開いてみた
IMG_0036.jpg

  • 普通にVSCodeの画面
  • 遅延とかも今の所あんまり感じない
  • 最初の読み込みは遅い − インスタンス立ち上げてるから?
  • 環境作ったらnpm installも終わってる
  • nodeも普通に動く
    • 多分stableのバージョンになるんじゃないかな
  • Safari(iPadOS)は今の所サポート対象外っぽい

Screen Shot 2019-11-06 at 1.04.46.png

動作確認

  • vsonline-qickstartではserver.jsが記述されていた
  • npm startでサーバを立ち上げる
  • Remote Explorer > Forwarded Ports > Port :3000 をクリックするとブラウザで動作確認できる
    • かなり時間がかかる
    • これもリクエストしてからインスタンス作ってるのかな...
    • HMRできるかどうかはまだ試してない
    • あとでwebpack dev serverで試してみる

Screen Shot 2019-11-06 at 1.07.39.png

IMG_0037.PNG

所感

もちろんmacOSアプリ版のVS Codeでローカル開発したほうがその瞬間のDXはいいんだけど、Visual Studio OnlineがあればiPad Proでコーディングできるのはメリットが大きい。
外でコーディングする機会が少ないのであればMacBookを手放してiPad Proオンリーにしても良いかもと思える素晴らしい可能性である。

普段はMac miniで開発し、外では優雅にiPad Proで開発する。
そういう生活を作っていけたらと思う。

つーかiPadOS用のアプリを作ってくれよサティア・ナデラさんッッ


kintone JS SDK (v0.7.1対応) を使ってコマンドラインでkintoneアプリを修正・反映する方法

$
0
0

はじめに

kintoneアプリを開発環境から本環境のデプロイをコマンドラインで行う方法について調べてみました。
コードはNode.jsで書いています。SDKには kintone JS SDKを利用します。

本記事の対象

  • 稼働中のアプリの修正を開発環境で行った後に、本環境にデプロイしている。
  • 本環境にログインしてGUIにて修正作業を行いたく無い。

環境

(2019-11-06現在)

  • mac OS 10.14.6
  • npm 6.9.0
  • node v12.3.1
  • kintone JS SDK 0.7.1

サンプルアプリ

スクリーンショット 2019-11-05 18.06.46.png

流れ

所謂デプロイ系APIを使います。

  1. フォームの設定をJSONで取得
  2. エディタで修正
  3. 修正したJSONにてフォームの設定を更新する

フォームの設定の取得

フィールドの一覧を取得する APIを使って、JSONでフィールドの設定情報を取得してみます。
処理にはkintone JS SDKライブラリのgetFormFields()を利用します。

インストールは下記リンク参照
https://kintone.github.io/kintone-js-sdk/latest/getting-started/quickstart-node/

コード

index.js
require('dotenv').config()constkintone=require('@kintone/kintone-js-sdk');// 認証constkintoneAuth=newkintone.Auth();constauth={username:process.env.USER_NAME,// パスワード認証時のIDpassword:process.env.PASS_WORD// パスワード認証時のパスワード};kintoneAuth.setPasswordAuth(auth);// 接続constconn={domain:process.env.USER_DOMAIN,auth:kintoneAuth};constkintoneConnection=newkintone.Connection(conn);constkintoneApp=newkintone.App({connection:kintoneConnection});constapp=process.env.APPID;// フィールド一覧取得kintoneApp.getFormFields({app}).then((response)=>{console.log(JSON.stringify(response,null,2));}).catch((error)=>{console.log(error);});

出力結果を form-fields.json などのファイルにリダイレクトしておきます。

ファイルを修正

消費税率のドロップダウンフィールドの初期値を修正します。
"revision" と "properties" も 読み込み時に必要無いため削除します。

diff --git a/form-fields.json b/form-fields.json
index a4e94f0..7bbc978 100644
--- a/form-fields.json
+++ b/form-fields.json
@@ -1,6 +1,4 @@
 {
-  "revision": "6",
-  "properties": {
     "カテゴリー": {
       "type": "CATEGORY",
       "code": "カテゴリー",
@@ -39,7 +37,7 @@
           "index": "0"
         }
       },
-      "defaultValue": "8%"
+      "defaultValue": "10%"
     },
     "更新者": {
       "type": "MODIFIER",
@@ -71,5 +69,4 @@
       "label": "作成日時",
       "noLabel": false
     }
-  }
 }

フィールドの設定を変更

フィールドの設定を変更するAPI を利用します。
実際の処理にはkintone JS SDKライブラリのupdateFormFields()を利用します。

コード

constfs=require('fs');require('dotenv').config()constkintone=require('@kintone/kintone-js-sdk');// 認証constkintoneAuth=newkintone.Auth();constauth={username:process.env.USER_NAME,// パスワード認証時のIDpassword:process.env.PASS_WORD// パスワード認証時のパスワード};kintoneAuth.setPasswordAuth(auth);// 接続constconn={domain:process.env.USER_DOMAIN,auth:kintoneAuth};constkintoneConnection=newkintone.Connection(conn);constkintoneApp=newkintone.App({connection:kintoneConnection});constapp=process.env.APPID;// ファイル読み込みconstf=fs.readFileSync('./form-fields.json','utf8');// console.log(JSON.parse(f));varfields=JSON.parse(f);// フィールド更新kintoneApp.updateFormFields({app,fields}).then((response)=>{console.log(response);}).catch((error)=>{console.log(error);});

デプロイ

デプロイというか前段の修正を反映させます。
アプリの設定を変更→アプリを更新 しても良し。API使っても良し。

APIはアプリの設定の運用環境への反映。SDKはdeployAppSettings()を使います。

コード

実際の処理の時は、パラメーターの内の revision は前段の処理の戻り値をセットする感じになるかと思います。

deploy.js
require('dotenv').config()constkintone=require('@kintone/kintone-js-sdk');// 認証constkintoneAuth=newkintone.Auth();constauth={username:process.env.USER_NAME,// パスワード認証時のIDpassword:process.env.PASS_WORD// パスワード認証時のパスワード};kintoneAuth.setPasswordAuth(auth);// 接続constconn={domain:process.env.USER_DOMAIN,auth:kintoneAuth};constkintoneConnection=newkintone.Connection(conn);constkintoneApp=newkintone.App({connection:kintoneConnection});constapp=process.env.APPID;constappsettings=[{revision:'7',app:app}];// アプリの設定の運用環境への反映kintoneApp.deployAppSettings({apps:appsettings,revert:false}).then((response)=>{console.log(response);}).catch((error)=>{console.log(error);});

デプロイ前

アプリを更新が押されて無い状態

スクリーンショット 2019-11-06 5.58.44.png

デプロイ後

アプリを更新が押されている状態

スクリーンショット 2019-11-06 10.02.48.png

ドロップダウンの初期値が10%に変更されています。
スクリーンショット 2019-11-06 10.03.47.png

感じたことなど

kintoneのAPIを試したい時、Node環境とSDKで試すのは楽で良いですね。
ただ、ドキュメントのサンプルコードが分かりにくかった。パラメータの渡すところをもう少し丁寧に書いて欲しかった。

例えば、
https://kintone.github.io/kintone-js-sdk/latest/reference/app/
の下記の箇所

const connection = new kintone.Connection(paramsConnection);
const kintoneApp = new kintone.App({connection});

パラメーターを渡す箇所は

const kintoneApp = new kintone.App({connection: connection});

と書いてある方が、自分としては分かりやすかった。

おかげで色々調べて英語版のドキュメントの読み方の理解は進みました。

もっと便利な方法

ここまで書いてきて何ですが、実際はもっと便利なツールがありますので、通常はこちらを使った方が良いかと思います。


関連リンク

npm 関連

kintone JS SDK 関連

kintone REST API 関連

Node.js (1)背景説明とインストールまとめ

$
0
0

Node.jsとは

一般的なJavascript使う場合はクライエントですが、Google社はJavascript V8 エンジン発表された、node.jsはV8入れるバックエンドサーバーとしてのオープンモジュールです。V8の特性あるので、node.jsはchromeのConsole似てる機能も付きます。それでは、Javascriptはフルスタック言語になっている。

インストール

環境:MacOS,Homebrew 2.1.16

環境バージョンアップデート
~:brew update 
安定バージョンインストール
~:brew install node 
バージョン確認
~:brew -v 
v10.13.0

NestJS 公式ドキュメント翻訳

Documentation | NestJS 【翻訳】

$
0
0

NestJS公式ドキュメント翻訳

NestJS公式ドキュメント翻訳

原文

Documentation | NestJS - A progressive Node.js web framework

イントロダクション

Nest(NestJS)は効率的でスケーラブルなNode.jsサーバサイドアプリケーションを構築するためのフレームワークです。プログレッシブJavaScriptを使用し、TypeScriptで構築され完全にサポートされます(開発者は純粋なJavaScriptでもコーディングできます)。OOP(オブジェクト指向プログラミング)、FP(関数型プログラミング)、およびFRP(関数型リアクティブプログラミング)の要素を組み合わせます。

Nestは内部でExpress(デフォルト)などの堅牢なHTTPサーバフレームワークを使用しており、オプションでFastifyを使用するように構成することもできます。

Nestはこれらの一般的なNode.jsフレームワーク(Express / Fastify)の上に抽象化レベルを提供しますが、そのAPIを開発者に直接公開します。これにより、開発者は基盤となるプラットフォームで利用可能な無数のサードパーティモジュールを自由に使用することができます。

思想

近年、Node.jsのおかげでJavaScriptはフロントエンドとバックエンドアプリケーションの両方にとってWebの「共通語」になっています。これにより、AngularReactVueのような素晴らしいプロジェクトが生まれ、開発者の生産性が向上し、高速でテスト可能で拡張可能なフロントエンドアプリケーションを作成できるようになりました。しかし、Node(およびサーバ側のJavaScript)には優れたライブラリ、ヘルパー、およびツールがたくさんありますが、いずれもアーキテクチャの問題を効果的に解決するものではありません。

Nestはすぐに使用可能なアプリケーションアーキテクチャを提供します。これにより、開発者やチームは高度にテスト可能で、スケーラブルかつ疎結合、そして保守が容易なアプリケーションを作成することができます。

インストール

開始するには、Nest CLIを使用してプロジェクトをスキャフォルドするか、スタータープロジェクトをクローンします(どちらも同じ結果になります)。

Nest CLIを使用してプロジェクトをスキャフォルドするには次のコマンドを実行します。これにより新しいプロジェクトディレクトリが作られ、ディレクトリに最初のNestファイルとサポートモジュールが追加され、プロジェクトの雛形が作成されます。初めてのユーザーには、Nest CLIを使用して新しいプロジェクトを作成することをお勧めします。最初のステップでこのアプローチを取ります。

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

またはGitでTypeScriptスタータープロジェクトをインストールするには:

$ git clone https://github.com/nestjs/typescript-starter.git project
$ cd project
$ npm install
$ npm run start

ブラウザを開き、http://localhost:3000/に移動します。

スタータープロジェクトのJavaScript版をインストールするには、上記のコマンドシーケンスでjavascript-starter.gitを使用します。

また、npm(またはyarn)を使用してコアとサポートファイルをインストールすることにより、新しいプロジェクトを手動で最初から作成することもできます。この場合はもちろん、プロジェクトの定型ファイルを自分で作成する必要があります。

$ npm i --save @nestjs/core @nestjs/common rxjs reflect-metadata

First steps | NestJS 【翻訳】

$
0
0

NestJS公式ドキュメント翻訳

NestJS公式ドキュメント翻訳

原文

First steps | NestJS - A progressive Node.js web framework

First steps

これからNestの基礎を学びます。Nestアプリケーションの重要な構成要素を理解するために、入門レベルで多くの範囲をカバーする基本的なCRUDアプリケーションを構築します。

言語

私たちはTypeScriptが大好きですが、何よりもNode.jsが大好きです。NestはTypeScriptと純粋なJavaScriptの両方と互換性があります。 Nestは最新の言語機能を利用しているため、普通のJavaScriptで使用するにはBabelコンパイラが必要です。

前提

Node.js(> = 8.9.0)がOSにインストールされていることを確認してください。

セットアップ

Nest CLIを使用すると、新しいプロジェクトを簡単に作成することができます。npmをインストールすると、OSターミナルで次のコマンドを使用して新しいNestプロジェクトを作成できます。

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

projectディレクトリが作成され、nodeモジュールと他のいくつかの定型ファイルがインストールされ、src/ディレクトリが作成されいくつかのコアファイルができます。

src
 ├─ app.controller.ts
 ├─ app.module.ts
 └─ main.ts

コアファイルの概要は次のとおりです。

app.controller.ts単一ルートの基本的なコントローラーサンプル。
app.module.tsアプリケーションのルートモジュール。
main.tsコア関数NestFactoryを使用してNestアプリケーションインスタンスを作成するアプリケーションのエントリファイル。

main.tsには、アプリケーションを起動(bootstrap)する非同期関数が含まれています。

import{NestFactory}from'@nestjs/core';import{AppModule}from'./app.module';asyncfunctionbootstrap(){constapp=awaitNestFactory.create(AppModule);awaitapp.listen(3000);}bootstrap();

Nestアプリケーションインスタンスを作成するには、コアのNestFactoryクラスを使用します。NestFactoryはアプリケーションインスタンスを作成するためのいくつかの静的メソッドを公開します。create()メソッドはINestApplicationインターフェイスを満たすアプリケーションオブジェクトを返します。このオブジェクトは、今後の章で説明する一連のメソッドを提供します。上のmain.tsの例では、HTTPリスナーを起動するだけでアプリケーションがHTTPリクエストを受け取れるようにします。

Nest CLIでスキャフォールドされたプロジェクトは、初期的なプロジェクト構造で構成され、開発者は各モジュールを専用ディレクトリに保持するという規則を守ることが推奨されます。

プラットフォーム

Nestはプラットフォームに依存しないフレームワークを目指しています。プラットフォームに依存しないため、開発者は複数の異なるタイプのアプリケーションで利用できる再利用可能な論理パーツを作成することができます。技術的にはアダプターを作成すれば、Nestは任意のNode HTTPフレームワークを利用できます。デフォルトでサポートされているHTTPプラットフォームは、expressfastifyの2つです。ニーズに最適なものを選択できます。

platform-expressExpressはNode用の有名なミニマムなWebフレームワークです。コミュニティによって実装された多くのリソースを備えた、実際によく使われる成熟したライブラリです。@nestjs/platform-expressパッケージはデフォルトで使用されます。多くのユーザーにとってExpressは十分であり、それを有効にするために何もする必要はありません。
platform-fastifyFastifyは最大の効率と速度を提供することに重点を置いた高性能で低オーバーヘッドのフレームワークです。使用方法はこちらをご覧ください。

どのプラットフォームを使用する場合でも、独自のアプリケーションインターフェイスを公開します。これらは、それぞれNestExpressApplicationおよびNestFastifyApplicationとして表示されます。

次の例のように、NestFactory.create()メソッドに型を渡すと、appオブジェクトにはその特定のプラットフォーム専用のメソッドが含まれます。ただし、基盤となるプラットフォームのAPIに実際にアクセスする場合を除き、型を指定する必要はありません。

constapp=awaitNestFactory.create<NestExpressApplication>(AppModule);

アプリケーションの実行

インストールが完了したら、OSコマンドプロンプトで次のコマンドを実行して、HTTPリクエストを待機するアプリケーションを開始できます。

$ npm run start

このコマンドでアプリが起動し、HTTPサーバはsrc/main.tsファイルで定義されたポートで待機します。アプリケーションが実行されたら、ブラウザを開いてhttp://localhost:3000/に移動してください。Hello World!のメッセージが表示されるはずです。

Controllers | NestJS 【翻訳】

$
0
0

NestJS公式ドキュメント翻訳

NestJS公式ドキュメント翻訳

原文

Controllers | NestJS - A progressive Node.js web framework

コントローラ

コントローラはリクエストを処理し、クライアントにレスポンスを返す役割を担います。

controllers1.PNG

コントローラの目的はアプリケーションに対する特定のリクエストを受信することです。ルーティングはどのコントローラがどのリクエストを受信するかを制御します。多くの場合、各コントローラには複数のルートがあり、異なるルートで異なるアクションを実行できます。

基本的なコントローラを作成するために、クラスとデコレータを使用します。デコレータはクラスを必要なメタデータに関連付け、Nestがルーティングマップを作成できるようにします(リクエストを対応するコントローラに結び付けます)。

ルーティング

次の例では@Controller()デコレータを使用します。これはコントローラを定義するために必須です。オプションのcatsのルートパスプレフィックスを指定します。@Controller()デコレータでパスプレフィックスを使用すると、関連するルートのセットを簡単にグループ化して、コードの繰り返しを最小限に抑えることができます。例えば、/customersルートの下にあるcustomerエンティティとのやり取りを管理する一連のルートをグループ化することができます。その場合、@Controller()デコレータでパスプレフィックスcustomersを指定することで、ファイル内の各ルートに対してパスのその部分を繰り返さなくてよくなります。

cats.controller.ts
import{Controller,Get}from'@nestjs/common';@Controller('cats')exportclassCatsController{@Get()findAll():string{return'This action returns all cats';}}

CLIを使用すると、$nest g controller catsコマンドを実行するだけでコントローラを作成できます。

findAll()メソッドの前の@Get()HTTPリクエストメソッドデコレータは、NestにHTTPリクエストの特定のエンドポイントのハンドラーを作成するように指示します。エンドポイントは、HTTPリクエストメソッド(この場合はGET)とルート(route)パスに対応します。このルートパスとは何でしょうか?ハンドラーのルートパスは、コントローラに宣言された(オプションの)プレフィックスと、リクエストデコレータで指定されたパスを連結したものです。すべてのルート(cats)のプレフィックスを宣言し、デコレータにはパス情報を追加していないため、NestはGET /catsリクエストをこのハンドラーにマッピングします。前述のとおり、パスにはオプションのコントローラパスプレフィックスリクエストメソッドデコレータで宣言されたパス文字列の両方が含まれます。たとえば、デコレータ@Get('profile')と組み合わせたcustomersのパスプレフィックスは、GET /customers/profileというリクエストのルートマッピングを生成します。

上記の例では、GETリクエストがこのエンドポイントに送信されると、Nestがリクエストをユーザー定義のfindAll()メソッドにルーティングします。このメソッド名は完全に任意に決められます。当然、ルートをバインドするメソッドを宣言する必要がありますが、Nestにとってメソッド名は重要ではありません。

このメソッドは200ステータスコードと関連するレスポンス(上記の例では単なる文字列)を返します。なぜそうなるのでしょうか?これを説明するために、最初にNestがレスポンスを操作するために2つの異なるオプションを使用するという概念を紹介します。

スタンダード(推奨)この組み込みメソッドを使用することでリクエストハンドラーがJavaScriptオブジェクトまたは配列を返すときに自動でJSONにシリアライズします。 ただしプリミティブ型(string,number,booleanなど)を返す場合はシリアライズしません。これにより、レスポンス処理が簡単になります。値を返すだけで、Nestは次の処理を行います。

さらに、201を使用するPOSTリクエストを除き、レスポンスのステータスコードはデフォルトで常に200です。ハンドラーレベルで@HttpCode(...)デコレータを追加することで、この動作を簡単に変更できます(ステータスコードを参照)。
ライブラリ固有ライブラリ固有(Expressなど)のレスポンスオブジェクトが使用できます。これはメソッドハンドラシグネチャの@Res()デコレータを使用して挿入します(findAll(@Res() response))。これによって、そのオブジェクトによって公開されたネイティブのレスポンス処理メソッドを使用する機能(および責任)を得ることができます。例えば、Expressではresponse.status(200).send()のようなコードでレスポンスを作成することができます。

両方のアプローチを同時に使用することはできません。 Nestはハンドラーが@Res()または@Next()を使用していることを検出し、ライブラリ固有のオプションを選択したことを示します。両方のアプローチを同時に使用すると、スタンダードアプローチはこの単一のルートに対して自動的に無効になり、期待どおりに機能しなくなります。

リクエストオブジェクト

多くの場合、ハンドラーはクライアントリクエストの詳細にアクセスする必要があります。 Nestは基盤となるプラットフォーム(デフォルトではExpress)のリクエストオブジェクトへのアクセスを提供します。@Req()デコレータをハンドラーのシグネチャに追加しNestに注入するよう指示することで、リクエストオブジェクトにアクセスすることができます。

cats.controller.ts
import{Controller,Get,Req}from'@nestjs/common';import{Request}from'express';@Controller('cats')exportclassCatsController{@Get()findAll(@Req()request:Request):string{return'This action returns all cats';}}

expressの型付け(上記のようにrequest: Requestパラメータ)を利用するには、@types/expressパッケージをインストールします。

リクエストオブジェクトはHTTPリクエストを表し、リクエストクエリ文字列、パラメーター、HTTPヘッダー、およびボディのプロパティがあります(詳細はこちら)。大抵の場合、これらのプロパティを手動で取得する必要はありません。代わりに@Body()@Query()など、すぐに使用できる専用のデコレータを使用します。以下は、提供されるデコレータとそれらが表すプレーンなプラットフォーム固有のオブジェクトのリストです。

@Request()req
@Response(), @Res()res
@Next()next
@Session()req.session
@Param(key?: string)req.params / req.params[key]
@Body(key?: string)req.body / req.body[key]
@Query(key?: string)req.query / req.query[key]
@Headers(name?: string)req.headers / req.headers[name]

※ 基盤となるHTTPプラットフォーム(Express、Fastify)での型付けとの互換性のために、Nestは@Res()および@Response()デコレータを提供します。@Res()@Response()の単なるエイリアスです。どちらも基になるネイティブプラットフォームレスポンスオブジェクトのインターフェースを直接公開します。それらを使用するときは、基礎となるライブラリ(@types/expressなど)の型付けもインポートして最大限に活用する必要があります。メソッドハンドラーに@Res()または@Response()のいずれかを付与すると、Nestがそのハンドラーのライブラリ固有モードになり、レスポンス管理を担当することに注意してください。その場合、レスポンスオブジェクト(res.json(...)res.send(...))を呼び出して何らかのレスポンスを発行する必要があります。そうしなければHTTPサーバがハングします。

独自のカスタムデコレータを作成する方法については、この章をご覧ください。

リソース

先ほどcatsリソースを取得するエンドポイントを定義しました(GETルート)。普通は新しいレコードを作成するエンドポイントも提供します。それではPOSTハンドラーを作成しましょう。

cats.controller.ts
import{Controller,Get,Post}from'@nestjs/common';@Controller('cats')exportclassCatsController{@Post()create():string{return'This action adds a new cat';}@Get()findAll():string{return'This action returns all cats';}}

とても簡単です。 Nestは@Put()@Delete()@Patch()@Options()@Head()、および@All()のように標準的なHTTPリクエストエンドポイントデコレータを提供します。それぞれが各HTTPリクエストメソッドを表します。

ルートワイルドカード

パターンベースのルートもサポートされています。例えばアスタリスクはワイルドカードとして使用され、任意の文字の組み合わせに一致します。

@Get('ab*cd')findAll(){return'This route uses a wildcard';}

'ab*cd'ルートパスは、abcdab_cdabecdなどに一致します。文字?+*、および()はルートパスで使用でき、正規表現のサブセットです。ハイフン(-)とドット(.)は、文字列ベースのパスによって文字通り解釈されます。

ステータスコード

前述のように、201であるPOSTリクエストを除き、レスポンスステータスコードはデフォルトで常に200です。ハンドラレベルで@HttpCode(...)デコレータを追加することでこれを簡単に変更できます。

@Post()@HttpCode(204)create(){return'This action adds a new cat';}

@nestjs/commonパッケージからHttpCodeをインポートしてください。

多くの場合、ステータスコードは静的ではなくさまざまな要因に依存します。その場合はライブラリ固有のレスポンス(@Res()を使用して注入)オブジェクトを使用できます(エラーの場合は例外をスローします)。

ヘッダー

カスタムレスポンスヘッダーを指定するには、@Header()デコレータまたはライブラリ固有のレスポンスオブジェクトを使用できます(そして直接res.header()を呼び出します)。

@Post()@Header('Cache-Control','none')create(){return'This action adds a new cat';}

@nestjs/commonパッケージからHeaderをインポートしてください。

リダイレクト

レスポンスを特定のURLにリダイレクトするには、@Redirect()デコレータまたはライブラリ固有のレスポンスオブジェクトを使用できます(そして直接res.redirect()を呼び出します)。@Redirect()は必須のurl引数と任意のstatusCode引数を取ります。省略した場合、statusCodeのデフォルトは302Found)です。

@Get()@Redirect('https://nestjs.com',301)

HTTPステータスコードまたはリダイレクトURLを動的に決めたい場合があります。これを行うにはルートハンドラーメソッドから次のようなオブジェクトを返します。

{"url":string,"statusCode":number}

返される値は@Redirect()デコレータに渡される引数をオーバーライドします。

@Get('docs')@Redirect('https://docs.nestjs.com',302)getDocs(@Query('version')version){if(version&&version==='5'){return{url:'https://docs.nestjs.com/v5/'};}}

ルートパラメータ

リクエストの一部として動的データを受け入れる必要がある場合、静的パスを使用したルートは機能しません(ID1catを取得するGET /cats/1など)。パラメータを使用してルートを定義するには、ルートのパスにルートパラメータトークンを追加してリクエストURLのその位置で動的な値をキャプチャします。以下では@Get()デコレータのルートパラメータトークンはこの使用法を示しています。この方法で宣言されたルートパラメータには、メソッドシグネチャに追加した@Param()デコレータを使用してアクセスできます。

@Get(':id')findOne(@Param()params):string{console.log(params.id);return`This action returns a #${params.id} cat`;}

@Param()はメソッドパラメーター(上記の例ではparams)を装飾するために使用され、メソッドの本体内でその装飾されたメソッドパラメーターのプロパティとしてルートパラメーターを使用できるようにします。このコードに見られるように、params.idを参照することでidパラメーターにアクセスできます。また、特定のパラメータートークンをデコレータに渡すことで、メソッド内でパラメータ名で直接ルートパラメーターを参照することもできます。

@nestjs/commonパッケージからParamをインポートしてください。

@Get(':id')findOne(@Param('id')id):string{return`This action returns a #${id} cat`;}

スコープ

さまざまなプログラミング言語のバックグラウンドを持つ人々にとって、Nestではあらゆるものがリクエストで共有されていることは予想外に思うかもしれません。例えばデータベースへのコネクションプール、グローバル状態のシングルトンサービスなどがあります。Node.jsはすべてのリクエストが個別のスレッドによって処理されるリクエスト/レスポンスマルチスレッドステートレスモデルに従っていないことに注意してください。そのため、シングルトンインスタンスを使用することがアプリケーションにとって完全に安全です。

ただし、GraphQLアプリケーションでのリクエストごとのキャッシュ、リクエストの追跡、マルチテナンシーなど、コントローラのリクエストベースのライフタイムが望ましい動作になる場合があります。スコープの制御方法についてはこちらをご覧ください。

非同期性

私たちはモダンJavaScriptが大好きで、データの取得はほとんど非同期であることを知っています。Nestはasync関数によって非同期をサポートしうまく機能します。

async/awaitの詳細はこちら

すべての非同期関数はPromiseを返す必要があります。これは、Nestが自動的に解決可能な遅延した値を返すことができることを意味します。この例を見てみましょう。

cats.controller.ts
@Get()asyncfindAll():Promise<any[]>{return[];}

上記のコードは完全に有効です。さらに、NestルートハンドラーはRxJSのobservableなストリームを返すことができるため強力です。 Nestはその下のソースを自動的にサブスクライブし、最後に(ストリームが完了すると)出力された値を取得します。

cats.controller.ts
@Get()findAll():Observable<any[]>{returnof([]);}

上記のアプローチはどちらも機能し、要件に合ったものを使用できます。

リクエストペイロード

先ほどのPOSTルートハンドラーの例はクライアントパラメーターを受け入れませんでした。ここでは@Body()デコレータを追加して修正しましょう。

ただし、最初に(TypeScriptを使用する場合)DTO(データ転送オブジェクト)スキーマを決定する必要があります。 DTOはネットワークを介してデータを送信する方法を定義するオブジェクトです。TypeScriptインターフェースまたはクラスを使用して、DTOスキーマを決定できます。ここではクラスを使用することをお勧めします。クラスはJavaScript ES6標準の一部であるため、コンパイルされたJavaScriptでは実際のエンティティとして保持されます。一方、TypeScriptインターフェースはトランスパイル時に削除されるため、Nestは実行時にそれらを参照できません。パイプなどの機能は、実行時に変数のメタタイプにアクセスする可能性があるためこれは重要です。

CreateCatDtoクラスを作成しましょう:

create-cat.dto.ts
exportclassCreateCatDto{readonlyname:string;readonlyage:number;readonlybreed:string;}

基本的なプロパティは3つだけです。その後、CatsController内で新しく作成されたDTOを使用できます。

cats.controller.ts
@Post()asynccreate(@Body()createCatDto:CreateCatDto){return'This action adds a new cat';}

エラーハンドリング

エラーハンドリング(例外処理など)に関する章がここにあります。

完成したリソースサンプル

以下は、いくつかの利用可能なデコレータを使用して基本的なコントローラを作成する例です。このコントローラは内部データにアクセスして操作するためのメソッドをいくつか公開しています。

cats.controller.ts
import{Controller,Get,Query,Post,Body,Put,Param,Delete}from'@nestjs/common';import{CreateCatDto,UpdateCatDto,ListAllEntities}from'./dto';@Controller('cats')exportclassCatsController{@Post()create(@Body()createCatDto:CreateCatDto){return'This action adds a new cat';}@Get()findAll(@Query()query:ListAllEntities){return`This action returns all cats (limit: ${query.limit} items)`;}@Get(':id')findOne(@Param('id')id:string){return`This action returns a #${id} cat`;}@Put(':id')update(@Param('id')id:string,@Body()updateCatDto:UpdateCatDto){return`This action updates a #${id} cat`;}@Delete(':id')remove(@Param('id')id:string){return`This action removes a #${id} cat`;}}

起動して実行

上記のコントローラは完全に定義されていますが、NestはCatsControllerが存在することをまだ認識していないため、このクラスのインスタンスは作成されません。

コントローラは常にモジュールに属しているため、@Module()デコレータ内にコントローラ配列を含めています。ルートAppModule以外のモジュールはまだ定義していないため、ここでCatsControllerを紹介します。

app.module.ts
import{Module}from'@nestjs/common';import{CatsController}from'./cats/cats.controller';@Module({controllers:[CatsController],})exportclassAppModule{}

@Module()デコレータを使用してモジュールクラスにメタデータを付与したため、Nestはどのコントローラをマウントする必要があるかを簡単に反映できるようになりました。

Appendix:ライブラリ固有のアプローチ

これまでレスポンスを操作するNestの標準的な方法について説明してきました。レスポンスを操作する2つ目の方法は、ライブラリ固有のレスポンスオブジェクトを使用することです。特定のレスポンスオブジェクトを注入するには@Res()デコレータ
を使用する必要があります。違いを示すためにCatsControllerを次のように書き換えましょう。

import{Controller,Get,Post,Res,HttpStatus}from'@nestjs/common';import{Response}from'express';@Controller('cats')exportclassCatsController{@Post()create(@Res()res:Response){res.status(HttpStatus.CREATED).send();}@Get()findAll(@Res()res:Response){res.status(HttpStatus.OK).json([]);}}

このアプローチは機能し、実際にはレスポンスオブジェクト(ヘッダー操作、ライブラリ固有の機能など)を完全に制御することにより、いくつかの方法で柔軟性を高めることができますが、慎重に使用する必要があります。一般にこのアプローチはあまり明確ではなく、いくつかの欠点があります。主な欠点はインターセプターや@HttpCode()デコレータなどのNest標準レスポンス処理に依存するNest機能との互換性が失われることです。また、コードはプラットフォームに依存する可能性があり(基になるライブラリがレスポンスオブジェクトに異なるAPIを持っている可能性があるため)、テストが難しくなります(レスポンスオブジェクトをモックする必要があります)。

そのため、可能な場合は常にNest標準アプローチを優先するべきです。

Providers | NestJS 【翻訳】

$
0
0

NestJS公式ドキュメント翻訳

NestJS公式ドキュメント翻訳

原文

Providers | NestJS - A progressive Node.js web framework

プロバイダ

プロバイダはNestの基本概念です。基本的なNestクラスの多くはサービス、リポジトリ、ファクトリー、ヘルパーなどのプロバイダとして扱われます。プロバイダのアイデアは依存関係を注入できることです。これはオブジェクトが互いにさまざまな関係を作成できることを意味し、オブジェクトのインスタンスを「繋げる」機能はNestランタイムシステムに委任できます。プロバイダは単に@Injectable()デコレータが付けられたクラスです。

キャプチャ.PNG

前の章では簡単なCatsControllerを作成しました。コントローラはHTTPリクエストを処理し、より複雑なタスクをプロバイダに委任する必要があります。プロバイダはクラス宣言の前に@Injectable()デコレータが付いたプレーンなJavaScriptクラスです。

Nestを使用するとオブジェクト指向で依存関係を設計および整理できるため、SOLIDの原則に従うことを強くお勧めします。

サービス

まずは、簡単なCatsServiceを作成してみましょう。このサービスはデータの保存と取得を担当し、CatsControllerで使用されるように設計されているためプロバイダとして定義するのに適しています。したがって、クラスを@Injectable()で装飾します。

cats.service.ts
import{Injectable}from'@nestjs/common';import{Cat}from'./interfaces/cat.interface';@Injectable()exportclassCatsService{privatereadonlycats:Cat[]=[];create(cat:Cat){this.cats.push(cat);}findAll():Cat[]{returnthis.cats;}}

CLIを使用してサービスを作成するには$nest g service catsコマンドを実行します。

CatsServiceは1つのプロパティと2つのメソッドを持つ基本的なクラスです。唯一の新しい機能は@Injectable()デコレータを使用している点です。@Injectable()デコレータは、このクラスがNestのプロバイダであることをNestに伝えるためのメタデータを付与します。ちなみに、この例では次のようなCatインターフェースを使用しています。

exportinterfaceCat{name:string;age:number;breed:string;}

catsを取得するサービスクラスができたので、CatsControllerで使用しましょう。

cats.controller.ts
import{Controller,Get,Post,Body}from'@nestjs/common';import{CreateCatDto}from'./dto/create-cat.dto';import{CatsService}from'./cats.service';import{Cat}from'./interfaces/cat.interface';@Controller('cats')exportclassCatsController{constructor(privatereadonlycatsService:CatsService){}@Post()asynccreate(@Body()createCatDto:CreateCatDto){this.catsService.create(createCatDto);}@Get()asyncfindAll():Promise<Cat[]>{returnthis.catsService.findAll();}}

CatsServiceはコンストラクタを通して注入されます。private readonly構文が使用されていることに注意してください。これによって、catsServiceメンバーを宣言し同じ場所ですぐに初期化することができます。

依存性の注入

Nestは一般的に依存性注入と呼ばれる強力な設計パターンをもとに構築されています。Angularの公式ドキュメントにこの概念に関するわかりやすい記事があるので読むことをお勧めします。

NestではTypeScriptのおかげで依存関係は型ごとに解決されるため、非常に簡単に管理することができます。次の例では、NestはCatsServiceのインスタンスを作成して返すことによりcatsServiceを解決します(または一般的なシングルトンの場合、既に他の場所で使用されている場合は既存のインスタンスを返します)。この依存関係が解決され、コントローラのコンストラクタに渡されます(または指定されたプロパティに割り当てられます)。

constructor(privatereadonlycatsService:CatsService){}

スコープ

プロバイダは通常、アプリケーションのライフサイクルと同期した有効期間(「スコープ」)を持っています。アプリケーションがブートストラップされると、すべての依存関係を解決するためにすべてのプロバイダをインスタンス化する必要があります。同様に、アプリケーションが終了するとプロバイダは破棄されます。ただし、プロバイダの有効期間をリクエストスコープにする方法もあります。この手法の詳細についてはこちらをご覧ください。

カスタムプロバイダ

Nestにはプロバイダ間の関係を解決する組み込みの制御の反転(「IoC」)コンテナがあります。この機能は前述の依存性注入機能の基礎になっていますが、実際にはこれまで説明してきた機能よりもはるかに強力です。@Injectable()デコレータは氷山の一角にすぎず、プロバイダを定義する唯一の方法ではありません。実際プレーンな値、クラス、および非同期または同期ファクトリーを使用できます。ここでさらに例を示します。

オプションのプロバイダ

場合によっては必ずしも解決する必要のない依存関係の場合あります。たとえば、クラスは構成オブジェクトに依存する場合がありますが、何も渡されない場合はデフォルト値を使用する必要があります。このような場合プロバイダがなくてもエラーにならないため、依存関係はオプションになります。

プロバイダがオプションであることを示すには、コンストラクタで@Optional()デコレータを使用します。

import{Injectable,Optional,Inject}from'@nestjs/common';@Injectable()exportclassHttpService<T>{constructor(@Optional()@Inject('HTTP_OPTIONS')privatereadonlyhttpClient:T){}}

上記の例ではカスタムプロバイダを使用していることに注意してください。これがHTTP_OPTIONSカスタムトークンを含める理由です。前の例では、コンストラクタのクラスを介した依存関係を示すコンストラクタベースの注入を示しました。カスタムプロバイダとその関連トークンの詳細についてはこちらをご覧ください。

プロパティベースの注入

プロバイダはコンストラクタを介して注入されるため、これまで使用してきた手法はコンストラクタベースの注入と呼ばれます。非常に特殊なケースですがプロパティベースの注入が役立つ場合があります。たとえば、トップレベルのクラスが1つまたは複数のプロバイダに依存している場合、コンストラクタからサブクラスのsuper()を呼び出してそれらを渡すことは非常に面倒です。これを回避するためにプロパティレベルで@Inject()デコレータを使用できます。

import{Injectable,Inject}from'@nestjs/common';@Injectable()exportclassHttpService<T>{@Inject('HTTP_OPTIONS')privatereadonlyhttpClient:T;}

ただし、クラスが別のプロバイダを拡張しない場合はコンストラクタベースのインジェクションを常に使用することをお勧めします。

プロバイダの登録

プロバイダ(CatsService)を定義しそのサービスのコンシューマー(CatsController)を取得したので、注入を実行できるようにNestにサービスを登録する必要があります。これを行うには、モジュールファイル(app.module.ts)を編集し、@Module()デコレータのproviders配列にサービスを追加します。

app.module.ts
import{Module}from'@nestjs/common';import{CatsController}from'./cats/cats.controller';import{CatsService}from'./cats/cats.service';@Module({controllers:[CatsController],providers:[CatsService],})exportclassAppModule{}

これでNestはCatsControllerクラスの依存関係を解決できるようになります。

ここまででディレクトリ構造は次のようになります。

src
 ├── cats
 │   ├── dto
 │   │    └── create-cat.dto.ts
 │   ├── interfaces
 │   │    └── cat.interface.ts
 │   ├── cats.service.ts
 │   └── cats.controller.ts
 ├── app.module.ts
 └── main.ts

Modules | NestJS 【翻訳】

$
0
0

NestJS公式ドキュメント翻訳

NestJS公式ドキュメント翻訳

原文

Modules | NestJS - A progressive Node.js web framework

モジュール

モジュールは@Module()デコレータが付けられたクラスです。@Module()デコレータはNestがアプリケーション構造を整理するために使用するメタデータを提供します。

sf.PNG

各アプリケーションには少なくとも1つのモジュール、ルート(root)モジュールがあります。ルートモジュールはNestがアプリケーショングラフの構築の始点です。Nestはモジュールとプロバイダの関係と依存関係を解決するために使用する内部データ構造です。非常に小さなアプリケーションでは、理論的にはルートモジュールがありさえすればよいですが、これは一般的なケースではありません。コンポーネントを整理する効果的な方法としてモジュール化を強くお勧めします。ほとんどのアプリケーションではアーキテクチャは複数のモジュールを使用し、各モジュールは密接に関連する機能ごとにカプセル化します。

@Module()デコレータはプロパティがモジュールを記述する単一のオブジェクトを受け取ります

providersNestインジェクタによってインスタンス化され、少なくともそのモジュール全体で共有されるプロバイダ
controllersインスタンス化する必要があるそのモジュールで定義されたコントローラのセット
importsそのモジュールで必要なプロバイダをエクスポートするインポートされたモジュールのリスト
exportsそのモジュールによって提供され、そのモジュールをインポートする他のモジュールで利用できるprovidersのサブセット

モジュールはデフォルトでプロバイダをカプセル化します。これは、現在のモジュールの直接的な一部ではなく、インポートされたモジュールからエクスポートされていないプロバイダを注入することは不可能であることを意味します。したがって、モジュールからエクスポートされたプロバイダは、モジュールのパブリックインターフェースまたはAPIと見なすことができます。

機能モジュール

CatsControllerCatsServiceは同じアプリケーションドメインに属します。これらは密接に関連しているため、機能モジュールに移動することは理にかなっています。機能モジュールは特定の機能に関連するコードを整理し明確な境界を確立します。これにより、アプリケーションやチームの規模が大きくなっても、複雑性を管理しつつSOLIDの原則に従って開発することができます。

これを実証するためにCatsModuleを作成します。

cats/cats.module.ts
import{Module}from'@nestjs/common';import{CatsController}from'./cats.controller';import{CatsService}from'./cats.service';@Module({controllers:[CatsController],providers:[CatsService],})exportclassCatsModule{}

CLIを使用してモジュールを作成するには、単に$nest g module catsコマンドを実行します。

上記では、cats.module.tsファイルでCatsModuleを定義し、このモジュールに関連するすべてのものをcatsディレクトリに移動しました。最後に、このモジュールをルートモジュール(app.module.tsファイルで定義されているAppModule)にインポートします。

app.module.ts
import{Module}from'@nestjs/common';import{CatsModule}from'./cats/cats.module';@Module({imports:[CatsModule],})exportclassAppModule{}

ディレクトリ構造は次のようになります。

src
 ├── cats
 │   ├── dto
 │   │    └── create-cat.dto.ts
 │   ├── interfaces
 │   │    └── cat.interface.ts
 │   ├── cats.service.ts
 │   ├── cats.controller.ts
 │   └── cats.module.ts
 ├── app.module.ts
 └── main.ts

共有モジュール

Nestではモジュールはデフォルトでシングルトンなので、複数のモジュール間で任意のプロバイダの同じインスタンスを簡単に共有することができます。

キャプチャf.PNG

すべてのモジュールは自動的に共有モジュールです。作成すると任意のモジュールで再利用できます。CatsServiceのインスタンスを他のいくつかのモジュール間で共有したいとします。これを行うには、最初にCatsServiceプロバイダをモジュールのexports配列に追加して、以下のようにエクスポートする必要があります。

cats.module.ts
import{Module}from'@nestjs/common';import{CatsController}from'./cats.controller';import{CatsService}from'./cats.service';@Module({controllers:[CatsController],providers:[CatsService],exports:[CatsService]})exportclassCatsModule{}

これでCatsModuleをインポートするモジュールはCatsServiceにアクセスでき、同じインスタンスをそれをインポートする他のすべてのモジュールで共有します。

モジュールの再エクスポート

上記のように、モジュールは内部プロバイダをエクスポートできます。さらに、インポートしたモジュールを再エクスポートできます。次の例では、CommonModuleCoreModuleにインポートおよびエクスポートされるため、このモジュールをインポートする他のモジュールで使用できます。

@Module({imports:[CommonModule],exports:[CommonModule],})exportclassCoreModule{}

依存性の注入

モジュールクラスはプロバイダを注入することもできます(構成上のためなど)。

cats.module.ts
import{Module}from'@nestjs/common';import{CatsController}from'./cats.controller';import{CatsService}from'./cats.service';@Module({controllers:[CatsController],providers:[CatsService],})exportclassCatsModule{constructor(privatereadonlycatsService:CatsService){}}

ただし、循環依存となるためモジュールクラス自体をプロバイダとして注入することはできません。

グローバルモジュール

どこにでも同じモジュールのセットをインポートする場合、冗長になる可能性があります。 Nestとは異なり、Angularprovidersはグローバルスコープに登録されます。定義すればどこでも利用できます。しかし、Nestはプロバイダをモジュールスコープ内にカプセル化します。最初にカプセル化したモジュールをインポートしないと、モジュールのプロバイダを他の場所で使用することはできません。

すぐに使用できるプロバイダのセット(ヘルパー、データベース接続など)を提供する場合は、@Global()デコレータを使用してモジュールをグローバルにします。

import{Module,Global}from'@nestjs/common';import{CatsController}from'./cats.controller';import{CatsService}from'./cats.service';@Global()@Module({controllers:[CatsController],providers:[CatsService],exports:[CatsService],})exportclassCatsModule{}

@Global()デコレータはモジュールをグローバルスコープにします。グローバルモジュールは、通常はルートモジュールまたはコアモジュールによって一度だけ登録する必要があります。上記の例では、CatsServiceプロバイダはどこにでもあり、サービスを注入したいモジュールはimports配列にCatsModuleをインポートする必要はありません。

すべてをグローバルにすることは設計上よくありません。必要なボイラープレートを減らすために、グローバルモジュールを使用しましょう。imports配列はモジュールのAPIを使う側が利用できるようにするための方法です。

動的モジュール

Nestモジュールシステムには、動的モジュールと呼ばれる強力な機能が含まれています。この機能により、プロバイダを動的に登録および構成するカスタマイズ可能なモジュールを簡単に作成できます。動的モジュールについてはここで詳しく説明します。この章では簡単な概要を示します。

DatabaseModuleの動的モジュール定義の例を次に示します。

import{Module,DynamicModule}from'@nestjs/common';import{createDatabaseProviders}from'./database.providers';import{Connection}from'./connection.provider';@Module({providers:[Connection],})exportclassDatabaseModule{staticforRoot(entities=[],options?):DynamicModule{constproviders=createDatabaseProviders(options,entities);return{module:DatabaseModule,providers:providers,exports:providers,};}}

forRoot()メソッドは、動的モジュールを同期的または非同期的に(Promiseを介して)返します。

このモジュールは@Module()デコレータメタデータでConnectionプロバイダを定義しますが、さらに(forRoot()メソッドに渡されるentitiesoptionsオブジェクトに応じて)リポジトリなどのプロバイダのコレクションを公開します。動的モジュールによって返されるプロパティは@Module()デコレータで定義されたベースモジュールメタデータを(オーバーライドするのではなく)拡張することに注意してください。これが、静的に宣言されたConnectionプロバイダ動的に生成されたリポジトリプロバイダの両方をモジュールからエクスポートする方法です。

上記のように定義されると、DatabaseModuleは次の方法でインポートおよび構成されます。

import{Module}from'@nestjs/common';import{DatabaseModule}from'./database/database.module';import{User}from'./users/entities/user.entity';@Module({imports:[DatabaseModule.forRoot([User])],})exportclassAppModule{}

動的モジュールを順番に再エクスポートする場合、exports配列ではforRoot()メソッド呼び出しを省略できます。

import{Module}from'@nestjs/common';import{DatabaseModule}from'./database/database.module';import{User}from'./users/entities/user.entity';@Module({imports:[DatabaseModule.forRoot([User])],exports:[DatabaseModule],})exportclassAppModule{}

動的モジュールの章では、このトピックをより詳細に説明し実例を紹介します。

Middleware | NestJS 【翻訳】

$
0
0

NestJS公式ドキュメント翻訳

NestJS公式ドキュメント翻訳

原文

Middleware| NestJS - A progressive Node.js web framework

ミドルウェア

ミドルウェアはルートハンドラのに呼び出される関数です。ミドルウェア関数はリクエストオブジェクトとレスポンスオブジェクト、およびアプリケーションのリクエスト/レスポンスサイクルのnext()ミドルウェア関数にアクセスできます。nextミドルウェア関数は、一般にnextという名前の変数によって示されます。

キャプチャ.PNG

Nestのミドルウェアは、デフォルトではexpressのミドルウェアと同等です。Express公式ドキュメントによると、以下のようにミドルウェアの機能について説明しています。

ミドルウェア機能では、次のタスクを実行できます。
  ・コードの実行
  ・リクエストおよびレスポンスオブジェクトに変更を加える
  ・リクエストとレスポンスのサイクルを終了する
  ・スタック内の次のミドルウェア関数を呼び出す
  ・現在のミドルウェア関数でリクエストとレスポンスのサイクルを終了しない場合、次のミドルウェア関数に制御を渡すためにnext()を呼び出す必要があります。そうしなければリクエストがハングします。

カスタムNestミドルウェアは、関数または@Injectable()デコレータを持つクラスのいずれかに実装します。クラスにはNestMiddlewareインターフェースを実装する必要がありますが、関数に特別な要件はありません。クラスメソッドを使用して簡単なミドルウェア機能を実装することから始めましょう。

logger.middleware.ts
import{Injectable,NestMiddleware}from'@nestjs/common';import{Request,Response}from'express';@Injectable()exportclassLoggerMiddlewareimplementsNestMiddleware{use(req:Request,res:Response,next:Function){console.log('Request...');next();}}

依存性の注入

Nestミドルウェアは依存性の注入を完全にサポートしています。プロバイダやコントローラと同様に、同じモジュール内で利用可能な依存関係を注入できます。これもconstructorを介して行われます。

ミドルウェアの適用

@Module()デコレータにはミドルウェアを記述する場所がありません。代わりに、モジュールクラスのconfigure()メソッドを使用してセットアップします。ミドルウェアを含むモジュールは、NestModuleインターフェイスを実装する必要があります。 AppModuleレベルでLoggerMiddlewareをセットアップしましょう。

app.module.ts
import{Module,NestModule,MiddlewareConsumer}from'@nestjs/common';import{LoggerMiddleware}from'./common/middleware/logger.middleware';import{CatsModule}from'./cats/cats.module';@Module({imports:[CatsModule],})exportclassAppModuleimplementsNestModule{configure(consumer:MiddlewareConsumer){consumer.apply(LoggerMiddleware).forRoutes('cats');}}

上記の例では、以前にCatsController内で定義された/catsルートハンドラー用のLoggerMiddlewareをセットアップしました。ミドルウェアの設定時に、ルートpathとリクエストmethodを含むオブジェクトをforRoutes()メソッドに渡すことにより、特定のリクエストメソッドに対するミドルウェアをにさらに制限することもできます。以下の例では、RequestMethodをインポートして、目的のリクエストメソッド型を参照していることに注意してください。

app.module.ts
import{Module,NestModule,RequestMethod,MiddlewareConsumer}from'@nestjs/common';import{LoggerMiddleware}from'./common/middleware/logger.middleware';import{CatsModule}from'./cats/cats.module';@Module({imports:[CatsModule],})exportclassAppModuleimplementsNestModule{configure(consumer:MiddlewareConsumer){consumer.apply(LoggerMiddleware).forRoutes({path:'cats',method:RequestMethod.GET});}}

configure()メソッドはasync/awaitを使用して非同期にすることができます(たとえば、configure()内で非同期操作の完了をawaitすることができます)。

ルートワイルドカード

パターンベースのルートもサポートされています。たとえば、アスタリスクはワイルドカードとして使用され、任意の文字の組み合わせに一致します。

forRoutes({path:'ab*cd',method:RequestMethod.ALL});

'ab*cd'ルートパスはabcdab_cdabecdなどに一致します。文字+*、および()はルートパスで使用でき、正規表現のサブセットです。ハイフン(-)とドット(.)は文字列ベースのパスによって文字通り解釈されます。

ミドルウェアコンシューマ

MiddlewareConsumerはヘルパークラスです。ミドルウェアを管理するためのいくつかの組み込みメソッドを提供します。それらのすべては、fluent styleで簡単に連鎖することができます。forRoutes()メソッドは単一の文字列、複数の文字列、RouteInfoオブジェクト、コントローラクラス、さらには複数のコントローラクラスを受け取ることができます。ほとんどの場合は、おそらくコンマで区切られたコントローラのリストを渡すだけでしょう。以下は、単一のコントローラを使用した例です。

app.module.ts
import{Module,NestModule,MiddlewareConsumer}from'@nestjs/common';import{LoggerMiddleware}from'./common/middleware/logger.middleware';import{CatsModule}from'./cats/cats.module';import{CatsController}from'./cats/cats.controller.ts';@Module({imports:[CatsModule],})exportclassAppModuleimplementsNestModule{configure(consumer:MiddlewareConsumer){consumer.apply(LoggerMiddleware).forRoutes(CatsController);}}

apply()メソッドは単一のミドルウェアまたは複数の引数を取り、複数のミドルウェアを指定できます。

多くの場合、特定のルートをミドルウェアの適用にしたい場合があります。(関数ミドルウェアを使用するのではなく、これまで行ってきたように)クラスでミドルウェアを定義する場合、exclude()メソッドを使用して特定のルートを簡単に除外できます。以下に示すように、このメソッドは除外するpathmethodを識別する1つ以上のオブジェクトを取ります。

consumer.apply(LoggerMiddleware).exclude({path:'cats',method:RequestMethod.GET},{path:'cats',method:RequestMethod.POST}).forRoutes(CatsController);

上記の例では、LoggerMiddlewareexclude()メソッドに渡される2つを除くCatsController内で定義されたすべてのルートにバインドされます。exclude()メソッドは関数ミドルウェア(クラスではなく関数で定義されたミドルウェア。詳細は以下を参照)では機能しないことに注意してください。さらに、このメソッドはより一般的なルート(ワイルドカードなど)からのパスを除外しません。そのレベルの制御が必要な場合は、パスによって制限するロジックをミドルウェアに直接持たせましょう。たとえば、リクエストURLにアクセスしてミドルウェアロジックを条件付きで適用する必要があります。

関数ミドルウェア

私たちが使用しているLoggerMiddlewareクラスは非常に単純です。メンバー、追加のメソッド、依存関係はありません。クラスではなく単純な関数で定義できないのはなぜでしょうか?実は、関数化することができます。このタイプのミドルウェアは、関数ミドルウェアと呼ばれます。ロガーミドルウェアをクラスベースから関数ミドルウェアに変換して、その違いを説明します。

logger.middleware.ts
exportfunctionlogger(req,res,next){console.log(`Request...`);next();};

AppModule内で使用します。

app.module.ts
consumer.apply(logger).forRoutes(CatsController);

ミドルウェアが依存関係を必要としない場合は、よりシンプルな関数ミドルウェアを代わりに使用することを検討してください。

マルチミドルウェア

前述のように、順次実行される複数のミドルウェアをバインドするにはapply()メソッド内にカンマ区切りのリストを指定するだけです。

consumer.apply(cors(),helmet(),logger).forRoutes(CatsController);

グローバルミドルウェア

ミドルウェアをすべての登録済みルートに一度にバインドする場合、INestApplicationインスタンスによって提供されるuse()メソッドを使用できます。

constapp=awaitNestFactory.create(AppModule);app.use(logger);awaitapp.listen(3000);

Exception filters | NestJS 【翻訳】

$
0
0

NestJS公式ドキュメント翻訳

NestJS公式ドキュメント翻訳

原文

Exception filters| NestJS - A progressive Node.js web framework

例外フィルター

Nestにはアプリケーション全体で未処理のすべての例外を処理する組み込みの例外レイヤーが付属しています。例外がアプリケーションコードによって処理されない場合、このレイヤーがキャッチし、適切なユーザーフレンドリーなレスポンスを自動的に送信します。

キャプチャ.PNG

この処理は組み込みのグローバルな例外フィルターによって実行されます。このフィルターはHttpException型の例外(およびそのサブクラス)を処理します。例外が認識されない場合(HttpExceptionでもHttpExceptionを継承するクラスでもない場合)、組み込みの例外フィルターは次のデフォルトのJSONレスポンスを生成します。

{"statusCode":500,"message":"Internal server error"}

標準的な例外のスロー

Nestは@nestjs/commonパッケージで公開されている組み込みのHttpExceptionクラスを提供します。一般的なHTTP REST / GraphQL APIベースのアプリケーションでは、特定のエラー状態が発生したときに標準HTTPレスポンスオブジェクトを送信することがベストプラクティスです。たとえば、CatsControllerにはfindAll()メソッド(GETルートハンドラー)があります。このルートハンドラーが何らかの理由で例外をスローすると仮定しましょう。これを実証するために、次のようにハードコードしてみましょう。

cats.controller.ts
@Get()asyncfindAll(){thrownewHttpException('Forbidden',HttpStatus.FORBIDDEN);}

ここではHttpStatusを使用しました。これは@nestjs/commonパッケージからインポートされたヘルパー列挙です。

クライアントがこのエンドポイントを呼び出すと、レスポンスは次のようになります。

{"statusCode":403,"message":"Forbidden"}

HttpExceptionコンストラクタは、レスポンスを決定する2つの必須の引数を取ります。

  • response引数はJSONレスポンスボディを定義します。以下で説明するようにstringまたはobjectにすることができます。
  • status引数はHTTPステータスコードを定義します。

デフォルトではJSONレスポンスボディには2つのプロパティが含まれています。

  • statusCode:デフォルトはstatus引数で提供されるHTTPステータスコード
  • messagestatusに基づくHTTPエラーの簡単な説明

JSONレスポンスボディのメッセージ部分のみをオーバーライドするには、response引数に文字列を指定します。

JSONレスポンスボディ全体をオーバーライドするにはresponse引数にオブジェクトを渡します。Nestはオブジェクトをシリアル化しJSONレスポンスボディとして返します。

2番目のコンストラクタ引数- status -は有効なHTTPステータスコードである必要があります。ベストプラクティスは@nestjs/commonからインポートしたHttpStatus列挙を使用することです。

レスポンスボディ全体をオーバーライドする例を次に示します。

cats.controller.ts
@Get()asyncfindAll(){thrownewHttpException({status:HttpStatus.FORBIDDEN,error:'This is a custom message',},403);}

上記を使用するとレスポンスは次のようになります。

{"status":403,"error":"This is a custom message"}

カスタム例外

多くの場合、独自の例外を記述する必要はなく、次のセクションで説明するように組み込みのNest HTTPエクセプションを使用できます。カスタマイズされた例外を作成する必要がある場合、独自の例外レイヤーを作成することをお勧めします。カスタム例外はHttpExceptionクラスを継承します。このアプローチにより、Nestは例外を認識しエラーレスポンスを自動的に処理します。そのようなカスタム例外を実装してみましょう:

forbidden.exception.ts
exportclassForbiddenExceptionextendsHttpException{constructor(){super('Forbidden',HttpStatus.FORBIDDEN);}}

ForbiddenExceptionHttpExceptionを継承することで組み込みの例外ハンドラーとシームレスに連携するため、findAll()メソッド内で使用できます。

cats.controller.ts
@Get()asyncfindAll(){thrownewForbiddenException();}

組み込みHTTPエクセプション

NestはHttpExceptionから継承する標準例外のセットを提供します。これらは@nestjs/commonパッケージから公開され、一般的なHTTP例外の多くを表します。

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableEntityException
  • InternalServerErrorException
  • NotImplementedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException

例外フィルター

基本(組み込み)例外フィルターは多くのケースを自動的に処理できますが、例外レイヤーを開発者が完全に制御したい場合があります。例えば、ログを追加したり、いくつかの動的要因に基づいて異なるJSONスキーマを使用したりすることができます。例外フィルターはまさにこの目的のために設計されています。これによって、正確な制御フローとクライアントに返されるレスポンスをコントロールすることができます。

HttpExceptionクラスのインスタンスである例外をキャッチし、それらのカスタムレスポンスロジックを実装する例外フィルターを作成しましょう。これには基盤となるプラットフォームのRequestおよびResponseオブジェクトにアクセスする必要があります。Requestオブジェクトにアクセスして、元のurlを引き出してログ情報に含めてみましょう。response.json()メソッドによってResponseオブジェクトを使用して、送信されるレスポンスを直接制御します。

http-exception.filter.ts
import{ExceptionFilter,Catch,ArgumentsHost,HttpException}from'@nestjs/common';import{Request,Response}from'express';@Catch(HttpException)exportclassHttpExceptionFilterimplementsExceptionFilter{catch(exception:HttpException,host:ArgumentsHost){constctx=host.switchToHttp();constresponse=ctx.getResponse<Response>();constrequest=ctx.getRequest<Request>();conststatus=exception.getStatus();response.status(status).json({statusCode:status,timestamp:newDate().toISOString(),path:request.url,});}}

すべての例外フィルターはExceptionFilter<T>インターフェースを実装する必要があります。これにはcatch(exception:T、host:ArgumentsHost)メソッドに指定のシグネチャを提供する必要があります。Tは例外の型を示します。

@Catch(HttpException)デコレータは必要なメタデータを例外フィルターにバインドし、この特定のフィルターがHttpException型の例外を探していることをNestに伝えます。@Catch()デコレータは単一のパラメータ、またはコンマ区切りのリストを取ることができます。これにより、複数の種類の例外のフィルターを一度に設定することができます。

Arguments host

catch()メソッドのパラメーターを見てみましょう。exceptionパラメーターは現在処理されている例外オブジェクトです。hostパラメーターはArgumentsHostオブジェクトです。ArgumentsHostは強力なユーティリティオブジェクトであり、他の章でさらに詳しく調べます※。ここでの主な目的は、(例外の発生元のコントローラー内の)元のリクエストハンドラーに渡されるRequestおよびResponseオブジェクトへの参照を提供することです。ここではArgumentsHostでいくつかのヘルパーメソッドを使用して、目的のRequestおよびResponseオブジェクトを取得しました。

host.switchToHttp()ヘルパーはHttpArgumentsHostオブジェクトを返します。HttpArgumentsHostオブジェクトには2つの便利なメソッドがあります。これらのメソッドを使用して目的のオブジェクトを抽出し、この場合はExpressの型アサーションを使用してネイティブExpress型オブジェクトを返します。

constresponse=ctx.getResponse<Response>();constrequest=ctx.getRequest<Request>();

※ このレベルの抽象化をする理由は、ArgumentsHostがすべてのコンテキスト(たとえば、現在作業しているHTTPサーバコンテキストだけでなく、マイクロサービスやソケット)で機能するためです。後でArgumentsHostとそのヘルパー関数を使用して、実行コンテキストの適切な基本引数にアクセスする方法について説明します。これにより、すべてのコンテキストで動作する一般的な例外フィルターを作成できます。

Binding filters

新しいHttpExceptionFilterCatsControllercreate()メソッドに関連付けましょう。

cats.controller.ts
@Post()@UseFilters(newHttpExceptionFilter())asynccreate(@Body()createCatDto:CreateCatDto){thrownewForbiddenException();}

@UseFilters()デコレータは@nestjs/commonパッケージからインポートされます。

ここでは@UseFilters()デコレータを使用しました。@Catch()デコレータと同様に、フィルターインスタンス、またはコンマ区切りのフィルターインスタンスのリストを使用できます。ここではHttpExceptionFilterのインスタンスを作成しました。(インスタンスの代わりに)クラスを渡し、インスタンス化の責任をフレームワークに委ね、依存性注入を有効にすることもできます。

cats.controller.ts
@Post()@UseFilters(HttpExceptionFilter)asynccreate(@Body()createCatDto:CreateCatDto){thrownewForbiddenException();}

可能な場合、インスタンスの代わりにクラスを使用してフィルターを適用することをお勧めします。 Nestはモジュール全体で同じクラスのインスタンスを簡単に再利用できるため、メモリ使用量が削減されます。

上記の例では、HttpExceptionFiltercreate()ルートハンドラーにのみ適用され、メソッドスコープになります。例外フィルターは、メソッドスコープ、コントローラスコープ、またはグローバルスコープのさまざまなレベルでスコープすることができます。例えば、フィルターをコントローラスコープとして設定するには次のようにします。

cats.controller.ts
@UseFilters(newHttpExceptionFilter())exportclassCatsController{}

この構造はCatsController内で定義されたすべてのルートハンドラーに対してHttpExceptionFilterをセットアップします。

グローバルスコープのフィルターを作成するには、次の操作を行います。

main.ts
asyncfunctionbootstrap(){constapp=awaitNestFactory.create(AppModule);app.useGlobalFilters(newHttpExceptionFilter());awaitapp.listen(3000);}bootstrap();

useGlobalFilters()メソッドはゲートウェイまたはハイブリッドアプリケーションのフィルターを設定しません。

グローバルスコープフィルターはすべてのコントローラーとすべてのルートハンドラーに対して、アプリケーション全体で使用されます。依存性注入に関して、モジュールの外部から登録されたグローバルフィルター(上記の例でのuseGlobalFilters())はモジュールのコンテキスト外で行われるため、依存性を注入できません。この問題を解決するには、次の構成を使用して、任意のモジュールからグローバルスコープフィルターを直接登録します。

app.module.ts
import{Module}from'@nestjs/common';import{APP_FILTER}from'@nestjs/core';@Module({providers:[{provide:APP_FILTER,useClass:HttpExceptionFilter,},],})exportclassAppModule{}

このアプローチでフィルターの依存性注入を行う場合、この方法が使われるモジュールに関係なく、フィルターは実際にはグローバルであることに注意してください。これはどこで行われるのでしょうか?フィルター(上記の例ではHttpExceptionFilter)が定義されているモジュールを選択します。また、カスタムプロバイダーの登録を処理する方法はuseClassだけではありません。詳細はこちらをご覧ください。

この方法で必要な数だけフィルターを追加できます。それぞれをプロバイダ配列に追加するだけです。

Catch everything

未処理の例外をすべて(例外の種類に関係なく)キャッチするには、@Catch()デコレータのパラメータリストを空のままにします(例:@Catch()

import{ExceptionFilter,Catch,ArgumentsHost,HttpException,HttpStatus}from'@nestjs/common';@Catch()exportclassAllExceptionsFilterimplementsExceptionFilter{catch(exception:unknown,host:ArgumentsHost){constctx=host.switchToHttp();constresponse=ctx.getResponse();constrequest=ctx.getRequest();conststatus=exceptioninstanceofHttpException?exception.getStatus():HttpStatus.INTERNAL_SERVER_ERROR;response.status(status).json({statusCode:status,timestamp:newDate().toISOString(),path:request.url,});}}

上記の例ではフィルターはその型(クラス)に関係なく、スローされた各例外をキャッチします。

継承

通常、アプリケーション要件を満たすように完全にカスタマイズされた例外フィルターを作成するでしょう。しかし、組み込みのデフォルトのグローバル例外フィルターを単純に拡張し、特定の要因に基づいて動作をオーバーライドするというユースケースがあります。

例外処理を基本フィルターに委任するには、BaseExceptionFilterを拡張し、継承されたcatch()メソッドを呼び出す必要があります。

all-exceptions.filter.ts
import{Catch,ArgumentsHost}from'@nestjs/common';import{BaseExceptionFilter}from'@nestjs/core';@Catch()exportclassAllExceptionsFilterextendsBaseExceptionFilter{catch(exception:unknown,host:ArgumentsHost){super.catch(exception,host);}}

BaseExceptionFilterを拡張したメソッドスコープおよびコントローラスコープのフィルターは、newでインスタンス化しないでください。その代わりにフレームワークがそれらを自動的にインスタンス化します。

上記の実装は一つのアプローチを示す単なるシェルです。拡張例外フィルターの実装にはビジネスロジック(さまざまな条件の処理など)が含まれます。

グローバルフィルターはベースフィルターを拡張することができます。これは2つの方法で実行できます。

一つ目の方法はカスタムグローバルフィルターをインスタンス化するときにHttpServer参照を注入することです。

asyncfunctionbootstrap(){constapp=awaitNestFactory.create(AppModule);const{httpAdapter}=app.get(HttpAdapterHost);app.useGlobalFilters(newAllExceptionsFilter(httpAdapter));awaitapp.listen(3000);}bootstrap();

2つ目の方法は、次に示すようにAPP_FILTERトークンを使用することです。

Pipes | NestJS 【翻訳】

$
0
0

NestJS公式ドキュメント翻訳

NestJS公式ドキュメント翻訳

原文

Pipes| NestJS - A progressive Node.js web framework

パイプ

パイプは@Injectable()デコレータが付けられたクラスです。パイプはPipeTransformインターフェースを実装する必要があります。

キャプチャ.PNG

パイプには2つの典型的な使用例があります。

  • 変換:入力データを目的の出力に変換します
  • バリデーション:入力データを評価し、有効であればそのまま変更せずに渡します。データが正しくないときは例外をスローします。

どちらの場合も、パイプはコントローラルートハンドラーによって処理されているargumentsで動作します。Nestはメソッドが呼び出される直前にパイプを挿入し、パイプはメソッド宛の引数を受け取ります。その時点で変換またはバリデートが行われた後、ルートハンドラーが(潜在的に)変換された引数で呼び出されます。

パイプは例外ゾーン内で実行されます。これはパイプが例外をスローすると例外レイヤー(グローバル例外フィルターと現在のコンテキストに適用される例外フィルター)によって処理されることを意味します。つまり、Pipeで例外がスローされるとその後コントローラメソッドは実行されません。

組み込みパイプ

NestにはValidationPipeParseIntPipeParseUUIDPipeの3つのパイプがあり、すぐに使用することができます。@nestjs/commonパッケージからエクスポートされます。これらがどのように機能するかをよりよく理解するために、それらをゼロから構築してみましょう。

ValidationPipeから始めましょう。まずは、単純に入力値を取得しすぐに同じ値を返すようにします。

validation.pipe.ts
import{PipeTransform,Injectable,ArgumentMetadata}from'@nestjs/common';@Injectable()exportclassValidationPipeimplementsPipeTransform{transform(value:any,metadata:ArgumentMetadata){returnvalue;}}

PipeTransform<T, R>は汎用インターフェイスで、Tは入力値の型を、Rtransform()メソッドの戻り値の型を示します。

すべてのパイプはtransform()メソッドを提供する必要があります。このメソッドには2つのパラメーターがあります。

  • value
  • metadata

valueは現在(ルートハンドラーメソッドによって受信される前)処理されている引数であり、metadataはそのメタデータです。メタデータオブジェクトには次のプロパティがあります。

exportinterfaceArgumentMetadata{type:'body'|'query'|'param'|'custom';metatype?:Type<any>;data?:string;}

これらのプロパティは現在処理されている引数を記述します。

type引数がbody@Body()、query@Query()、param @Param()、カスタムパラメーター(詳細はこちら)のどれかを示します。
metatype引数のメタタイプ(Stringなど)を提供します。※ ルートハンドラーメソッドシグネチャで型宣言を省略するか、バニラJavaScriptを使用すると値はundefinedになります。
data@Body('string')などデコレータに渡される文字列。デコレータの括弧を空のままにするとundefinedになります。

TypeScriptインターフェースはトランスパイル時に消えます。したがって、メソッドパラメーターの型がクラスではなくインターフェースとして宣言されている場合、metatypeの値はObjectになります。

バリデーションのユースケース

CatsControllercreate()メソッドを詳しく見てみましょう。

@Post()asynccreate(@Body()createCatDto:CreateCatDto){this.catsService.create(createCatDto);}

createCatDtobodyパラメーターに注目しましょう。型はCreateCatDtoです。

create-cat.dto.ts
exportclassCreateCatDto{readonlyname:string;readonlyage:number;readonlybreed:string;}

createメソッドへのリクエストには有効なボディが含まれるようにします。そのため、createCatDtoオブジェクトの3つのメンバーをバリデートする必要があります。ルートハンドラーメソッド内でこれを実行することもできますが、単一責任原則(SRP)を破ります。別のアプローチとして、バリデータクラスを作成しそこにタスクを委任することもできますが、各メソッドの最初にこのバリデータを使用する必要があります。それではバリデートミドルウェアを作るのはどうでしょうか?これは良いアイデアかもしれませんが、アプリケーション全体で使用できる汎用ミドルウェアを作成することはできません(ミドルウェアは呼び出されるハンドラーとそのパラメーターを含む実行コンテキストを認識しないため)。

パイプは最適なケースです。それでは1つを作成しましょう。

オブジェクトスキーマバリデーション

オブジェクトのバリデーションにはいくつかのアプローチがあります。一般的なアプローチの1つは、スキーマベースのバリデーションを使用することです。Joiライブラリを使用すると、読み取り可能なAPIを使用して非常に簡単な方法でスキーマを作成できます。Joiベースのスキーマを利用するパイプを見てみましょう。

最初に必要なパッケージをインストールします。

$ npm install --save @hapi/joi
$ npm install --save-dev @types/hapi__joi

以下のサンプルコードでは、​​constructor引数としてスキーマを使用する単純なクラスを作成します。次にschema.validate()メソッドを適用します。このメソッドは指定されたスキーマに対して引数を検証します。

前述のように、バリデーションパイプは値を変更せずに返すか例外をスローします。

次のセクションでは、@UsePipes()デコレータを使用して特定のコントローラメソッドに適切なスキーマを提供する方法を説明します。

import{PipeTransform,Injectable,ArgumentMetadata,BadRequestException}from'@nestjs/common';@Injectable()exportclassJoiValidationPipeimplementsPipeTransform{constructor(privatereadonlyschema:Object){}transform(value:any,metadata:ArgumentMetadata){const{error}=this.schema.validate(value);if(error){thrownewBadRequestException('Validation failed');}returnvalue;}}

パイプのバインディング

パイプのバインディング(適切なコントローラまたはハンドラーへのパイプ処理)は非常に簡単です。@UsePipes()デコレータを使用してパイプインスタンスを作成し、Joiバリデーションスキーマを渡します。

@Post()@UsePipes(newJoiValidationPipe(createCatSchema))asynccreate(@Body()createCatDto:CreateCatDto){this.catsService.create(createCatDto);}

Class validator

このセクションの手法にはTypeScriptが必要であり、アプリがバニラJavaScriptを使用して記述されている場合は利用できません。

バリデーション方法の代替実装を見てみましょう。

Nestはclass-validatorライブラリと連携して動作します。このすばらしいライブラリを使うと、デコレータベースのバリデーションを行うことができます。処理されたプロパティのメタタイプにアクセスできるため、デコレータベースのバリデーションは特にNestのパイプ機能と組み合わせると非常に強力です。使用前に必要なパッケージをインストールする必要があります。

$ npm i --save class-validator class-transformer

インストールできたらCreateCatDtoクラスにいくつかのデコレータを追加します。

create-cat.dto.ts
import{IsString,IsInt}from'class-validator';exportclassCreateCatDto{@IsString()readonlyname:string;@IsInt()readonlyage:number;@IsString()readonlybreed:string;}

class-validatorデコレータの詳細については、こちらをご覧ください。

これでValidationPipeクラスを作成できます。

validation.pipe.ts
import{PipeTransform,Injectable,ArgumentMetadata,BadRequestException}from'@nestjs/common';import{validate}from'class-validator';import{plainToClass}from'class-transformer';@Injectable()exportclassValidationPipeimplementsPipeTransform<any>{asynctransform(value:any,{metatype}:ArgumentMetadata){if(!metatype||!this.toValidate(metatype)){returnvalue;}constobject=plainToClass(metatype,value);consterrors=awaitvalidate(object);if(errors.length>0){thrownewBadRequestException('Validation failed');}returnvalue;}privatetoValidate(metatype:Function):boolean{consttypes:Function[]=[String,Boolean,Number,Array,Object];return!types.includes(metatype);}}

上記ではclass-transformerライブラリを使用しました。class-validatorライブラリと同じ作成者が作成しており非常にうまく機能します。

このコードを見ていきましょう。まず、transform()関数がasyncであることに注意してください。これは、Nestは同期パイプと非同期パイプの両方をサポートしており、またclass-validatorのバリデートの一部が非同期(Promiseを利用)にできるためです。

次に、構造化することでmetatypeパラメーターとしてメタタイプフィールドを取り出します(ArgumentMetadataからこのメンバーだけを抽出します)。これは完全なArgumentMetadataを取得し、メタタイプ変数を割り当てるための追加ステートメントを使用するための略記です。

次に、ヘルパー関数toValidate()を見てください。処理中の現在の引数がネイティブJavaScript型である場合、バリデーションステップをパスする責任があります(これらはスキーマをアタッチできないためバリデーションステップを実行する理由はありません)。

次に、class-transformer関数plainToClass()を使用して、プレーンJavaScript引数オブジェクトを型付きオブジェクトに変換しバリデーションを適用できるようにします。ネットワークリクエストからデシリアライズされる際、ボディは型情報を持ちません。class-validatorは、以前にDTOに対して定義したバリデーションデコレータを使用する必要があるため、この変換を実行する必要があります。

そして前述のとおり、これはバリデーションパイプであるため値を変更せずに返すか例外をスローします。

最後のステップとしてValidationPipeをバインドします。例外フィルターと同様に、パイプはメソッドスコープ、コントローラスコープ、またはグローバルスコープにすることができます。さらに、パイプはパラメータースコープにすることもできます。以下の例では、パイプインスタンスをルートパラメーター@Body()デコレータに直接結び付けます。

cats.controller.ts
@Post()asynccreate(@Body(newValidationPipe())createCatDto:CreateCatDto,){this.catsService.create(createCatDto);}

パラメータスコープのパイプは、バリデーションロジックが1つの特定のパラメーターのみに関係する場合に役立ちます。

または、メソッドレベルでパイプを設定するには@UsePipes()デコレータを使用します。

cats.controller.ts
@Post()@UsePipes(newValidationPipe())asynccreate(@Body()createCatDto:CreateCatDto){this.catsService.create(createCatDto);}

@UsePipes()デコレータは@nestjs/commonパッケージからインポートされます。

上記の例では、ValidationPipeのインスタンスがすぐに作成されています。または、(インスタンスではなく)クラスを渡すことでインスタンス化をフレームワークに任せ、依存性注入を有効にします。

cats.controller.ts
@Post()@UsePipes(ValidationPipe)asynccreate(@Body()createCatDto:CreateCatDto){this.catsService.create(createCatDto);}

ValidationPipeは非常に汎用的に作成されているため、アプリケーション全体のすべてのルートハンドラーに適用されるグローバルスコープのパイプとして設定してみましょう。

main.ts
asyncfunctionbootstrap(){constapp=awaitNestFactory.create(AppModule);app.useGlobalPipes(newValidationPipe());awaitapp.listen(3000);}bootstrap();

ハイブリッドアプリの場合、useGlobalPipes()メソッドはゲートウェイおよびマイクロサービス用のパイプを設定しません。 「スタンダード(非ハイブリッド)」マイクロサービスアプリの場合、useGlobalPipes()はパイプをグローバルにマウントします。

グローバルパイプは、すべてのコントローラとルートハンドラーに対してアプリケーション全体で使用されます。依存性注入に関しては、モジュールの外部で登録されたグローバルパイプ(上記の例のようなuseGlobalPipes())は、モジュールのコンテキスト外で行われるため依存性を注入できません。次のようにすることで、任意のモジュールからグローバルパイプを直接設定しこの問題を解決することができます。

app.module.ts
import{Module}from'@nestjs/common';import{APP_PIPE}from'@nestjs/core';@Module({providers:[{provide:APP_PIPE,useClass:ValidationPipe,},],})exportclassAppModule{}

このアプローチでパイプの依存性注入を実行する場合、この構造が使用されるモジュールに関係なく、パイプは実際にはグローバルであることに注意してください。これはどこで行われるのでしょうか?パイプ(上記の例ではValidationPipe)が定義されているモジュールを選択します。また、カスタムプロバイダの登録を処理する方法はuseClassだけではありません。詳細はこちらをご覧ください。

変換のユースケース

パイプのユースケースはバリデーションだけではありません。この章の冒頭で、パイプを使用して入力データを目的の出力に変換できることを説明しました。これはtransform関数から返された値が引数の以前の値を完全にオーバーライドするため実現することができます。どのような場面で役に立つでしょうか?クライアントから渡されたデータはルートハンドラーメソッドによって適切に処理される前に、文字列を整数に変換するなど何らかの変更が必要になることがあります。さらに、一部の必須データフィールドが欠落している可能性があるため、デフォルト値を適用したい場合もあるでしょう。変換パイプはクライアントリクエストとリクエストハンドラの間に処理機能を挿入することにより、これらの機能を実行できます。

これは文字列を整数値に解析するためのParseIntPipeです。

parse-int.pipe.ts
import{PipeTransform,Injectable,ArgumentMetadata,BadRequestException}from'@nestjs/common';@Injectable()exportclassParseIntPipeimplementsPipeTransform<string,number>{transform(value:string,metadata:ArgumentMetadata):number{constval=parseInt(value,10);if(isNaN(val)){thrownewBadRequestException('Validation failed');}returnval;}}

以下のように、このパイプを選択したパラメーターに簡単に結びつけることができます。

@Get(':id')asyncfindOne(@Param('id',newParseIntPipe())id){returnawaitthis.catsService.findOne(id);}

必要に応じて、文字列のパースを担当するParseUUIDPipeを使用し、UUIDかどうかを検証することができます。

@Get(':id')asyncfindOne(@Param('id',newParseUUIDPipe())id){returnawaitthis.catsService.findOne(id);}

ParseUUIDPipe()を使用する場合、バージョン3,4,5でUUIDをパースしています。特定のバージョンのUUIDのみが必要な場合は、パイプオプションでバージョンを渡すことができます。

これを設定すると、リクエストが対応するハンドラーに到達する前にParseIntPipeまたはParseUUIDPipeが実行され、idパラメーターの整数またはUUIDを常に受け​​取るようになります。

別の便利なケースは、IDによってデータベースから既存のユーザーエンティティを選択するケースです。

@Get(':id')findOne(@Param('id',UserByIdPipe)userEntity:UserEntity){returnuserEntity;}

このパイプの実装は読者に任せます。他のすべての変換パイプと同様に、入力値(id)を受け取り、出力値(UserEntityオブジェクト)を返すことに注意してください。ハンドラーから共通パイプへボイラープレートコードを抽象化することにより、コードをより宣言的でDRYにすることができます。

組み込みのValidationPipe

幸いなことに、ValidationPipeParseIntPipeはNestで提供されているため、これらのパイプを自分で作成する必要はありません。 (ValidationPipeではclass-validatorclass-transformerパッケージの両方をインストールする必要があることに注意してください)。

組み込みのValidationPipeは、この章で作成したサンプルよりも多くのオプションを提供しています。サンプルではパイプの基本的な仕組みを説明しました。ここでさらに多くの例を見ることができます。

そのようなオプションの1つがtransformです。シリアル化されていないbodyオブジェクトがバニラJavaScriptオブジェクトである(DTOタイプを持たない)という以前の議論を思い出してください。これまで、パイプを使用してペイロードを検証してきました。バリデーションができるように、その過程でclass-transformを使用してプレーンオブジェクトを型付きオブジェクトに一時的に変換したことを思い出してください。組み込みのValidationPipeはオプションでこの変換されたオブジェクトを返すこともできます。構成オブジェクトをパイプに渡すことにより、この動作を有効にします。このオプションの場合、以下に示すように、値がtrueのフィールドtransformを持つ構成オブジェクトを渡します。

cats.controller.ts
@Post()@UsePipes(newValidationPipe({transform:true}))asynccreate(@Body()createCatDto:CreateCatDto){this.catsService.create(createCatDto);}

ValidationPipe@nestjs/commonパッケージからインポートされます。

このパイプはclass-validatorライブラリとclass-transformerライブラリに基づいているため、多くの追加オプションが利用可能です。上記のtransformオプションと同様に、パイプに渡された構成オブジェクトを介してこれらの設定を構成します。組み込みオプションは次のとおりです。

exportinterfaceValidationPipeOptionsextendsValidatorOptions{transform?:boolean;disableErrorMessages?:boolean;exceptionFactory?:(errors:ValidationError[])=>any;}

これらに加えて、すべてのclass-validatorオプション(ValidatorOptionsインターフェイスから継承)が利用可能です:

オプション説明
skipMissingPropertiesbooleantrueに設定すると、バリデータはバリデーションオブジェクトにないすべてのプロパティのバリデーションをスキップします。
whitelistbooleantrueに設定すると、バリデータはバリデーションデコレータを使用しないプロパティの検証済み(返された)オブジェクトを取り除きます。
forbidNonWhitelistedbooleantrueに設定すると、ホワイトリストに登録されていないプロパティバリデータを削除する代わりに例外がスローされます。
forbidUnknownValuesbooleantrueに設定すると、不明なオブジェクトをバリデートしようとするとすぐに失敗します。
disableErrorMessagesbooleantrueに設定すると、バリデーションエラーはクライアントに返されません。
exceptionFactoryFunctionバリデーションエラーの配列を受け取り、スローされる例外オブジェクトを返します。
groupsstring[]オブジェクトのバリデーション中に使用されるグループです。
dismissDefaultMessagesbooleantrueに設定されている場合、バリデーションではデフォルトのメッセージは使用されません。明示的に設定されていない場合エラーメッセージは常にundefinedになります。
validationError.targetbooleanValidationErrorで対象を公開するかどうかを示します。
validationError.valuebooleanValidationErrorでバリデーション済みの値を公開するかどうかを示します。

class-validatorパッケージの詳細についてはリポジトリをご覧ください。

Node.jsでHTTPメソッドを使う。

$
0
0

免責事項

この記事は初心者視点でザックリとした説明をしています。正確性に欠ける可能性がございますが、ご了承ください。「明らかに違うよ」ということがありましたら、ご指摘くださると幸いです。

環境

OS:最新版ではないMacOS
VirtualBox:5.2.26
Vagrant:2.2.6
Ubuntu:ubuntu/bionic64 v20181129.0.0

目次

  1. HTTPメソッドとは
  2. HTTPメソッドを使ってみる

1. HTTPメソッドとは

HTTPメソッド(HTTPリクエストメソッド)とは、WEBブラウザが、HTTPというルールの元に、WEBサーバーに様々な種類のお願いを出すことです。
普段、WEBページにアクセスする場合はGETメソッドが使われますが、
HTTPメソッド1.1は8種類あり、以下のようなお願いを出すことができます。

リクエストメソッド意味CRUD例(Twitterの例)
GETリソースの取得READツイートをみる
POSTリソースの追加CREATE ツイートをする
PUTリソースの更新/追加UPDATE/CREATEリツイートをする
DELETEリソースの削除DELETEツイートを消す
HEADヘッダ情報の取得××
CONNECTプロキシサーバに通信を開始してもらう××
OPTIONSサーバのオプションの確認××
TRACEリクエストがどんな経路で届いたか確認××

WEBアプリケーションを作る上では、CRUDだけを意識すれば良いらしいです。

2. HTTPメソッドを使ってみる

今回はHTTPメソッドのGET、POST、DELETEをNode.jsで作ったサーバーに対してリクエストします。
index.jsファイルを作り、以下のように記述してください。

index.js
'use strict';consthttp=require('http');constserver=http.createServer((req,res)=>{res.writeHead(200,{'Content-Type':'text/plain; charset=utf-8'});

これはサーバーを作っています。
サーバーに対してクライアントからリクエストがあったときに、リクエストを表すオブジェクトの引数 req と レスポンスを表すオブジェクトの引数 res を受け取る無名関数が呼び出されます。

関数が呼び出されたら、res.writeHead(200, ....)が実行されます。
これは200というリクエストの成功を示すステータスコードと共に、レスポンスヘッダを書き込んでいます。

レスポンスヘッダには、内容の形式(Content-Type)が、通常のテキスト(text/plain)であるという情報と、文字セット(charset)がutf-8であるという情報を書き出しています。

余談ですが、HTTPリクエストをしたら、HTTPレスポンスが返ってきます。
HTTPレスポンスは、
1. HTTPステータスライン
2. HTTPレスポンスヘッダ
3. HTTPレスポンスボディ
の3つに分かれており、それぞれには別の情報が書かれています。
今回は、その中のHTTPレスポンスヘッダに情報を書き込んでいます。

続いて、index.jsファイルに以下を追記してください。

index.js
switch(req.method){case'GET':res.write('GET '+req.url);break;case'POST':res.write('POST '+req.url);letbody='';req.on('data',(chunk)=>{body=chunk;}).on('end',()=>{console.info('['+now+'] Data posted: '+body);});break;case'DELETE':res.write('DELETE '+req.url);break;default:break;}res.end();})

req.methodにはHTTPリクエストメソッドが格納されているので、Switch文を使い、処理を分岐させています。
GETメソッドならば、そのURLをコンテンツとしてレスポンスに返して終わりです。
POSTメソッドの際は、そのURLをコンテンツとしてレスポンスに返し、さらに追加して送られてくるデータがあります。
送られたデータはchunkに格納され、それを変数bodyに入れています。
DELETEメソッドは、そのURLをコンテンツとしてレスポンスに返して終わりです。res.end()は、resのendメソッドを使いレスポンスの書き出しを終了してます。

続いて以下のようにindex.jsに追記します。

index.js
server.on('error',(e)=>{console.error('['+newDate()+'] Server Error',e);server.on('clientError',(e)=>{console.error('['+newDate()+'] Client Error',e);});constport=8000;server.listen(port,()=>{console.info('['+newDate()+'] Listening on '+port);});

次にイベントハンドラの設定です。
サーバー側でエラーイベントが発生したら、errorが呼び出されコンソールに表示されます。
クライアント側でエラーイベントが発生したら、clientErrorが呼び出されコンソールに表示されます。

constport=8000;server.listen(port,()=>{console.info('['+newDate()+'] Listening on '+port);});

はサーバーが8000番ポートでリクエストを待つという処理です。

全体でこのようなファイル構成になります。

index.js
'use strict';consthttp=require('http');constserver=http.createServer((req,res)=>{constnow=newDate();console.info('['+now+'] Requested by '+req.connection.remoteAddress);res.writeHead(200,{'Content-Type':'text/plain; charset=utf-8'});switch(req.method){case'GET':res.write('GET '+req.url);break;case'POST':res.write('POST '+req.url);letbody='';req.on('data',(chunk)=>{body=chunk;}).on('end',()=>{console.info('['+now+'] Data posted: '+body);});break;case'DELETE':res.write('DELETE '+req.url);break;default:break;}res.end();})server.on('error',(e)=>{console.error('['+newDate()+'] Server Error',e);})server.on('clientError',(e)=>{console.error('['+newDate()+'] Client Error',e);});constport=8000;server.listen(port,()=>{console.info('['+newDate()+'] Listening on '+port);});

確認を行う。

それでは、GET、POST、DELETEのHTTPリクエストメソッドが適切に送られるかの確認を行います。
コンソールを2つ立ち上げ、片方をサーバー側、もう片方をクライアント側とします。

サーバー側のコンソールで以下のようにサーバーを立ち上げます。

サーバー
$ node index.js
> [Wed Nov 06 2019 15:59:55 GMT+0900 (GMT+09:00)] Listening on 8000
GETメソッド
クライアント
$ curl -X GET http://localhost:8000
> GET /
POSTメソッド

POSTメソッドは-dオプションをつけて、情報を送ることができます。

クライアント
$ curl -X POST -d 'こんにちは' http://localhost:8000
> POST /

以下のように送った内容がサーバー側に表示されます。

サーバー
[Wed Nov 06 2019 16:44:30 GMT+0900 (GMT+09:00)] Data posted: こんにちは
DELETEメソッド
クライアント
$ curl -X DELETE http://localhost:8000
> DELETE /

> の後で 'リクエストの種類 / ' と表示されたらうまくリクエストが送られた証拠になります。

参考

「N予備校 プログラミングコース」
https://www.nnn.ed.nico/
「逆引きcurlコマンドのオプション一覧」
https://www.setouchino.cloud/blogs/99#post-x-post-d

Viewing all 8952 articles
Browse latest View live