Angular, React & Node.js開発環境構築 / サンプル・プログラム

--------------------------------------------------------------------------------
・概要
  ・Node.jsでDataBaseからデータを入手するAPIを用意します。
    http://192.168.56.121:3000/api/list
    AngularやReactのスクリプトからこのAPIを呼び、フロント画面(Client画面)に
    表示します。
  ・Angular表示チェック
    http://192.168.56.121
  ・React表示チェック
    http://192.168.56.121:8081
--------------------------------------------------------------------------------

・前処理

 ・用意するもの

    CentOS-8.1.1911-x86_64-dvd1.iso

--------------------------------------------------------------------------------

・Oracle VM VirtualBox マネージャ で 新規作成

 ・名前    ang01
 ・タイプ   Linux
 ・バージョン Other Linux (64bit)

 ・メモリサイズ 1024MB
 ・仮想ハードディスクを作成する
 ・ハードディスクのファイルタイプ: VDI(VirtualBox Disk Image)
 ・物理ハードディスクにあるストレージ: 可変サイズ
 ・ファイルの場所とサイズ: ang01, 80GB
 ・[作成]

--------------------------------------------------------------------------------

・ストレージ

 ・CentOS-8.1.1911-x86_64-dvd1.iso

・ネットワーク
 ・固定IPの場合
  ・[設定(S)] - ネットワーク(固定IP)
   ・ブリッジ・アダプタ
   ・[OK]

 ・DHCPの場合
  ・[設定(S)] - ネットワーク(Local Only)
   ・NAT
   ・Host Only
   ・[OK]

--------------------------------------------------------------------------------

・インストール

 ・起動(T)
 ・Install CentOS 8
  ・Language: English (United States)
  ・DATE & TIME: Asia Tokyo
  ・KEYBOARD: Japanese
  ・SOFTWARE SELECTION: Minimal Install;
  ・KDUMP: disabled
  ・NETWORK & HOST ; Ethernet = ON; ang01.mydomain
  ・[Begin Installation]

  ・ROOT PASSWORD
  ・USER CREATION       // <-- devusr

  ・(処理終了を待つ)

  ・Reboot

  ・DHCPの場合(Local Only の場合)
      /etc/sysconfig/network-scripts/ifcfg-enp0s8
        ONBOOT=yes

  ・固定IPの場合
      $ su -
      # cd /etc/sysconfig/network-scripts
      # cp ifcfg-enp0s3 _ifcfg-enp0s3_org
      # vi ifcfg-enp0s3
      # diff _ifcfg-enp0s3_org ifcfg-enp0s3
      < BOOTPROTO="dhcp"
      ---
      > BOOTPROTO="static"
      > IPADDR="192.168.3.223"
      > NETMASK="255.255.255.0"
      > GATEWAY="192.168.3.1"
      #
      
      # vi /etc/resolv.conf
      # cat /etc/resolv.conf
      nameserver 192.168.3.1
      #

  ・Reboot
      # shutdown -r now

--------------------------------------------------------------------------------

・以降、client端末からsshでログインし、処理します。

--------------------------------------------------------------------------------

# yum -y update

###
### reboot
###
# shutdown -r now

###
### remi and PowerTools.
###
# yum -y install epel-release https://rpms.remirepo.net/enterprise/remi-release-8.rpm; \
yum config-manager --set-enabled PowerTools

###
### Tools
###
# yum -y install tar wget net-tools gcc make zip unzip emacs nkf
# yum -y install ImageMagick ImageMagick-devel
# yum -y install git


###
### .bashrc
###
# cd
# vi .bashrc
# cat .bashrc
<<<
# .bashrc

# User specific aliases and functions

# alias rm='rm -i'
# alias cp='cp -i'
# alias mv='mv -i'

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

alias ls='ls'
alias ll='ls -la'
alias e='emacs'
alias delb='rm -f *~ .??*~'
>>>

###
### .emacs
###
# cd
# vi .emacs
# cat .emacs
<<<
(setq-default tab-width 4 indent-tabs-mode nil)
>>>

###
### MariaDB
###
# yum -y install mariadb mariadb-server mariadb-devel

# systemctl enable mariadb.service; \
systemctl start mariadb.service

# mysql -u root -p
パスワード入力はCR(Enter)だけ。
MariaDB [(none)]> set password for 'root'@'localhost' = password('...password...');
MariaDB [(none)]> create database devdb character set utf8;
MariaDB [(none)]> create user 'devusr'@'localhost' identified by '...password...';
MariaDB [(none)]> grant all privileges on devdb.* to 'devusr'@'localhost';
MariaDB [(none)]> exit

