みなさんこんにちは、現役エンジニアのサメハックです
未経験からWebエンジニアに転職し、
正社員として5年働いたのちフリーランスとして独立しました。
Node.jsの解説シリーズです。
今回はAngular環境にExpressの導入とプロキシ設定について学んでいきましょう!
駆け出しエンジニアや未経験の方、
また新入社員を指導する先輩社員にとっても
わかりやすいように解説していきます!
- Expressの導入ができる
- プロキシ設定をしてローカルでサーバ環境を動かせる
- SQLiteのDBと接続できる
- 全てが動作する環境が手に入る
そのまま手に入るので超有料級だよ!
環境構築しよう!
初期設定用コマンド
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: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",
フロントエンドの設定
ボタンを押すとサービスファイルに記述した
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自体の設定はサーバ側で行います。
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>
DBの準備
今回はSQLiteとExpressを接続します。
環境がない方は以下の記事を参考にインストール&テーブル作成してください。
このように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ファイルへアクセスする設定です。
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を渡し、指定のユーザを取得
ルーティング設定する構文
express.Router().get("パス", async function (req, res, next) { 処理 });
ルーティングについてはこちらを参考にしてね!
実際に起動させてみよう!
超重要なことなのですが、
本環境ではmy-appとmy-app/serverは別々に起動させる必要があります!
フロントエンドの起動
my-appをVSCodeで開いてnpm start
Express(サーバ)の起動
my-app下の/serverをコマンドプロンプトで開いてターミナルで
nodemon
を入力してください。
ブラウザで確認
http://localhost:4200
へアクセスしてください。
■全てのユーザを取得を押下
■ユーザIDが1のユーザを取得を押下
GitHubのサンプルコード
今回作ったものはGitHubにあげているので
うまく動作しなかった方は是非ダウンロードしてみてください。
まとめ
全体的な処理フローをまとめるとこのようになります。
オレンジがフロントエンドの処理
緑がサーバサイドの処理となっています。
※システムが変われば、user.jsとDBの間にRepositoryとEntityがあります。
- プロキシ設定を行うことで別ポートへのアクセスが可能となる
- sqlite3の導入でExpress→SQLiteの接続が可能となる
- app.jsでExpressの基本設定と大まかなルーティング設定を行う
- routes/user.js等で詳細なルーティング設定を行う
- CRUD処理の種別はコンポーネント←→サーバ間で一致させる
- コンポーネント側からはHTTP通信を行い、作成したAPIパスへアクセス
満足いただけたら、1クリックなのでSNSフォローしてもらえると嬉しいです🦈