MEANスタック入門(4) AngularとExpressの連携

今回はExpressで作成したAPIをAngularから実際に利用しつつ、Angularの基本的な開発方法に触れていきます。

project-name/src/app配下で作業し、作業後の構成は以下のようになります。

app
│  app-routing.module.ts
│  app.component.html
│  app.component.scss
│  app.component.spec.ts
│  app.component.ts
│  app.module.ts
│
├─components
│  └─call-express-api
│          call-express-api.component.html
│          call-express-api.component.scss
│          call-express-api.component.spec.ts
│          call-express-api.component.ts
│
└─services
        call-express-api.service.spec.ts
        call-express-api.service.ts

Serviceの作成

Angularでは、各種APIへのアクセスはServiceから行います。

servicesディレクトリを作成し、Angular CLIng g serviceコマンドで必要ファイルを生成します。 ng g serviceコマンドはカレントディレクトリに依存するので、servicesディレクトリまで移動してから実行します。

$ ng g service callExpressApi

実行すると以下の2ファイルが生成されます。

  • call-express-api.service.ts
  • call-express-api.service.spec.ts

specのほうはテスト用なので、call-express-api.service.tsのほうにAPIにアクセスするgetUsersメソッドを実装します。

まず、HTTPアクセスに必要なHttpクラスをインポートしてコンストラクタの引数に指定します。 また、Http.getはObservable型の値を返すので、Observable.mapを使うためにrxjs/add/operator/mapをインポートします。

call-express-api.service.ts

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class CallExpressApiService {

  constructor(
    private http: Http
  ) { }

  getUsers() {
    return this.http.get("api/users")
            .map(response => response.json());
  }
}

上記ではapi/usersにアクセスしていますが、ng serverで実行している場合、Angularの簡易サーバとAPIサーバが別のポートで動いているので、Angular側からAPIを呼ぶと404のエラーになります。 また、ポートまで指定してAPIにアクセスした場合も、クロスオリジンのエラーで同じく実行出来ません。

これを回避するには、プロジェクトのルートディレクトリにproxy.conf.jsonを作成し、ng server実行時に--proxy-configオプションで読み込ませます。

proxy.conf.json

{
  "/api": {
    "target": "http://localhost:4300",
    "secure": false
  }
}
$ ng server --proxy-config proxy.conf.json

これでポートの差異を意識せず開発出来るようになります。

Componentの作成

Componentはビューの部品のようなもので、各部品毎の表示や動作を定義します。

componentsディレクトリを作成し、Angular CLIng g componentコマンドで必要ファイルを生成します。

ng g componentコマンドはng g serviceと同じくカレントディレクトリに依存するので、componentsディレクトリまで移動してから実行します。

$ ng g component callExpressApi

実行するとcall-express-apiディレクトリが生成され、その配下に以下4ファイルが生成されます。

  • call-express-api.component.html
  • call-express-api.component.scss
  • call-express-api.component.ts
  • call-express-api.component.spec.ts
call-express-api.component.ts

CallExpressApiComponentクラスを作成し、先ほど作成したCallExpressApiServiceを実行して、HTML表示用の変数に代入します。

CallExpressApiServiceをインポートしてコンストラクタの引数に指定し、Serviceのインスタンスを生成するためにprovidersで読み込みます。これは、app.module.tsでグローバルに読み込んでも動きますが、今回はこのComponentだけなのでここで読み込みます。

getUsersはObservableを返すので、subscribeメソッドでデータを受け取って変数usersに代入します。

call-express-api.component.ts

import { Component, OnInit } from '@angular/core';
import { CallExpressApiService } from '../../services/call-express-api.service';

@Component({
  selector: 'app-call-express-api',
  templateUrl: './call-express-api.component.html',
  styleUrls: ['./call-express-api.component.scss'],
  providers: [CallExpressApiService]
})
export class CallExpressApiComponent implements OnInit {

  users: any;

  constructor(
    private callExpressApiService: CallExpressApiService
  ) { }

