【有料級!】Angular-Express-SQLiteを接続しよう!【GitHubサンプルあり】

ANGULAR

みなさんこんにちは、現役エンジニアのサメハックです

未経験からWebエンジニアに転職し、
正社員として5年働いたのちフリーランスとして独立しました。

Node.jsの解説シリーズです。

今回はAngular環境にExpressの導入とプロキシ設定について学んでいきましょう!

駆け出しエンジニアや未経験の方、
また新入社員を指導する先輩社員にとっても
わかりやすいように解説していきます!

この記事を読むと・・・
  • Expressの導入ができる
  • プロキシ設定をしてローカルでサーバ環境を動かせる
  • SQLiteのDBと接続できる
  • 全てが動作する環境が手に入る
いわゆるMEANスタックのDBがSQLiteになった環境が
そのまま手に入るので超有料級だよ!

環境構築しよう!

初期設定用コマンド

ng new my-app
cd my-app
ng generate service service/app

npm install sqlite3

npm install express
npm install -g express-generator
express server
cd server 
del routes\test.js
type nul > routes\user.js
npm install cookie-parser

cd ..
type nul > proxy.conf.json

npm install
npm start

補足

ng new my-app
cd my-app
ng generate service service/app

//sqlite3のインストール
npm install sqlite3

// ↓Expressのインストール
npm install express
// ↓Expressの自動設定ツールのインストール
npm install -g express-generator
// ↓serverというDirにExpress環境を構築
express server
// ↓serverへ移動し、ファイルの整理とパッケージのインストール
cd server 
del routes\test.js
type nul > routes\user.js
npm install cookie-parser
npm install nodemon

// ↓親Dirにプロキシ設定ファイルを作成
cd ..
type nul > proxy.conf.json

npm install
npm start

やっていることは上記のとおりです。

このようなファイル構成になるよ!

プロキシの設定

そもそもプロキシとは

この時点で実はExpressの環境自体はできています。
しかし、基本的にExpressとフロントエンドの環境は別のポートで動いています。

  • フロントエンド:http://localhost:4200/
  • Express  :http://localhost:3000/

Angular環境ではこのように動いています。

ポートの壁は超えられないのでこのままではフロントエンドとExpressは接続ができません


つまり、サーバ側(=Express)で設定したAPIを叩くことができません!

このポートの壁を突破するための変換機能がプロキシです。

プロキシ設定をすることでポートの壁を超えることができるよ!

proxy.conf.json

proxy.conf.json

{
  "/api": {
    "target": "http://localhost:3000",
    "secure": false,
    "logLevel": "debug",
    "pathRewrite": { "^/api": "/" }
  }
}

このようにプロキシ設定をすることで、
/apiへのアクセスをhttp://localhost:3000へ変換します。

図解するとこのようになります。
逆に言うと、このような設定をしないと、
フロントエンドからExpressのAPIを叩くことはできません。

“http://localhost:4200/api”と指定すると、
“http://localhost:3000″へアクセスするプロキシだよ!

パッケージの設定

{
  "name": "my-app",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve -o --proxy-config proxy.conf.json",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^15.1.0",
    "@angular/common": "^15.1.0",
    "@angular/compiler": "^15.1.0",
    "@angular/core": "^15.1.0",
    "@angular/forms": "^15.1.0",
    "@angular/platform-browser": "^15.1.0",
    "@angular/platform-browser-dynamic": "^15.1.0",
    "@angular/router": "^15.1.0",
    "express": "^4.18.2",
    "rxjs": "~7.8.0",
    "sqlite3": "^5.1.4",
    "tslib": "^2.3.0",
    "zone.js": "~0.12.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^15.1.3",
    "@angular/cli": "~15.1.3",
    "@angular/compiler-cli": "^15.1.0",
    "@types/jasmine": "~4.3.0",
    "jasmine-core": "~4.5.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.0.0",
    "typescript": "~4.9.4"
  }
}

念のためそのまま掲載していますが、書き換える行は以下のみです。
他のバージョン等は異なっていて問題ありません。

