簡易APIサーバ構築については、ExpressJSを使用したサンプルコードをネット上で見つけることができます。
ExpressJSですと実装するときのコードは概ねJavaScriptベースになります。
私自身、TypeScriptのほうがなじみがあるので簡易APIサーバーを構築するフレームワークがないかと探していたところ、ExpressoTSというのがリリースされているこを見つけました。
今回は、ExpressoTSで簡易APIサーバを作ってみたのでその情報を紹介したいと思います。
ExpressoTS公式ページ
開発環境
- OS: Windows10
- node: 18.17.0
- ExpressoTS: ver2.6.0
- adapter-express: ver1.1.0
その他コマンドラインからビルドなどを実行する時はGitBashを使用します。標準で用意されるnpmコマンドラインにshellコマンドが含まれてるためです。
実装概要
- ExpressoTSをnpmコマンドを使用し、globalの環境にインストールします。これで以下のexpressotsコマンドが使用できるようになります。
- ExpressoTSのCLI expressotsコマンドを使用し、初期のプロジェクト(スケルトン)を自動生成します。
- 初期生成されたコードからGetの動作確認をします。初期プロジェクトにhttp Getメソッドは実装済みのため。
- つづいて、Postメソッドを実装します。ここでは、postリクエスト(Body部:Jsonデータ)の受取から、ビジネスロジック部分を呼び出し、シンプルなJSON形式のデータを返却するというものです。
ソースコード
今回検証したソースコードはこちらに置いていますので参考にしていただけると幸いです。
プロジェクトの準備
ExpressoTSインストール
$ npm i -g @expressots/cli
初期プロジェクト作成
$ expressots new simple-expressots-get-post
[🐎 Expressots]
? Project name (simple-expressots-get-post) ※ エンターを押します
? Package manager (Use arrow keys) ※ エンターを押します
> npm
yarn
pnpm
bun
? Select a template (Use arrow keys) ※ エンターを押します
> Non-Opinionated :: A simple ExpressoTS project.
Opinionated :: A complete ExpressoTS project with an opinionated structure and features.
? Do you want to create this project? (Y/n) ※ エンターを押します
※ プロジェクトの生成が開始されます。
Progress |████████████████████░░░░░░░░░░░░░░░░░░░░| 50% || Installing dependencies
🐎 Project simple-expressots-get-post created successfully!
🤙 Run the following commands to start the project:
$ cd simple-expressots-get-post
$ npm run dev
Happy coding!
Please consider donating to support the project.
💖 Sponsor: https://github.com/sponsors/expressots
※ ここまで表示されれば初期プロジェクトの生成の完了です。
生成されたディレクトリ構造
node_modules以外のディレクトリ内容は以下です。
$ tree -I node_modules
.
|-- README.md
|-- expressots.config.ts
|-- jest.config.ts
|-- package-lock.json
|-- package.json
|-- src
| |-- app.container.ts
| |-- app.controller.ts
| |-- app.module.ts
| |-- app.usecase.ts
| `-- main.ts
|-- test
| `-- app.usecase.spec.ts
|-- tsconfig.build.json
`-- tsconfig.json
生成された各ファイルの説明
これからGetとPostメソッドについて確認していきますが、その前に主要なファイルについて説明します。
今回のGetメソッドを確認する際に「app.controller.ts」と「app.usecase.ts」ファイルを主に参照します。
ファイル名 | 概要 |
---|---|
app.container.ts | アプリケーションの依存関係を記述 |
app.controller.ts | HTTPリクエストを処理するためのコードを記述 |
app.module.ts | アプリケーションの各部分を組み立て、結合を記述 |
app.usecase.ts | アプリケーションのビジネスロジックを記述 |
main.ts | アプリケーションのエントリーポイントとなるファイル |
サービスの起動
サービスを起動します。
$ cd simple-expressots-get-post
$ npm run dev
> simple-expressots-get-post@1.0.0 dev
> tsnd ./src/main.ts
[INFO] 12:35:21 ts-node-dev ver. 2.0.0 (using ts-node ver. 10.9.1, typescript ver. 4.9.5)
Application version not provided is running on port 3000 - Environment: development
※ ここまで表示されれば正常に起動しています。3000ポートを使用します。
続いて、ソースコードの内容をみていきます。
Getメソッド
Getメソッドは、上記初期プロジェクトが生成されるとGetメソッドはすでに実装がされています。
実装されているソースコードを参照します。
続いて、リクエストをcurlコマンドにより送信します。
すでに実装されているGetメソッドの応答結果「Hello from ExpressoTS!」が得られます。
$ curl localhost:3000
Hello from ExpressoTS!
※ メッセージが帰ってくれば正常に動作しています。
Getメソッドの実装(入り部口部分)の確認
app.controller.tsにGetメソッドが実装されています。
import { BaseController } from "@expressots/core";
import { Response } from "express";
import { AppUseCase } from "./app.usecase";
import { Get, controller, response } from "@expressots/adapter-express";
/*
* AppController クラスの定義
* ExpressotsのBaseControllerクラスを継承しています。
* これにより、Expressotsの機能を利用できるようになります。
*/
@controller("/")
export class AppController extends BaseController {
constructor(private appUseCase: AppUseCase) {
super();
}
/*
* @Get("/") デコレータは、GETリクエストを処理するためのメソッドを定義しています。
* このメソッドは / ルートに対するGETリクエストを処理します。
* この定義により「localhost:3000」でアクセスするとメッセージが返却されます。
*/
@Get("/")
execute(@response() res: Response) {
return res.send(this.appUseCase.execute());
}
}
Getメソッドの業務ロジックの確認
上記のthis.appUseCase.execute()
の実装内容を以下に示します。
上記curlコマンドによりリクエストを送付した結果「Hello from ExpressoTS!」メッセージが返却されました。
import { provide } from "inversify-binding-decorators";
@provide(AppUseCase)
export class AppUseCase {
/*
* executeメソッドの戻り値に「Hello from ExpressoTS!」が設定されています。
*/
execute() {
return "Hello from ExpressoTS!";
}
}
Getメソッドについて以上です。
Postメソッド
Postメソッドは、初期のプロジェクトに実装がされていないため、追加実装します。
なお、ExpressoTSは@Get("/") デコレータをPostに変更するだけでは動作せず、その他プロバイダなどを実装する必要があります。
数ファイル追加しつつ、既存のコードに追加記述しながら進めます。
Postメソッドのシナリオと実装概要
アクセスするURIパスは、http://localhost:3000/user
とします。
リクエストにname,email項目を送信して、レスポンスにname,email,statusをセットし受け取ります。
POSTメソッドにおいて、JSON形式のリクエストBody部に名前と電子メールの項目、およびレスポンスには名前と電子メールに加え処理結果を返却する想定で実装をします。
なお、各ファイルを追加する際にexpressots generate
コマンドを使用することができます。
これからの作業はexpressots generateコマンドを使用しながら進めます。
Postメソッド実装後のファイル群
初期で生成されたファイルの修正に加え、「app.provider.ts」および「user.dto.ts」を追加します。
$ tree -I node_modules
.
|-- README.md
|-- expressots.config.ts
|-- jest.config.ts
|-- package-lock.json
|-- package.json
|-- src
| |-- app.container.ts
| |-- app.controller.ts
| |-- app.module.ts
+| |-- app.provider.ts
| |-- app.usecase.ts
| |-- main.ts
+| `-- user.dto.ts
|-- test
| `-- app.usecase.spec.ts
|-- tsconfig.build.json
`-- tsconfig.json
Postメソッドの実装
以下に実装するファイルと詳細内容を説明します。
1. 送受信する項目の定義記述
user.dto.tsファイルを用意します。
このファイルにJSON形式で送受信を行う項目定義をします。
entityもしくはdtoとして定義します。今回は、dtoとして定義しました。
user.dtoを以下のコマンドにより追加します。
$ expressots generate dto user2
[🐎 Expressots]
[dto] user dto created! ✔️
続いて内容を編集します。
以下の内容を記述します。
export interface IUserRequestDTO {
name: string;
email: string;
}
export interface IUserResponseDTO {
name: string;
email: string;
status: string;
}
2. POSTメソッドから呼び出される業務ロジック部分の記述
続いて、app.usecase.tsファイルを編集します。
このファイルは、すでにGetメソッドから呼び出されるexecute()メソッドが実装されています。
今回は、このファイルにPostメソッドで使用するexecutePostを追加で記述します。
ここで追加したexecutePostをControllerのPostデコーダ内の処理から呼び出します。
import { provide } from "inversify-binding-decorators";
import { IUserRequestDTO, IUserResponseDTO } from "./user.dto"
@provide(AppUseCase)
export class AppUseCase {
execute() {
return "Hello from ExpressoTS!";
}
+ executePost(data: IUserRequestDTO): IUserResponseDTO {
+ return {
+ name: data.name,
+ email: data.email,
+ status: "Success"
+ };
+ }
}
3. POSTデコーダ部分の記述
app.controller.tsファイルに追加記述します。
import { BaseController, StatusCode } from "@expressots/core";
import { Response } from "express";
import { AppUseCase } from "./app.usecase";
import { Get, Post, body, controller, response } from "@expressots/adapter-express";
import { IUserRequestDTO, IUserResponseDTO } from "./user.dto"
@controller("/")
export class AppController extends BaseController {
constructor(private appUseCase: AppUseCase) {
super();
}
// こちらは自動生成されたGetメソッドの値の返却処理
@Get("/")
execute(@response() res: Response) {
return res.send(this.appUseCase.execute());
}
/*
* @Postを追加して、usreパスにアクセスします。
* executePost()を呼び出し業務処理を行います。
* 今回はリクエスト内容に処理結果を追加したJSONデータを返却する実装にします。
* BaseControllerより継承されたcallUseCaseを呼び出しデータを返却し、処理を終了します。
*/
+ @Post("user")
+ async executePost(
+ @body() payload: IUserRequestDTO,
+ @response() res: Response
+ ): Promise<IUserResponseDTO> {
+ console.log("body: ", payload);
+ const result: IUserResponseDTO = this.appUseCase.executePost(payload);
+ return this.callUseCase(
+ result,
+ res,
+ StatusCode.Created,
+ );
+ }
}
4. Body部のパース処理の記述
app.provider.tsファイルを追加します。
以下の実装によりリクエストに含まれるBody部のデータを内部的に扱うことができるようになります。
$ expressots generate provider app
[🐎 Expressots]
[provider] app provider created! ✔️
app.provider.tsファイルを開き以下の内容を記述します。
/*
* @expressots/coreに定義されている、IMiddleware, Middlewareを読み込みます。
*/
import { provide } from "inversify-binding-decorators";
+ import { IMiddleware, Middleware } from "@expressots/core";
+ import { AppExpress } from "@expressots/adapter-express";
+ import { container } from "./app.container";
@provide(App)
export class App extends AppExpress {
+ private middleware: IMiddleware;
/*
* MiddlewareのaddBodyParserメソッドにてBody部のパースをするので、
* 必要なモジュール類を用意します。
*/
+ constructor() {
+ super();
+ this.middleware = container.get<IMiddleware>(Middleware);
+ }
+ protected configureServices(): void {
+ this.middleware.addBodyParser();
+ }
}
5. Providerの有効化
このファイルが最後の編集ファイルです。
上記Providerを追加しましたので、AppFactoryに認識させます。
main.tsファイルを開きます。
上記で追加したapp.provider
を認識させます。
import "reflect-metadata";
import { AppFactory } from "@expressots/core";
import { container } from "./app.container";
import { ServerEnvironment } from "@expressots/adapter-express";
+ import { App } from "./app.provider";
async function bootstrap() {
- const app = await AppFactory.create(container, []);
+ const app = await AppFactory.create(container, App);
await app.listen(3000, ServerEnvironment.Development);
}
bootstrap();
Postメソッドの動作確認
curlコマンドからPOSTとして、http://localhost:3000/user
リクエスト送信が正しく動作するか確認します。
{"name":"aaa2","email":"aaa2@bbb","status":"Success"}が返却されれば正常に操作しています。
$ curl -X POST -H "Content-Type: application/json" \
-d '{"name": "aaa2", "email": "aaa2@bbb"}' \
http://localhost:3000/user
{"name":"aaa2","email":"aaa2@bbb","status":"Success"}
Postメソッドについては以上です。
補足
上記の初期プロジェクト作成時の手順でNon-Opinionated
をOpinionated
として生成すると、各種IOが含まるプロジェクトが生成されます。Non-Opinionatedでは基本的にはGetメソッドのみで、各資材やモジュールの配置もシンプルでした。
Opinionatedを指定して展開するとストレージ間I/O(参照、更新、削除処理)など各機能が満載なプロジェクトが生成されます。
興味のある方はお試しくださいませ。
? Select a template (Use arrow keys) ※ 2つ目のOpinionatedを選択する
Non-Opinionated :: A simple ExpressoTS project.
> Opinionated :: A complete ExpressoTS project with an opinionated structure and features.
さいごに
今回はExpressoTSで簡易APIサーバを作ってみるということで実装した内容を紹介しました。
次回は、ExpressoTSのバックエンドのなかでデータベースへの書き込み処理について実装してみたいと思います。