###
### Node
###
# yum -y install npm
# npm install -g express-generator
# cd /opt
# express --css stylus api
# cd api
# npm install
# npm install socket.io
# npm install mysql

# vi /etc/firewalld/services/node.xml
# cat /etc/firewalld/services/node.xml
<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>Node.js</short>
  <description>Service for SocketIO</description>
  <port protocol="tcp" port="3000"/>
</service>
#

# firewall-cmd --add-service=node --zone=public --permanent; \
firewall-cmd --reload; \
firewall-cmd --list-services --zone=public

# vi /etc/systemd/system/node.service
# cat /etc/systemd/system/node.service
[Unit]
Description=Node.js Server

[Service]
WorkingDirectory=/opt/api
Type=simple
ExecStart=/opt/api/bin/www
Restart=always
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=node-server
Environment=NODE_ENV=production PORT=3000
User=root
Group=root

[Install]
WantedBy=multi-user.target
#

# systemctl daemon-reload
# systemctl enable node
# systemctl start node
# systemctl status node

###
### angular CLI
###
# cd
# npm install -g @angular/cli

###
### Project Name: myapp
### My IP: 192.168.56.121
###
# firewall-cmd --zone=public --add-port=80/tcp --permanent; \
firewall-cmd --reload
# cd /opt
# ng new myapp
# cd myapp
# npm install rxjs
# npm install rxjs-compat
# vi angular.json
<<<
...
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "port": 80,                             <-- この行を追加。
            "browserTarget": "myapp:build"
          },
...
>>>
# ng serve --host 192.168.56.121       <-- これは、ちょっと時間がかかる。
....
^C
#

###
### myapp daemon
###
# vi /etc/systemd/system/myapp.service
# cat /etc/systemd/system/myapp.service
[Unit]
Description=MyWeb Application
After=network-online.target

[Service]
Restart=on-failure
WorkingDirectory=/opt/myapp
ExecStart=/usr/lib/node_modules/@angular/cli/bin/ng serve --host 192.168.56.121
CPUAccounting=true
CPUQuota=50%
MemoryAccounting=true
MemoryLimit=1024M

[Install]
WantedBy=multi-user.target
#

# systemctl daemon-reload; \
systemctl enable myapp

# systemctl start myapp    <-- これは、ちょっと時間がかかる。
# systemctl status myapp

###########################################################################################
###
### ここからAngularサンプルプログラム
###

###
### Test data
###
# mysql -u devusr -p devdb

MariaDB [devdb]> CREATE TABLE `album` (
    `id`     INT(11)      NOT NULL AUTO_INCREMENT,
    `artist` VARCHAR(100) NOT NULL,
    `title`  VARCHAR(100) NOT NULL,
    PRIMARY KEY (id)
);

MariaDB [devdb]> INSERT INTO `album` (`artist`, `title`) VALUES
('The Military Wives', 'In My Dreams'),
('Adele', '21'),
('Bruce Springsteen', 'Wrecking Ball (Deluxe)'),
('Lana Del Rey', 'Born To Die'),
('Gotye', 'Making Mirrors'),
("David Bowie", "The Next Day (Deluxe Version)"),
("Bastille", "Bad Blood"),
("Various Artists", "Call the Midwife (Music From the TV Series)");

MariaDB [devdb]> select * from album;
.....

MariaDB [devdb]> exit
#

###
### API
###
# cd /opt/api
# vi app.js
<<<
..........
var usersRouter = require('./routes/users');
var apiRouter   = require('./routes/api');    <-- 追加
..........
app.use('/users', usersRouter);
app.use('/api', apiRouter);                   <-- 追加
..........
>>>

# cd routes
# vi api.js
# cat api.js
var express = require('express');
var router = express.Router();

var mysql = require('mysql');

var db = mysql.createConnection({
  host: 'localhost',
  user: 'devusr',
  password: '...password...',
  database: 'devdb'
});

db.connect(function(err) {
  if (err) throw err;
});

var accessControlAllowOrigin = '*';

router.get('/', function(req, res, next) {
  var param = {"result": ""};
  res.header('Access-Control-Allow-Origin', accessControlAllowOrigin);
  res.send(param);
});

router.get('/list', function(req, res, next) {
  var sql = "SELECT `id`, `artist`, `title` FROM `album` ORDER BY `id` ASC"
  db.query(sql, function (err, result, fields) {
    if (err) throw err;
    var param = {"result": result};
    res.header('Access-Control-Allow-Origin', accessControlAllowOrigin);
    res.send(param);
  });
});