  ngOnInit() {
    this.callExpressApiService.getUsers().subscribe(u => {
      this.users = u.users;
    });
  }
}
call-express-api.component.html

APIからの返答が代入された変数usersを利用して、内容を表示するHTMLを作成します。

配列を受けて要素の数だけ繰り返し表示させる場合、ngForディレクティブを利用します。*プレフィックスを付与することでtr要素がテンプレート化され、そのテンプレートを元にusersの各要素の内容が表示されます。

call-express-api.component.html

<table border="1">
  <tr *ngFor="let user of users">
    <td>{{user.id}}</td>
    <td>{{user.address}}</td>
  </tr>
</table>

ルーティングの設定

初めに表示されるComponentはapp直下にあるapp.component.htmlです。 ここにナビゲーションを用意し、ナビゲーションをクリックするとヘッダーを残してコンテンツ部分が切り替わるような、いわゆるシングルページアプリケーションの構成にします。

routerLinkディレクティブでURIを指定し、<router-outlet>タグでコンテンツ表示位置を設定します。

app.component.html

<header>
  <h1 class="logo"><a href="/">MEAN</a></h1>
  <nav>
    <ul>
      <li><a routerLink="/users">Users</a></li>
    </ul>
  </nav>
</header>
<router-outlet></router-outlet>

Angularアプリをng newで初めに生成する際、--routing=trueを付けているとapp直下にapp-routing.module.tsが生成され、app.module.tsで既に読み込まれています。

このapp-routing.module.tsに、ルーティングさせたいURIとComponentを記述します。

app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { CallExpressApiComponent } from './components/call-express-api/call-express-api.component';

const routes: Routes = [
  {path: 'users', component: CallExpressApiComponent},
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

動作確認

ng serverで実行している場合のAngular側のテストは、http://localhost:4200にアクセスします。 f:id:tadajam:20170504094858p:plain

Usersのリンクをクリックすると、APIのレスポンスを受けてテーブルが表示されます。 f:id:tadajam:20170504095029p:plain

ビルド後はdistディレクトリのclient配下のファイルも揃うので、dist/server/bin/www.jsを実行するだけでhttp://localhost:4300からclient配下のファイルも提供されます。

MEANスタック入門(3) Expressを利用したバックエンド開発

今回はAngular CLIで作成したAngularのディレクトリ階層を足場にして、Expressを活用したバックエンドの開発を行います。
言語はTypeScriptで書いていきますので、トランスパイルの方法などにも触れていきます。

TypeScriptのインストール

基本的にコードはTypescriptで書くのでtypescriptをインストールします。
--save-devオプションを指定すると、package.jsondevDependenciesに書き込まれます。

npm install --save-dev typescript

ローカルインストールしたのでtscnode_modules/.bin/配下にあります。
node_modules/.bin/にパスを通しておくか、以下のように実行します。

$ node_modules/.bin/tsc

ここから先はパスを通した前提で進めます。

Expressのインストールとバックエンドの実装

まずはexpressとリクエストデータを処理するためのbody-parserをインストールします。 --saveオプションを指定すると、package.jsondependenciesに書き込まれます。

$ npm install --save express body-parser

ルートディレクトリにserverディレクトリを作成し、以下のような構成にします。

server
│  app.ts
│  config.ts
│  tsconfig.json
│
├─bin
│    www.ts
│
└─routes
     users.ts

エントリポイントはbin配下のwww.tsです。 server直下のapp.tsconfig.tsを参照します。

www.ts

import * as http from "http";
import { app } from "../app";
import { serverPort } from "../config";

const port = process.env.PORT || serverPort;
app.set("port", port);

const server = http.createServer(app);

server.listen(port);

設定ファイルconfig.tsにポート番号を記載します。

config.ts

export const serverPort = 4300;

www.tsの中でhttp.createServerに渡しているapp.tsには、サーバーでリクエストを受け取った時の処理を書きます。

ルーティングはここで実行し、実際の処理はroutes配下のファイルに振ります。

ちなみに開発中にAngularをng serverで実行している場合はdist/clientは空なので、ルートにアクセスしても404になります。

app.ts

import * as express from "express";
import * as path from "path";
import { json, urlencoded } from "body-parser";
import * as compression from "compression";

import { usersRouter } from "./routes/users";

const app: express.Application = express();
app.disable("x-powered-by");

app.use(json());
app.use(compression());
app.use(urlencoded({ extended: true }));

app.use(express.static(path.join(__dirname, "../client")));

app.use("/api/users", usersRouter);

app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, '../client/index.html'));
});

