Node.js
MongoDB
Express
angular

注意事項

本稿は後述する参考サイトでやられていることを Angular 6 でやってみた自分用のメモになります。

なので、書かれている方法がセオリーではないとか、色々知識足らずで誤認している場合があるかもしれませんので本稿を参考に学習される際はその点留意の上で、お願いします。

また、何かお気づきの点があったらご教授いただければ嬉しいです。

やりたいこと

  • Mean stack の構築
    • Angular Cli と npm で構築する(yeoman とか gulp とか grunt とかは使わない。というか本来Java屋なのでこの辺はよく知らない。)
    • MongoDB は Docker 上に構築する

バージョン

  • MongoDB: v3.6.4
  • Express: v4.16.3
  • Angular: v6.0.0
  • Node.js: v8.11.1

作業の流れ

  1. Node.js の環境構築
  2. Angular の環境構築
  3. Express の環境構築
  4. Angular と Express の連携
  5. MongoDB の環境構築
  6. Express と MongoDB の連携

Node.js の環境構築

以下の流れで進めていきます。

  1. nodebrew のインストール
  2. Node.js のインストール

nodebrew のインストール


以下は Homebrew がすでにインストールされている前提です。
Homebrew のインストールについては公式を参照するのが一番良いと思います。


以下のように Homebrew を使って nodebrew をインストールします。

$ brew install nodebrew

Node.js のインストール

以下のように nodebrew を使って Node.js をインストールします。
(nodebrew の詳細を知りたい方は nodebrew github まで)

$ nodebrew install-binary v8.11.1

※ ちなみに、 nodebrew install を使うとすごく時間がかかりますので、注意。

これで Node.js の準備ができました。

Angular の環境構築

  1. Angular Cli のインストール
  2. Angular Application の作成
  3. Angular Application の動作確認
  4. ビルド設定の変更およびビルド

Angular Cli のインストール

以下のように npm を使って Angular Cli をインストールします。
(今回はグローバルにインストールします)

$ npm install -g @angular/cli

Angular Application の作成

以下のように Angular Cli を使って Angular Application の雛形を作成します。
(以下では、雛形作成時にルーティング用の設定ファイルの生成も同時に行うように指定しています。)

$ ng new sample-mean --routing=true

Angular Application の動作確認

ng new で作成した雛形はそのままで完全に動作します。
以下のコマンドで一旦動作することを確認します。
--open をつけることでアプリケーション起動後に自動でブラウザでページが開きます)

$ cd sample-mean
$ ng serve --open

何も問題なければ、以下のようなページが表示されます。

ビルド設定の変更およびビルド

ビルドされたファイルはデフォルトの設定だと dist/sample-mean に出力されます。
後述するバックエンドについても dist に置くため、出力先を以下のように変更します。

angular.json(抜粋)
"outputPath": "dist/client"

設定変更後、一度以下のコマンドでビルドをしておきます。

$ ng build --prod

Express の環境構築

  1. Express のインストール
  2. バックエンドの実装
  3. トランスパイルと起動

Express のインストール

npm installexpressbody-parser をインストールします。
インストールは ng new で生成されたルートフォルダ( sample-mean ディレクトリ直下)で行います。

ちなみに、 body-parser は request body のパースを行うためのミドルウェアです。
また、 --saveオプションをつけることで package.json の dependencies にインストールしたパッケージが自動で追加されます。
@types/~ は Typescript用の型定義になります。)

$ npm install --save express @types/express body-parser @types/body-parser @types/compression

バックエンドの実装

ng new で作成した Angular プロジェクトに Express を使ったバックエンド部分を作り足していきます。
npm install express-generator によってインストールされる express コマンドを使うとng new みたいな感じでアプリケーションスケルトンが作成できますが、その場合フロントエンド部分も同時に作成されるようです。
今回は Express をあくまでバックエンドとして使いたいだけですので1つ1つ実装していきます。

まず以下のような構成でフォルダ、ファイルを作成します。

ディレクトリ構成
sample-mean/
 └ server/
    ├ bin/
    │  └ www.ts
    ├ routes/
    │  └ users.ts
    ├ app.ts
    ├ config.ts
    └ tsconfig.json

次に作成した各ファイルに実装を記述していきます。

tsconfig.json
{
  "compileOnSave": false,
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "es2017",
      "dom"
    ],
    "declaration": false,
    "sourceMap": true,
    "outDir": "../dist/server",
    "moduleResolution": "node",
    "typeRoots": [
      "../node_modules/@types"
    ],
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  }
}

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
export const serverPort = 4300;

app.ts
import * as express from 'express';
import * as path from 'path';
import { json, urlencoded } from 'body-parser';
import * as compression from 'compression';

import { userRouter } 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', userRouter);

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

export { app };

users.ts
import { Request, Response, Router } from 'express';

const userRouter: Router = Router();

