REST APIのバックエンドを構築する場合、多くの場合、Express.jsがNode.jsフレームワークの最初の選択肢です。静的HTMLとテンプレートの作成もサポートしていますが、このシリーズでは、TypeScriptを使用したバックエンド開発に焦点を当てます。結果のRESTAPIは、フロントエンドフレームワークまたは外部バックエンドサービスがクエリできるものになります。
あなたは必要になるでしょう:
ターミナル(またはコマンドプロンプト)で、プロジェクト用のフォルダーを作成します。そのフォルダーから、npm init
を実行します。 それが作成されます 必要な基本的なNode.jsプロジェクトファイルのいくつか。
次に、Express.jsフレームワークといくつかの便利なライブラリを追加します。
npm install --save express debug winston express-winston cors
これらのライブラリには十分な理由があります Node.js開発者 お気に入り:
debug
console.log()
の呼び出しを回避するために使用するモジュールですアプリケーションの開発中。このようにして、トラブルシューティング中にデバッグステートメントを簡単にフィルタリングできます。手動で削除する代わりに、本番環境で完全にオフにすることもできます。winston
APIへのリクエストと、返されたレスポンス(およびエラー)をログに記録する責任があります。 express-winston
Express.jsと直接統合されるため、すべての標準API関連winston
ロギングコードはすでに実行されています。cors
Express.jsミドルウェアの一部であり、これを有効にすることができます クロスオリジンリソースシェアリング 。これがないと、APIは、バックエンドとまったく同じサブドメインから提供されるフロントエンドからのみ使用できます。バックエンドは、実行時にこれらのパッケージを使用します。しかし、いくつかをインストールする必要もあります 開発 TypeScript構成の依存関係。そのために、以下を実行します。
npm install --save-dev @types/cors @types/express @types/debug source-map-support tslint typescript
これらの依存関係は、Express.jsやその他の依存関係で使用されるタイプとともに、アプリ自体のコードでTypeScriptを有効にするために必要です。これにより、コーディング中に一部の関数メソッドを自動的に完了することができるため、WebStormやVSCodeなどのIDEを使用しているときに多くの時間を節約できます。
ブルームバーグターミナルとは
package.json
の最終的な依存関係次のようになります。
'dependencies': { 'debug': '^4.2.0', 'express': '^4.17.1', 'express-winston': '^4.0.5', 'winston': '^3.3.3', 'cors': '^2.8.5' }, 'devDependencies': { '@types/cors': '^2.8.7', '@types/debug': '^4.1.5', '@types/express': '^4.17.2', 'source-map-support': '^0.5.16', 'tslint': '^6.0.0', 'typescript': '^3.7.5' }
必要な依存関係がすべてインストールされたので、独自のコードの作成を始めましょう。
このチュートリアルでは、次の3つのファイルのみを作成します。
./app.ts
./common/common.routes.config.ts
./users/users.routes.config.ts
プロジェクト構造の2つのフォルダー(common
とusers
)の背後にある考え方は、独自の責任を持つ個々のモジュールを持つことです。この意味で、最終的には、モジュールごとに次の一部またはすべてを使用することになります。
このフォルダ構造は、このチュートリアルシリーズの残りの部分の初期の開始点であり、練習を開始するのに十分です。
common
でフォルダ、common.routes.config.ts
を作成しましょう次のようなファイル:
import express from 'express'; export class CommonRoutesConfig { app: express.Application; name: string; constructor(app: express.Application, name: string) { this.app = app; this.name = name; } getName() { return this.name; } }
ここでルートを作成する方法はオプションです。しかし、TypeScriptを使用しているため、ルートシナリオは、extends
で継承を使用する練習をする機会です。キーワード。これについては後ほど説明します。このプロジェクトでは、すべてのルートファイルの動作は同じです。名前(デバッグ目的で使用します)とメインのExpress.jsへのアクセスがありますApplication
オブジェクト。
これで、ユーザールートファイルの作成を開始できます。 users
でフォルダ、作成しましょうusers.routes.config.ts
次のようにコーディングを開始します。
ノードjsが使用される理由
import {CommonRoutesConfig} from '../common/common.routes.config'; import express from 'express'; export class UsersRoutes extends CommonRoutesConfig { constructor(app: express.Application) { super(app, 'UsersRoutes'); } }
ここでは、CommonRoutesConfig
をインポートしていますクラスとそれをUsersRoutes
と呼ばれる新しいクラスに拡張します。コンストラクターを使用して、アプリ(メインのexpress.Application
オブジェクト)と名前UsersRoutesをCommonRoutesConfig
のコンストラクターに送信します。
この例は非常に単純ですが、スケーリングして複数のルートファイルを作成する場合、これは重複コードを回避するのに役立ちます。
このファイルに、ロギングなどの新しい機能を追加するとします。必要なフィールドをCommonRoutesConfig
に追加できますクラス、そしてCommonRoutesConfig
を拡張するすべてのルートそれにアクセスできるようになります。
いくつかの機能が必要な場合はどうなりますか 同様 これらのクラス間(APIエンドポイントの構成など)ですが、クラスごとに異なる実装が必要ですか? 1つのオプションは、と呼ばれるTypeScript機能を使用することです。 抽象化 。
UsersRoutes
という非常に単純な抽象関数を作成しましょう。クラス(および将来のルーティングクラス)はCommonRoutesConfig
から継承します。すべてのルートにconfigureRoutes()
という名前の関数(共通のコンストラクターから呼び出すことができるようにする)を強制したいとします。ここで、各ルーティングクラスのリソースのエンドポイントを宣言します。
これを行うために、common.routes.config.ts
に3つの簡単なものを追加します。
abstract
class
へline、このクラスの抽象化を有効にします。abstract configureRoutes(): express.Application;
。これにより、CommonRoutesConfig
を拡張するクラスが強制されます。その署名に一致する実装を提供します。一致しない場合、TypeScriptコンパイラはエラーをスローします。this.configureRoutes();
への呼び出しコンストラクターの最後に、この関数が存在することを確認できるためです。結果:
import express from 'express'; export abstract class CommonRoutesConfig { app: express.Application; name: string; constructor(app: express.Application, name: string) { this.app = app; this.name = name; this.configureRoutes(); } getName() { return this.name; } abstract configureRoutes(): express.Application; }
これで、CommonRoutesConfig
を拡張するクラスconfigureRoutes()
という関数が必要ですexpress.Application
を返しますオブジェクト。つまり、users.routes.config.ts
更新が必要:
import {CommonRoutesConfig} from '../common/common.routes.config'; import express from 'express'; export class UsersRoutes extends CommonRoutesConfig { constructor(app: express.Application) { super(app, 'UsersRoutes'); } configureRoutes() { // (we'll add the actual route configuration here next) return this.app; } }
私たちが作ったものの要約として:
最初にcommon.routes.config
をインポートしますファイル、次にexpress
モジュール。次に、UserRoutes
を定義しますクラス、CommonRoutesConfig
を拡張したいと言っています基本クラス。これは、configureRoutes()
を実装することを約束することを意味します。
CommonRoutesConfig
に情報を送信するにはクラスでは、constructor
を使用していますクラスの。 express.Application
を受け取ることを期待していますオブジェクト。次のステップでさらに詳しく説明します。 super()
を使用して、CommonRoutesConfig
のコンストラクターに、アプリケーションとルートの名前(このシナリオではUsersRoutes)を渡します。 (super()
は、configureRoutes()
の実装を呼び出します。)
configureRoutes()
関数は、RESTAPIのユーザー用のエンドポイントを作成する場所です。そこで、使用します 応用 そしてその ルート Express.jsの機能。
app.route()
を使用する際のアイデア機能は、コードの重複を回避することです。これは、明確に定義されたリソースを使用してRESTAPIを作成しているため簡単です。このチュートリアルの主なリソースは ユーザー 。このシナリオには2つのケースがあります。
users
が必要です。要求されたパスの最後。 (この記事では、クエリのフィルタリング、ページ付け、またはその他のそのようなクエリについては説明しません。)users/:userId
に従います。方法.route()
Express.jsで動作するため、エレガントなチェーンでHTTP動詞を処理できます。これは、.get()
、.post()
などがすべてIRoute
の同じインスタンスを返すためです。その最初の.route()
呼び出しは行います。最終的な構成は次のようになります。
configureRoutes() { this.app.route(`/users`) .get((req: express.Request, res: express.Response) => { res.status(200).send(`List of users`); }) .post((req: express.Request, res: express.Response) => { res.status(200).send(`Post to users`); }); this.app.route(`/users/:userId`) .all((req: express.Request, res: express.Response, next: express.NextFunction) => { // this middleware function runs before any request to /users/:userId // but it doesn't accomplish anything just yet--- // it simply passes control to the next applicable function below using next() next(); }) .get((req: express.Request, res: express.Response) => { res.status(200).send(`GET requested for id ${req.params.userId}`); }) .put((req: express.Request, res: express.Response) => { res.status(200).send(`PUT requested for id ${req.params.userId}`); }) .patch((req: express.Request, res: express.Response) => { res.status(200).send(`PATCH requested for id ${req.params.userId}`); }) .delete((req: express.Request, res: express.Response) => { res.status(200).send(`DELETE requested for id ${req.params.userId}`); }); return this.app; }
上記のコードにより、RESTAPIクライアントはusers
を呼び出すことができます。 POST
のエンドポイントまたはGET
リクエスト。同様に、クライアントは私たちの/users/:userId
を呼び出すことができますGET
、PUT
、PATCH
、またはDELETE
のエンドポイントリクエスト。
良いランディングページをデザインする方法
ただし、/users/:userId
については、all()
を使用した汎用ミドルウェアも追加しました。 get()
、put()
、patch()
、またはdelete()
のいずれかの前に実行される関数機能。この機能は、(シリーズの後半で)認証されたユーザーのみがアクセスすることを目的としたルートを作成する場合に役立ちます。
.all()
でお気づきかもしれません関数は、他のミドルウェアと同様に、Request
、Response
、およびNextFunction
の3種類のフィールドがあります。
NextFunction
コールバック関数として機能し、制御が他のミドルウェア関数を通過できるようにします。その過程で、コントローラーが最終的にリクエスターに応答を送り返す前に、すべてのミドルウェアが同じ要求オブジェクトと応答オブジェクトを共有します。app.ts
いくつかの基本的なルートスケルトンを構成したので、アプリケーションのエントリポイントの構成を開始します。 app.ts
を作成しましょうプロジェクトフォルダのルートにあるファイルを次のコードで開始します。
import express from 'express'; import * as http from 'http'; import * as bodyparser from 'body-parser'; import * as winston from 'winston'; import * as expressWinston from 'express-winston'; import cors from 'cors'; import {CommonRoutesConfig} from './common/common.routes.config'; import {UsersRoutes} from './users/users.routes.config'; import debug from 'debug';
記事のこの時点では、これらのインポートのうち2つだけが新しいものです。
http
Node.jsネイティブモジュールです。 Express.jsアプリケーションを起動する必要があります。body-parser
Express.jsに付属するミドルウェアです。制御が独自のリクエストハンドラに移る前に、リクエストを(この場合はJSONとして)解析します。ファイルをインポートしたので、使用する変数の宣言を開始します。
const app: express.Application = express(); const server: http.Server = http.createServer(app); const port: Number = 3000; const routes: Array = []; const debugLog: debug.IDebugger = debug('app');
express()
関数は、コード全体に渡すメインのExpress.jsアプリケーションオブジェクトを返します。これは、http.Server
への追加から始まります。オブジェクト。 (http.Server
を構成した後、express.Application
を開始する必要があります。)
通常、アプリのフロントエンドに使用されるため、標準のポート80(HTTP)または443(HTTPS)ではなくポート3000でリッスンします。
ポートを3000にするという規則はありません。指定されていない場合は、 任意のポート が割り当てられますが、Node.jsとExpress.jsの両方のドキュメントの例では3000が使用されているため、ここでは伝統を継続します。
バックエンドが標準ポートでの要求に応答するようにしたい場合でも、カスタムポートでローカルに実行できます。これには、 リバースプロキシ 特定のドメインまたはサブドメインを持つポート80または443でリクエストを受信します。次に、それらを内部ポート3000にリダイレクトします。
routes
配列は、以下に示すように、デバッグの目的でルートファイルを追跡します。
最後に、debugLog
最終的にはconsole.log
と同様の関数になりますが、より優れています。ファイル/モジュールコンテキストと呼びたいものに自動的にスコープされるため、微調整が簡単です。 (この場合、文字列でdebug()
コンストラクターに渡したときに「アプリ」と呼びました。)
リフトvsユーバービジネスモデル
これで、すべてのExpress.jsミドルウェアモジュールとAPIのルートを構成する準備が整いました。
// here we are adding middleware to parse all incoming requests as JSON app.use(bodyparser.json()); // here we are adding middleware to allow cross-origin requests app.use(cors()); // here we are configuring the expressWinston logging middleware, // which will automatically log all HTTP requests handled by Express.js app.use(expressWinston.logger({ transports: [ new winston.transports.Console() ], format: winston.format.combine( winston.format.colorize(), winston.format.json() ) })); // here we are adding the UserRoutes to our array, // after sending the Express.js application object to have the routes added to our app! routes.push(new UsersRoutes(app)); // here we are configuring the expressWinston error-logging middleware, // which doesn't *handle* errors per se, but does *log* them app.use(expressWinston.errorLogger({ transports: [ new winston.transports.Console() ], format: winston.format.combine( winston.format.colorize(), winston.format.json() ) })); // this is a simple route to make sure everything is working properly app.get('/', (req: express.Request, res: express.Response) => { res.status(200).send(`Server up and running!`) });
expressWinston.errorLogger
に気づいたかもしれません設定されています 後 ルートを定義します。これは間違いではありません!として Express-Winstonのドキュメント 状態:
ロガーは、エクスプレスルーター(
app.router
)の後、およびカスタムエラーハンドラー(express.handler
)の前に追加する必要があります。
最後にそして最も重要なこと:
server.listen(port, () => { debugLog(`Server running at http://localhost:${port}`); routes.forEach((route: CommonRoutesConfig) => { debugLog(`Routes configured for ${route.getName()}`); }); });
これにより、実際にサーバーが起動します。開始されると、Node.jsはコールバック関数を実行します。この関数は、実行中であることを報告し、その後に構成したすべてのルートの名前が続きます。これまでのところ、UsersRoutes
だけです。
package.json
TypeScriptをJavaScriptにトランスパイルしてアプリを実行するスケルトンを実行する準備ができたので、最初にTypeScriptトランスパイルを有効にするための定型的な構成が必要です。ファイルを追加しましょうtsconfig.json
プロジェクトルート内:
{ 'compilerOptions': { 'target': 'es2016', 'module': 'commonjs', 'outDir': './dist', 'strict': true, 'esModuleInterop': true, 'inlineSourceMap': true } }
次に、最後の仕上げをpackage.json
に追加する必要があります。次のスクリプトの形式で:
'scripts': { 'start': 'tsc && node ./dist/app.js', 'debug': 'export DEBUG=* && npm run start', 'test': 'echo 'Error: no test specified' && exit 1' },
test
スクリプトは、シリーズの後半で置き換えるプレースホルダーです。
ザ・ tsc start
でスクリプトはTypeScriptに属しています。 TypeScriptコードをJavaScriptにトランスパイルし、それをdist
に出力します。フォルダ。次に、ビルドバージョンをnode ./dist/app.js
で実行します。
debug
スクリプトはstart
を呼び出しますスクリプトですが、最初にDEBUG
を定義します環境変数。これには、すべてのdebugLog()
を有効にする効果があります。ステートメント(および、Express.js自体からの同様のステートメント。これは、同じdebug
モジュールを使用します)を使用して、有用な詳細を端末に出力します。 npm start
。
実行してみてくださいnpm run debug
あなた自身、そしてその後、それをnpm start
と比較してくださいコンソール出力がどのように変化するかを確認します。
ヒント:デバッグ出力をapp.ts
に制限できますファイル独自のdebugLog()
DEBUG=app
を使用するステートメントDEBUG=*
の代わりに。 debug
モジュールは一般的に非常に柔軟性があり、この機能は 例外ではありません 。
Windowsユーザーはおそらくexport
を変更する必要があります〜SET
export
以降MacとLinuxでどのように機能するかです。プロジェクトが複数の開発環境をサポートする必要がある場合は、 cross-envパッケージ ここで簡単な解決策を提供します。
npm run debug
を使用またはnpm start
引き続き、REST APIはポート3000でリクエストを処理する準備が整います。この時点で、cURLを使用できます。 郵便配達員 、 不眠症 など、バックエンドをテストします。
ユーザーリソースのスケルトンのみを作成したため、本文なしでリクエストを送信するだけで、すべてが期待どおりに機能していることを確認できます。例えば:
curl --location --request GET 'localhost:3000/users/12345'
バックエンドは回答を返送する必要がありますGET requested for id 12345
。
POST
ingについて:
curl --location --request POST 'localhost:3000/users' --data-raw ''
これと、スケルトンを作成した他のすべてのタイプのリクエストは、非常によく似ています。
この記事では、プロジェクトを最初から構成し、Express.jsフレームワークの基本に飛び込むことで、RESTAPIの作成を開始しました。次に、UsersRoutesConfig
を使用してパターンを作成することにより、TypeScriptをマスターするための最初の一歩を踏み出しました。 CommonRoutesConfig
を拡張します。これは、このシリーズの次の記事で再利用するパターンです。 app.ts
を構成して終了しました新しいルートとpackage.json
を使用するためのエントリポイントアプリケーションをビルドして実行するためのスクリプトを使用します。
しかし、Express.jsとTypeScriptで作成されたRESTAPIの基本でさえかなり複雑です。に 次の部分 このシリーズでは、ユーザーリソース用の適切なコントローラーの作成に焦点を当て、サービス、ミドルウェア、コントローラー、およびモデルのいくつかの有用なパターンを掘り下げます。
プロジェクト全体が利用可能です GitHubで 、およびこの記事の終わりのコードはtoptal-article-01
にあります。ブランチ。
絶対に!人気のあるnpmパッケージ(Express.jsを含む)には、対応するTypeScriptタイプ定義ファイルがあるのが非常に一般的です。これは、Node.js自体に加えて、デバッグパッケージなどの含まれているサブコンポーネントにも当てはまります。
はい。 Node.jsを単独で使用して本番環境に対応したRESTAPIを作成できます。また、Express.jsのように、避けられない定型文を減らすための一般的なフレームワークもいくつかあります。
いいえ、最新のJavaScriptのバックグラウンドを持つ人のためにTypeScriptの学習を開始することは難しくありません。オブジェクト指向プログラミングの経験がある人にとってはさらに簡単です。ただし、他のスキルと同様に、TypeScriptのニュアンスとベストプラクティスをすべて習得するには時間がかかります。
go vs nodejsのパフォーマンス
プロジェクトによって異なりますが、Node.jsプログラミングには絶対にお勧めです。これは、バックエンドで実際の問題ドメインをモデル化するためのより表現力豊かな言語です。これにより、コードが読みやすくなり、バグの可能性が減少します。
TypeScriptは、JavaScriptが見つかる場所ならどこでも使用されますが、特に大規模なアプリケーションに適しています。 JavaScriptをベースとして使用し、静的型付けを追加し、オブジェクト指向プログラミング(OOP)パラダイムのサポートを大幅に向上させます。これにより、より高度な開発とデバッグのエクスペリエンスがサポートされます。