export { app };

users.tsを作成してapiの実体を書きます。 ここでは/api/usersにGETでアクセスがあった場合、ダミーデータのjsonを返すようにします。

users.ts

import { Request, Response, Router } from "express";

const usersRouter: Router = Router();

let users = {users: [
  {
    id: 0,
    address: "NA6IF2OMBSTK2NEMXMYDQYXT32IGPKHSMULWLAKE"
  },
  {
    id: 1,
    address: "NDSNEM2HC4IEPQXGVDOWYMJ2MIPENUXN6HFJDHYZ"
  }
]};

usersRouter.get("/", (request: Request, response: Response) => {
  response.json(users);
});

export { usersRouter };

トランスパイルとサーバーの実行

これでロジックは完成なので、トランスパイルの設定をserver直下のtsconfig.jsonに書きます。 outDirで出力先を設定します。

tsconfig.json

{
  "compilerOptions": {
    "declaration": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": ["es6", "dom"],
    "module": "commonjs",
    "moduleResolution": "node",
    "outDir": "../dist/server",
    "target": "es6",
    "typeRoots": [
      "../node_modules/@types"
    ]
  }
}

tsc-pオプションで上記で作成したtsconfig.jsonの位置を指定して実行します。

$ tsc -p ./server

トランスパイルされたwww.jsを実行すると、config.tsに設定したポートでサービスが提供されます。

$ node dist/server/bin/www.js

動作確認

http://localhost:4300/api/usersにアクセスすると、apiからレスポンスが返ってきます。 f:id:tadajam:20170503233422p:plain

これでExpress側の準備は一旦完了です。

MEANスタック入門(2) Angularを利用したフロントエンド開発

今回はAngular CLIを利用したAngularのHello World的な内容になります。

Angular CLIのインストール

Angular2アプリの開発には、必須という訳ではありませんがAngular CLIを利用するのがおすすめです。
ディレクトリ構成を自動的に整えてくれますし、各種Componentのひな型の生成、簡易サーバによる高速なプレビューなど、非常に効率的に開発出来ます。

今後も基本的にAngular CLIで生成されたディレクトリを足場にして以降の開発を進めていきます。

まずはangular-cliをインストールします。

$ npm install -g @angular/cli
$ ng -v
@angular/cli: 1.0.1
node: 6.9.4
os: win32 x64

Angular2アプリの作成と実行

次にAngular2アプリを作成します。

CSSではなくSCSSなどの言語を利用したい場合は--styleオプションで切り替え、ルーティングモジュール付きで生成する場合は--routingオプションで設定します。 ルーティングモジュールはapp.moduleに直接書くケースもあるようですが、別ファイルに分けたほうが後々開発しやすいと思うので、trueで実行します。

$ ng new project-name --style=scss --routing=true

作成したアプリをng serverコマンドで実行します。

$ cd project-name
$ ng server

実行するとプロジェクトがビルドされ、簡易サーバが立ち上がります。

動作確認

http://localhost:4200にアクセスすると、動作が確認出来ます。 f:id:tadajam:20170430232920p:plain

同時にソースを監視するようになっているので、コードを書き換えるたびに自動的にリビルドされて非常に便利です。

Angular2アプリのビルド

実行中のアプリを本番環境用にビルドします。

デフォルトだと、ビルドされたファイルはdistディレクトリに配置されます。 後ほどdistディレクトリをサーバ側と共有する予定なので、.angular-cli.jsonappsにあるoutDirを書き換えて出力先を変更します。