router.get('/get/:id', function(req, res, next) {
  var sql = "SELECT `id`, `artist`, `title` FROM `album` WHERE `id` = '" + req.params.id + "'"
  db.query(sql, function (err, result, fields) {
    if (err) throw err;
    var param = {"result": (result[0]) ? result[0] : []};
    res.header('Access-Control-Allow-Origin', accessControlAllowOrigin);
    res.send(param);
  });
});

module.exports = router;
#

# systemctl restart node

### check http://192.168.56.121:3000/api/list

###
### myapp
###   参考 https://qiita.com/ksh-fthr/items/840ae54472892a87f48d
###
# cd /opt/myapp
# ng generate service example
# cd /opt/myapp/src/app
# mkdir service

# vi app.module.ts
# cat app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { ExampleService } from './example.service';

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

# vi service/example.service.ts
# cat service/example.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';

@Injectable()
export class ExampleService {

  constructor(private http: HttpClient) { }

  url = 'http://192.168.56.121:3000/api/list';

  getData() {
    return this.http.get(this.url);
  }
}
#

# vi app.component.ts
# cat app.component.ts
import { Component } from '@angular/core';
import { ExampleService } from './example.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  public ansDat: any;

  constructor(private exampleService: ExampleService) { }

  ngOnInit() {
    this.exampleService.getData().subscribe(
      (Response: any) => {
        this.ansDat = Response.result;
      }
    )
  }
}
#

# cat app.component.html
<table class="table table-hover">
  <thead>
    <th>ID</th>
    <th>ARTIST</th>
    <th>TITLE</th>
  </thead>
  <tbody>
    <tr *ngFor = "let ans of ansDat">
      <td>{{ans.id}}</td>
      <td>{{ans.artist}}</td>
      <td>{{ans.title}}</td>
    </tr>
  </tbody>
</table>
#

# systemctl restart myapp

### check http://192.168.56.121

###########################################################################################
###
### ここからReactサンプルプログラム
###

# cd
# npm install -g create-react-app
# npm install -g serve
# cd /opt
# create-react-app my-react-app

# vi /etc/firewalld/services/react.xml
# cat /etc/firewalld/services/react.xml
<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>React</short>
  <description>React</description>
  <port protocol="tcp" port="8081"/>
</service>
#

# firewall-cmd --add-service=react --zone=public --permanent; \
firewall-cmd --reload; \
firewall-cmd --list-services --zone=public

# vi /etc/systemd/system/react.service
# cat /etc/systemd/system/react.service
[Unit]
Description=React Server

[Service]
WorkingDirectory=/opt/my-react-app
Type=simple
ExecStart=/usr/bin/serve -l 8081 -s build
Restart=always
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=node-server
Environment=REACT_ENV=production
User=root
Group=root

[Install]
WantedBy=multi-user.target
#

# systemctl daemon-reload
# systemctl enable react

# cd /opt/my-react-app; npm run build
# systemctl start react
# systemctl status react

### Check http://192.168.56.121:8081

# cd /opt/my-react-app/src
# vi index.js
# cat index.js
var React = require('react');
var ReactDOM = require('react-dom');

class AlbumList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      albums: [],
    };

    this.loadAjax = this.loadAjax.bind(this);
  }

  loadAjax() {
    return fetch(this.props.url)
      .then((response) => response.json())
      .then((responseJson) =>
        this.setState({
          albums: responseJson.result,
        })
      )
      .catch((error) => {
        console.error(error);
      });
  }

  UNSAFE_componentWillMount() {
    this.loadAjax();
  }

  render() {
    const album_list = this.state.albums.map((e) =>
      <tr key={e.id}><td>{e.id}</td><td>{e.artist}</td><td>{e.title}</td></tr>
    );
    return(
      <div>
        <h1>The Album List</h1>
        <table>
          <thead>
            <tr><th>ID</th><th>ARTIST</th><th>TITLE</th></tr>
          </thead>
          <tbody>
            {album_list}
          </tbody>
        </table>
      </div>
    );
  }
}

// ========================================

ReactDOM.render(
  <AlbumList url="http://192.168.56.121:3000/api/list" />,
  document.getElementById('root')
);
#

# systemctl stop react
# cd /opt/my-react-app; npm run build
# systemctl start react
# systemctl status react

### Check http://192.168.56.121:8081

.end of line