const users = {
  users: [
    {
      id: 0,
      name: 'user0'
    },
    {
      id: 1,
      name: 'user1'
    }
  ]
};

userRouter.get('/', (req: Request, res: Response) => {
  res.json(users);
});

export { userRouter };

トランスパイルと起動

以下のコマンドで、トランスパイルを行います。
ちなみに、npx コマンドを使うとローカルのパッケージを実行できます。

$ npx tsc -p ./server

node コマンドでトランスパイルされたエンドポイントを指定するとサーバが起動します。

$ node dist/server/bin/www.js

http://localhost:4300/api/users にアクセスすると以下のようにレスポンスが返されるはずです。

Angular と Express の連携

  1. service の作成
  2. component の作成
  3. proxyの設定
  4. ルーティングの設定
  5. HttpClient モジュールの設定
  6. 動作確認
  7. scriptsの設定

service の作成

service は以下のようなディレクトリ構成になるように作成していきます。
(特に同じ構成にする必要はありません。)

ディレクトリ構成
sample-mean/
 └ src/
    └ app/
       └ services/
          └ apis/
             ├ users-api.service.ts
             └ users-api.service.spec.ts

まずはディレクトリを作成していきます。
以下はカレントディレクトリが app である前提となります。

$ mkdir -p services/apis

次に Angular Cli で service のスケルトンを作成します。

$ ng g service usersApi

今回は Mean stack を構築することが目的なので、usersを取得する単純なメソッドのみを作成していきます。

users-api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class UsersApiService {

  constructor(
    private http: HttpClient
  ) { }

  getUsers() {
    return this.http.get('api/users');
  }
}

component の作成

Angular と Express の連携自体には不要ですが、
連携結果をビューとして確認するために Component を作成します。

component は以下のようなディレクトリ構成になるように作成していきます。
(特に同じ構成にする必要はありません。)

ディレクトリ構成
sample-mean/
 └ src/
    └ app/
       └ component/
          └ users/
             ├ users.component.ts
             ├ users.component.spec.ts
             ├ users.component.css
             └ users.component.html

まずはディレクトリを作成していきます。
以下はカレントディレクトリが app である前提となります。

$ mkdir components

次に Angular Cli で component のスケルトンを作成します。

$ ng g component users

先に作成した service を使って users を取得・表示するように実装していきます。

users.component.ts
import { Component, OnInit } from '@angular/core';
import { UsersApiService } from '../../services/apis/users-api.service';

@Component({
  selector: 'app-users',
  templateUrl: './users.component.html',
  styleUrls: ['./users.component.css'],
  providers: [ UsersApiService ]
})

export class UsersComponent implements OnInit {
  users: any;

  constructor(
    private usersApiService: UsersApiService
  ) { }

  ngOnInit() {
    this.usersApiService.getUsers().subscribe(res => {
      this.users = res['users'];
    });
  }
}
users.component.html
<table border="1px">
  <tr>
    <th>id</th>
    <th>name</th>
  </tr>
  <tr *ngFor="let user of users">
    <td>{{user.id}}</td>
    <td>{{user.name}}</td>
  </tr>
</table>
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>

proxyの設定

Angular 側からExpress 側にアクセスするために proxyの設定が必要になります。
プロジェクトのルートディレクトリに proxy.conf.json を作成し、以下のように設定を記述します。
このproxy設定を利用するには ng serve コマンドで実行する際に --proxy-config オプションで proxy.conf.json を渡してやります。

proxy.conf.json
{
  "/api": {
    "target": "http://localhost:4300",
    "secure": false,
    "changeOrigin": true
  }
}
proxy設定を渡す場合のコマンド実行例
$ ng serve --proxy-config proxy.conf.json

ルーティングの設定

app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { UsersComponent } from './components/users/users.component';

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

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

HttpClient モジュールの設定

作成した service の中で HttpClientModule を使用するので app.module.ts に追加しておきます。

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { UsersComponent } from './components/users/users.component';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent,
    UsersComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

動作確認

まず、Express 側を起動しておきます。

$ node dist/server/bin/www.js

次に proxy 設定を読み込ませて Angular 側を起動します。

$ ng serve --proxy-config proxy.conf.json

動作に問題なければ、以下のような画面が表示されるはずです。

http:localhost:4200 にアクセスした画面。

画面上の Users をクリックし、 users の内容が表示された画面。

scriptsの設定

先程の動作確認では Angular 側と Express 側を個別に起動していました。
また Angular 側では、 proxy 設定を指定する必要がありました。
開発を進めていくにあたって都度この手順を繰り返すのは効率が悪いので、
package.json の scripts を修正して使っていきます。

まず、 nodemonnpm-run-all というパッケージをインストールします。

$ npm install --save-dev npm-run-all nodemon

次に package.json を以下のように書き換えます。