.angular-cli.json

"outDir": "dist/client"

ng buildでビルドします。 --prodオプションを付けると公開用にminifyされたファイルが生成されます。

$ ng build --prod

これでAngular2側の準備は一旦完了です。

MEANスタック入門(1) MEANスタックとは

Angularにハマってしばらく色々と実験していたものの、フロントエンド開発だけではやれないことが出てきたので、バックエンド開発も含めて一通り触ってみようと思い立ちました。

当然のようにMEANスタックに辿り着き、関連する様々な記事を読んでみましたが、イマイチ最新かつ全体を網羅した良い感じの記事に出会えませんでした。 例えば、Angularが古いAngularJSであったり、Angular CLIを使っていなかったり、また、TypeScriptではなかったり。

今更AngularJSは使いたくありませんし、Angular CLIを使いたいので、そうなるとフロントエンドもバックエンドもTypeScriptで統一したいです。ただ、資料がないということは、もしかしたら主流の構成じゃないのかもしれませんが。。

結構サイトによってフォルダ構造などもまちまちで、何が本当に最適な手法かは分からなかったのですが、GitHubなどを見ながら正解を探し求め、最終的にとりあえず辿り着いたそれっぽい結論を、自分用の備忘録も兼ねてまとめておきます。

前提知識

筆者の事前知識は以下のような状況なので、ほぼ入門者と変わらないと思われます。

  • JavaScriptは割と昔から触ったことがあった。
  • TypeScriptや、いわゆるモダンJavaScriptには最近出会った。
  • 最近AngularJSを初めて触り、その流れでAngular2を調べ始めた。
  • Angularに触るまでNode.jsは触ったことがなかった。
  • ExpressやMongoDBは今回初めて触った。

よって、とりあえず動かしてみるというレベルであれば、前提となる知識はJavaScriptを理解している程度で良いかと思います。

MEANスタックとは

MEANスタックとは、MongoDB、Express、Angular、Node.jsの頭文字をとったもので、4つを組み合わせてバックエンドからフロントエンドまで全部JavaScriptで開発してしまおうというものです。

複数の言語を行き来しなくて良いというのはかなり魅力的です。

MongoDB

JSONJavaScriptと親和性が高い、NoSQL型ドキュメント指向データベース。

Express

Node.js上で動作するWEBアプリケーションのフレームワーク。 MEANスタックで利用される場合には、主にAPIの実装などバックエンド開発を担う。

Angular

フロントエンドのフレームワーク。モダンJavaScriptの教科書とも言われ、数年ぶりにJavaScriptに触るというような人にはもってこいの教材。

Node.js

サーバーサイドJavaScriptの実行環境。

事前準備

Node.jsのインストール

OSによってまちまちだと思うので、以降はNode.jsはすでに入っている前提で進めます。
入っていなければ公式からインストールします。

パスが通ってなければ通しておきます。

自分がインストールしたバージョンは以下です。

$ node -v
v6.9.4
MongoDBのインストール

同じく、以降はMongoDBはすでに入っている前提で進めます。
入っていなければ公式からインストールします。

インストールフォルダ(自分の環境だとC:/MongoDB/Server/3.4/bin)にパスを通しておきます。

インストールしたバージョンは以下です。

$ mongod --version
db version v3.4.4
git version: 888390515874a9debd1b6c5d36559ca86b44babd
OpenSSL version: OpenSSL 1.0.1u-fips  22 Sep 2016
allocator: tcmalloc
modules: none
build environment:
    distmod: 2008plus-ssl
    distarch: x86_64
    target_arch: x86_64

データベースは任意のディレクトリに保存出来るので、bin直下にmongodb.configを作成して、データベースの保存場所を指定します。

mongodb.config

dbpath=C:/MongoDB/data

上記を指定してmongodを起動します。

$ mongod --config "C:/MongoDB/Server/3.4/bin/mongodb.config"

これでデータベースが起動しました。

次回からは実際にMEANスタックを利用した開発に入っていきます。