"start": "ng serve -o --proxy-config proxy.conf.json",
npm startした際に、proxy.conf.jsonの設定を参照して起動するよ!

フロントエンドの設定

ボタンを押すとサービスファイルに記述した
HTTP通信処理を実行するシステムを作成します。

tsconfig.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2020",
    "module": "es2020",
    "strictPropertyInitialization": false, //これを追記
    "lib": ["es2020", "dom"]
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

// HTTP通信を行うためのモジュール
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    // HTTP通信を行うためのモジュール
    HttpClientModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

app.service.ts

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class AppService {
  // HttpClientをインスタンス化(DI)
  constructor(private http: HttpClient) {}

  // http://localhost:4200/api/user/
  // 上記のURLへアクセスし、全てのユーザを取得
  fetchAllUsers() {
    return this.http.get('/api/user/');
  }

  // http://localhost:4200/api/user/:id
  // 上記のURLへアクセスし、指定のユーザを取得
  // ※:idはユーザのIDを渡します
  fetchUserById(id: number) {
    return this.http.get(`/api/user/${id}`);
  }
}
  • すべてのユーザの取得  → /api/user/all
  • 指定したidのユーザを取得 → /api/user/:id

2つのAPIを実行する関数を作成しています。

API自体の設定はサーバ側で行います。

HTTP通信について復習したい人はこちら!

【Angular】HTTP通信をしよう!【HttpClient】

app.component.ts

import { Component } from '@angular/core';
import { AppService } from './service/app.service';

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

  // サービスファイルをDI
  constructor(private service: AppService) {}

  /**サービスファイルのfetchAllUsersを実行する */
  onFetchAllUsers() {
    this.service.fetchAllUsers().subscribe((res: any) => {
      this.users = res;
    });
  }

  /**サービスファイルのfetchUserByIdを実行する */
  onFetchUserById() {
    this.service.fetchUserById(1).subscribe((res: any) => {
      this.user = res;
    });
  }
}

// User型
interface User {
  id: number;
  name: string;
}

app.component.html

<div>
  <button (click)="onFetchAllUsers()">全てのユーザを取得</button>
  <!-- usersの取得が完了すると表示される -->
  <ng-container *ngIf="users">
    <p>すべてのユーザを取得しました!</p>
    <ul>
      <li *ngFor="let user of users">
        id:{{ user.id }} , name: {{ user.name }}
      </li>
    </ul>
  </ng-container>
</div>

<div>
  <button (click)="onFetchUserById()">ユーザIDが1のユーザを取得</button>
  <!-- userの取得が完了すると表示される -->
  <ng-container *ngIf="user">
    <p>idが1のユーザを取得しました!</p>
    <li>id:{{ user.id }} , name: {{ user.name }}</li>
  </ng-container>
</div>
*ngIfを指定したコンポーネントはデータ取得後に表示されるよ!

DBの準備

今回はSQLiteとExpressを接続します。
環境がない方は以下の記事を参考にインストール&テーブル作成してください。

【SQLite】SQLiteの環境構築とテーブル作成

このようにid, name カラムを持つuserテーブルを作ってmyDbという名前で保存します。

保存先はmy-app/serverの直下です。

このようなファイル構成になるよ!

Express(サーバ)の設定

基本的な設定はexpress serverを実行した時点で完了しています。

  • SQLiteとの連携
  • 新規APIの作成
  • ルーティング設定

これらの処理を追加で実装していきます。

app.js

var createError = require("http-errors");
var express = require("express");
var path = require("path");
var cookieParser = require("cookie-parser");
var logger = require("morgan");

var indexRouter = require("./routes/index");
// var usersRouter = require("./routes/users"); // これをコメントアウト※削除が理想
var userRouter = require("./routes/user"); // これを追加

var app = express();

// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jade");

app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));

app.use("/", indexRouter);
// app.use("/users", usersRouter); // これをコメントアウト※削除が理想
app.use("/user", userRouter); // これを追加

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get("env") === "development" ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render("error");
});

module.exports = app;

このファイルでサーバ側の基本設定をしているのですが、
重要な要素として大まかなルーティング設定を行っています。