package.json(scripts部分抜粋)
  "scripts": {
    "ng": "ng",
    "tsc": "tsc -p ./server",
    "watch:ng": "ng serve --open --proxy-config proxy.conf.json",
    "watch:tsc": "tsc -w -p ./server",
    "watch:nodemon": "nodemon dist/server/bin/www.js",
    "start:p": "run-p watch:*",
    "start": "run-s tsc start:p",
    "build:ng": "ng build --prod",
    "build": "run-p tsc build:ng",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },

上記のように scripts を書き換えると以下のコマンドで Angular 側、 Express 側を1つのコマンドで起動できるとともにそれぞれのソースコードに変更があれば変更を反映してくれます。

$ npm start

MongoDB の環境構築

今回は MongoDB をローカル環境にインストールするのではなく、 Docker 上に用意します。
合わせて MongoDB の Web GUI 環境である Express も同時に用意したいので docker-compose を使います。

  1. docker-compose.yml の作成
  2. コンテナの起動
  3. mongodbの下準備

docker-compose.yml の作成

以下のような docker-compose.yml を作成します。
MongoDB の方は Port の 27017 をバインドし、
Express の方は Port の 8081 をバインドする設定です。
また、 Express は Basic 認証の設定をいれてあります。

docker-compose.yml
version: '3.6'

services:
  mongo:
    image: mongo
    restart: always
    volumes:
      - mongodb-data:/data/db
    environment:
      MONGO_INITDB_ROOT_USERNAME: username
      MONGO_INITDB_ROOT_PASSWORD: password
    ports:
      - 27017:27017

  mongo-express:
    image: mongo-express
    restart: always
    ports:
      - 8081:8081
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: username
      ME_CONFIG_MONGODB_ADMINPASSWORD: password
      ME_CONFIG_BASICAUTH_USERNAME: username
      ME_CONFIG_BASICAUTH_PASSWORD: password

volumes:
  mongodb-data:

コンテナの起動

docker-compose.yml があるディレクトリに移動し、以下のコマンドを実行します。
-d はバックグランド実行のためのオプションです。

$ docker-compose up -d

MongoDBの下準備

コンテナが立ち上がったら以下のコマンドで MongoDB のコンテナ内に入ります。

$ docker exec -it mongo_mongo_1 bash

コンテナ内に入ったら MongoDB のシェルを起動します。

$ mongo admin -u username -p password

作成するデータベース用のユーザを追加します。

> use sample_db

> db.createUser({
  ... user: "sample",
  ... pwd: "sample",
  ... roles: [{
  ... role: "readWrite",
  ... db: "sample_db"
  ... }]
  ... })

さらにRDBのテーブルにあたる Collection を作成します。

> db.createCollection("users")

最後にダミーデータを作成しておきます。

> for(let i = 0; i < 20; i++) {
  ... db.users.save({id: i, name: "name" + i})
  ...} 

Express と MongoDB の連携

  1. mongoose のインストール
  2. Model の作成
  3. MongoDB との接続設定
  4. 動作確認

mongoose のインストール

$ npm install --save mongoose @types/mongoose

Model の作成

以下のようなディレクトリ構成になるようにディレクトリを作成し、ファイルを追加します。

ディレクトリ構成
sample-mean/
 └ server/
    └ models/
       └ users.model.ts
users.model.ts
import * as mongoose from 'mongoose';

const usersModel: mongoose.Model<mongoose.Document> = mongoose.model(
  'users',
  new mongoose.Schema({
      id: {
        type: Number
      },
      name: {
        type: String
      }
    })
);

export { usersModel };

MongoDB との接続設定

config.ts
export const serverPort = 4300;
export const mongoUrl = 'mongodb://sample:sample@localhost/sample_db';
www.ts
import * as http from 'http';
import { app } from '../app';
import { serverPort, mongoUrl } from '../config';
import * as mongoose from 'mongoose';

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

const server = http.createServer(app);

server.listen(port, () => {
  mongoose.connect(mongoUrl);
});
users.ts
import { Request, Response, Router } from 'express';
import { usersModel } from '../models/users.model';

const userRouter: Router = Router();

userRouter.get('/', (req: Request, res: Response) => {
  usersModel.find({}, function(err, users) {
    if (err) { throw err; }
    res.json(users);
  });
});

export { userRouter };

動作確認

動作確認の前に users.component.ts を以下のように修正してください。

users.component.ts
import { Component, OnInit } from '@angular/core';
import { UsersApiService } from '../../services/apis/users-api.service';

@Component({
  selector: 'app-users',
  templateUrl: './users.component.html',
  styleUrls: ['./users.component.css'],
  providers: [ UsersApiService ]
})

export class UsersComponent implements OnInit {
  users: any;

  constructor(
    private usersApiService: UsersApiService 
  ) { }

  ngOnInit() {
    this.usersApiService.getUsers().subscribe(res => {
      this.users = res;
    });
  }
}

http://localhost:4200/users にアクセスすると、以下のように MongoDB に作成したダミーデータが表示されれば OK です。

参考

MEANスタック入門