ルーティングの構文

app.use("APIのパス", 詳細なルーティング設定をしたファイル)

補足

var userRouter = require("./routes/user");
// 中略
app.use("/user", userRouter);

例えば今回追加したルーティングは

"/user"

というパスが渡された場合にuserRouterファイルへアクセスする設定です。

実際にアクセスするためにはプロキシ設定パスと結合して“/api/user”となるよ!

user.js

const express = require("express");
var userRouter = express.Router();
const sqlite3 = require("sqlite3");
// DBとの接続
const db = new sqlite3.Database("./myDb.db");

/**
 * /api/user/
 * 全てのユーザを取得
 */
userRouter.get("/", async function (req, res, next) {
  console.log("fetchAllUsers  :  /api/user/");
  let result = null;
  const data = await db.all("select * from user", [], (err, rows) => {
    if (err) {
      // エラー発生時.
      result = err.message;
    }
    // 取得データ rows に対する処理.
    result = rows;
    console.log("result", result);
    // 取得データを呼び出し元(クライアント側に返す)
    res.send(result);
  });
});

/**
 * /api/user/:id
 * パラメータとしてIDを渡し、指定のユーザを取得
 */
userRouter.get("/:id", async function (req, res, next) {
  const userId = req.params.id;
  console.log(`fetchUserById  :  /api/user/${userId}`);
  let result = null;
  const data = await db.get(
    `select * from user where id=:userId `,
    [userId],
    (err, rows) => {
      if (err) {
        // エラー発生時.
        result = err.message;
      }
      // 取得データ rows に対する処理.
      result = rows;
      console.log("result", result);
      // 取得データを呼び出し元(クライアント側に返す)
      res.send(result);
    }
  );
});

module.exports = userRouter;

app.jsで設定したルーティングパスの後のパスを定義し
処理を振り分けています。

  • “/api/user/”   → 全てのユーザを取得
  • “/api/user/:id” → パラメータとしてIDを渡し、指定のユーザを取得
ここでDBにSQLコマンドを流しているよ!

ルーティング設定する構文

express.Router().get("パス", async function (req, res, next) { 処理 });
.getの部分はクライアント側のCRUD処理の内容と合致させる必要があるよ!
ルーティングについてはこちらを参考にしてね!

404 NOT FOUND – サメハック
Infomation for Engineer🦈

実際に起動させてみよう!

超重要なことなのですが、

本環境ではmy-appとmy-app/serverは別々に起動させる必要があります!

フロントエンドの起動

my-appをVSCodeで開いてnpm start

Express(サーバ)の起動

my-app下の/serverをコマンドプロンプトで開いてターミナルで

nodemon

を入力してください。

ブラウザで確認

http://localhost:4200

へアクセスしてください。

■全てのユーザを取得を押下

■ユーザIDが1のユーザを取得を押下

myDb内のユーザテーブルのデータが正しく取得できたよ!

GitHubのサンプルコード

今回作ったものはGitHubにあげているので
うまく動作しなかった方は是非ダウンロードしてみてください。

GitHub - same-hack/Angular-Express-SQLite
Contribute to same-hack/Angular-Express-SQLite development by creating an account on GitHub.
Angularの環境構築は大変なのでよければどうぞ!

まとめ

全体的な処理フローをまとめるとこのようになります。
オレンジがフロントエンドの処理
緑がサーバサイドの処理となっています。

※システムが変われば、user.jsとDBの間にRepositoryとEntityがあります。

  • プロキシ設定を行うことで別ポートへのアクセスが可能となる
  • sqlite3の導入でExpress→SQLiteの接続が可能となる
  • app.jsでExpressの基本設定と大まかなルーティング設定を行う
  • routes/user.js等で詳細なルーティング設定を行う
  • CRUD処理の種別はコンポーネント←→サーバ間で一致させる
  • コンポーネント側からはHTTP通信を行い、作成したAPIパスへアクセス

満足いただけたら、1クリックなのでSNSフォローしてもらえると嬉しいです🦈

タイトルとURLをコピーしました