この記事では5回に分けてwebアプリケーションの作り方を説明していく
第1回
webアプリケーションの構成、仕組み
第2回
サーバー立ち上げ(この記事)
第3回
フロントエンドの実装
第4回
バックエンドの実装
第5回
メニュー画像の読み取り
実際に作ったアプリケーション
やること
今回のではサーバーの立ち上げ、アプリケーションサーバー(Go)に情報を送る。そして、以下のような画面構成にする。これらについて解説していく。
実際に書いていく
まずサーバーの設定と立ち上げ、その後、地図の初期化やバックエンドの情報を送るためのコードを書いていく。
では、サーバーを設定し立ち上げていく。
その前に使用技術についても紹介しておこう。
今回使用するのは JavaScript, CSS, Node.js, Express, CORS である。
JavaScript
動的なウェブページを作成するために広く使用されているプログラミング言語。ブラウザで直接実行され、クライアントサイド(フロントエンド)のスクリプトとして機能するが、以下の Node.js などを使用することでサーバーサイド(バックエンド)として機能することも可能
CSS
CSS(Cascading Style Sheets)は、ウェブページのデザインとレイアウトを制御するために使用されるスタイルシート言語。HTMLと組み合わせることで、ユーザーインターフェースの外観と感触を向上させることが可能
Node.js
大量の同時接続をさばけるネットワークアプリケーションの構築を目的に設計されたJavaScript環境。非同期I/O(入出力)処理を行なっており、C10K問題(クライアントの同時接続数がある程度まで増えると、サーバ自体に問題がないにも関わらず、サーバ性能が劣化(レスポンス速度が低下するなど)する)などを解決できる機能が備わっている。また、他のフレームワーク等と違い必要最低限のみをダウンロードすれば良いので早く動くため採用
Express
ExpressはNode.jsのための最も人気のあるWebアプリケーションフレームワーク。利点としては以下の3つ
-
簡潔なルーティング: URLルートに対するリクエストを処理する関数を簡単に定義できる。これにより、異なるHTTPメソッド(GET, POSTなど)に応じて異なるアクションを実行できる
-
ミドルウェアのサポート: リクエストとレスポンスの間に特定の機能を実行するミドルウェアを組み込むことができます。これにより、エラーハンドリング、セキュリティ対策、データ処理などが柔軟に行える
-
シンプルなAPI: RESTful APIを簡単に構築できる
CORS
CORS(Cross-Origin Resource Sharing)は、異なるオリジン間でリソースを安全に共有するためのブラウザの仕組み。ウェブアプリケーションが異なるドメインのAPIやリソースにアクセスする場合、ブラウザはセキュリティ上の理由からこのアクセスを制限するが、CORS設定を適切に行うことで、特定の外部ドメインからのリクエストを許可することが可能になる
前回の記事を読んでくれた方はわかると思うが、webブラウザでURLを入力し、今回だとwebサーバーで JavaScript が Node.js によって記載されており、それらがまず、webブラウザに情報を返却し、ユーザーが画面を見れるようになる。その後、ユーザーが希望金額を入力し、送信ボタンを押すと、webサーバーから Go で書かれたアプリケーションサーバーへ Express や CORS などによってHTTPリクエストが送られて、そこで適切な処理をした結果がwebサーバーに返却され、それをwebブラウザに返却し、そこでまたユーザーに読めるように解析して、ユーザーに表示してくれているのである。
では実際に書いてく
サーバーの立ち上げ
WifiRader/
├── frontend/
| ├── app.css # スタイルシート定義
| ├── app.js # クライアントサイド(フロントエンド)のロジック 画面の動きをここに書く(第3回で書く)
| └── server.js # Node.jsのサーバーサイド(バックエンド)スクリプト
└──backend/
├── looking-for.go #第4回で書く
├── main.go #第4回で書く
└── scraping.go #第5回で書く
HTMLファイルがない、と思う方もいると思うが後でそれについても説明する。
まずはserver.jsから書いていく。ここでまずはサーバーを設定して立ち上げる。
const express = require('express');
const app = express();
const port = 3000;
const cors = require('cors');
app.use(express.static('.'));
app.use(cors());
app.get('/', (req, res) => {
res.send('Welcome to the Home Page!');
});
const startServer = async () => {
app.listen(port, async () => {
console.log(`Server running on http://localhost:${port}`);
try {
const open = (await import('open')).default;
open(`http://localhost:${port}`);
} catch (error) {
console.error("Failed to open browser:", error);
}
});
};
startServer();
それぞれについて解説していく
const express = require('express');
const app = express();
const port = 3000;
const cors = require('cors');
ここでは必要な Express や CORS を使用できるようにするためにインポートする。またサーバーが動作するポート番号を3000で設定する。
app.use(express.static('.'));
app.use(cors());
上の行ではアプリケーションのルートURL(例えば http://localhost:3000/ )にアクセスしたとき、カレントディレクトリ内の index.html などの静的ファイルを自動的に探し、ブラウザに返すように設定している。ファイルが見つかれば、その内容がレスポンスとして返される。
下の行ではCORSを設定しており、CORSミドルウェアは上でも述べたように、ウェブページが異なるドメインのリソースにアクセスできるようにするためのもの。デフォルトでは、ブラウザはセキュリティ上の理由から、異なるオリジン間でリソースを共有することを制限しているが、cors() ミドルウェアを使用することで、異なるドメインからのリクエストに対してAPIが応答を返すことを許可できるようになる
app.get('/', (req, res) => {
res.send('Welcome to the Home Page!');
});
-
app.get(): このメソッドはExpressのアプリケーションオブジェクトで定義されており、HTTPのGETリクエストを指定されたパス(この場合はルートパス '/')に対してリッスンする。これは、ブラウザからサーバーに何か情報を要求する時の一般的な方法である
-
ルートパス '/': ここで指定されている '/' はウェブサイトのホームページを意味する。つまり、このハンドラーはウェブサイトのルートURL(例えば http://localhost:3000/ )にアクセスがあった場合に動作するよう設定されている
-
コールバック関数 (req, res) => {...}: この関数は、リクエストが行われた際に実行されます。req(リクエストオブジェクト)とres(レスポンスオブジェクト)の2つの引数を持つ
-
req: リクエストオブジェクトは、HTTPリクエストに関する情報を含んでいる。これには、URLパラメータ、ヘッダー、ユーザーが送信したデータなどが含まれる
-
res: レスポンスオブジェクトは、クライアントへの応答を構築するために使われる。このオブジェクトを使って、サーバーはデータを送り返したり、ステータスコードを設定したりしている
-
-
res.send('Welcome to the Home Page!'): このメソッドはレスポンスオブジェクトの一部で、クライアントに対してテキストメッセージを送信している(今回はとりあえずこのように設定した)。この場合、ユーザーがホームページにアクセスしたときに「Welcome to the Home Page!」というメッセージが表示される
const startServer = async () => {
app.listen(port, async () => {
console.log(`Server running on http://localhost:${port}`);
try {
const open = (await import('open')).default;
open(`http://localhost:${port}`);
} catch (error) {
console.error("Failed to open browser:", error);
}
});
};
startServer();
ここで実際に Node.js の Express アプリケーションでサーバーを非同期的に起動し、成功した場合にはデフォルトのウェブブラウザで自動的に開くようにしている。具体的な構成要素について詳しく説明する。
const startServer = async () => {
app.listen(port, async () => {
// ...
});
};
ここで実際にstartServer関数を定義している。
app.listen(port, callback)
は、指定したポートでサーバーを起動し、サーバーが起動したらコールバック関数を実行する。ここでは、コールバック関数も非同期関数として定義されている。
ここでasync関数について簡単に説明する。
async関数とは非同期関数のことである。つまり、ある時間のかかる操作をしているときに、その操作が終わるのを待たずに他の処理を進めてしまおう、という考え方を実現するための関数である。この関数を定義した中では、await というキーワードを使用することができ、await を使用するとその操作が終わるまで待ってから別の操作を行う、というものである。
今回で言うと、app.listen 関数(サーバーの起動)と open(http://localhost:${port}
) が非同期であり、open 関数のインポートが同期的処理である。つまり、サーバーを起動してから、Server running on ~~ と表示して、その後に open モジュールをインポートしてから、それを利用して localhostのブラウザを起動しているのではなく、非同期処理を用いて、サーバーの起動とブラウザの起動を同時に行っているイメージである。しかし、open モジュールを利用するためにインポートする必要があるので await を利用して、インポートが終わってから実際にwebブラウザを open しているのである。
try {
const open = (await import('open')).default;
open(`http://localhost:${port}`);
} catch (error) {
console.error("Failed to open browser:", error);
}
コールバック関数の中では簡単なエラーハンドリングが行われている。主にやっていることは以下の2つで、
ブラウザを開く:open(http://localhost:${port}); により、デフォルトのブラウザでサーバーのURLを開く。これにより、サーバーが正常に動作していることをすぐに確認できる。
エラーハンドリング:try-catchブロックを使用して、ブラウザの起動に失敗した場合のエラーハンドリングを行っている。
これでサーバーを立ち上げられた。
次に HTML ファイルを書いていくのだが、ここで問題があり、Google MAPS API (現在地を取得するAPI)などを使用するのだが、HTML ファイルにキーを直接書き込まなくてはいけなく、セキュリティ面に不安が残る。なので今回は環境変数から読み込むために JavaScript のファイルに HTML ファイルと同様のものを書いていく。
とりあえずは HTML ファイルを作成し、その後に server.js ファイルに埋め込む。
HTML を作成
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Wifi Radar</title>
<link rel="stylesheet" href="/app.css">
<script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API _KEY&language=ja&libraries=geometry"></script>
<script src="/app.js" defer></script>
</head>
<body>
<div id="container">
<div id="sidebar">
<button id="toggleButton">⇄</button>
<input type="number" id="desiredAmount" placeholder="希望金額を入力" />
<button onclick="submitLocationAndAmount()">送信</button>
<iframe id="placeIframe"></iframe>
</div>
<div id="map"></div>
</div>
</body>
</html>
これは実際に作った画面と見比べて作成してみれば作ってみて欲しい
<script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&language=ja&libraries=geometry"></script>
ここでの async 属性ではHTMLの解析と並行してスクリプトが読み込まれ、スクリプトのダウンロードが完了次第すぐに実行されるようにしている。
ここの部分の YOUR_API_KEY に実際の Google Maps API が有効になっている API キーを貼り付けてくれれば動くと思う。しかしこれではセキュリティ面に問題が残るので、環境変数から読み込むために JavaScript に書き直そう。そんなに難しくないので、安心して欲しい。
最初にコードを載せる
require('dotenv').config();
const express = require('express');
const app = express();
const port = 3000;
const googleMapsApiKey = process.env.GOOGLE_MAPS_API_KEY;
const cors = require('cors');
app.use(express.static('.'));
app.use(cors());
app.get('/', (_, res) => {
res.send(`
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Wifi Radar</title>
<link rel="stylesheet" href="/app.css">
<script async src="https://maps.googleapis.com/maps/api/js?key=${googleMapsApiKey}&language=ja&libraries=geometry"></script>
<script src="/app.js" defer></script>
</head>
<body>
<div id="container">
<div id="sidebar">
<button id="toggleButton">⇄</button>
<input type="number" id="desiredAmount" placeholder="希望金額を入力" />
<button onclick="submitLocationAndAmount()">送信</button>
<iframe id="placeIframe"></iframe>
</div>
<div id="map"></div>
</div>
</body>
</html>
`);
});
const startServer = async () => {
app.listen(port, async () => {
console.log(`Server running on http://localhost:${port}`);
try {
const open = (await import('open')).default;
open(`http://localhost:${port}`);
} catch (error) {
console.error("Failed to open browser:", error);
}
});
};
startServer();
先ほどできた server.js ファイルに作成した html ファイルを突っ込んだように見えるだろう。新しく付け加わった部分について説明していく
const googleMapsApiKey = process.env.GOOGLE_MAPS_API_KEY;
ここで、環境変数から googleMapsApiKey 変数に実際のAPIキーを代入している。今回は frontend ディレクトリの中に .env というファイルを作り、そこに環境変数を設定して読み込む仕様にした。
app.get('/', (_, res) => {
res.send(`
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Wifi Radar</title>
<link rel="stylesheet" href="/app.css">
<script async src="https://maps.googleapis.com/maps/api/js?key=${googleMapsApiKey}&language=ja&libraries=geometry"></script>
<script src="/app.js" defer></script>
</head>
<body>
<div id="container">
<div id="sidebar">
<button id="toggleButton">⇄</button>
<input type="number" id="desiredAmount" placeholder="希望金額を入力" />
<button onclick="submitLocationAndAmount()">送信</button>
<iframe id="placeIframe"></iframe>
</div>
<div id="map"></div>
</div>
</body>
</html>
`);
});
この部分についてだが、先ほどとやっていることはほとんど変わりない。先ほどのコードで
app.get('/', (req, res) => {
res.send('Welcome to the Home Page!');
});
このようにしていたところだが、req のリクエストが入らなくなったのは、最初に静的な画面を返す際にreqは必要なくなったので _ にして使用していない。
今回も前のコードと同様に、ルートディレクトリにアクセスすると res.send() 内の内容が読み込まれて実行される。今回だと静的な HTML コンテンツを返すためのコードが読み込まれる。
これにて server.js の内容は完成だ。
次に画面の動きを作るために app.js と app.css について記載する
画面構成
再度今回作る画面を見せる。
上記のような構成で、css 適当にやってくれれば良いと思うので、今回はコードだけを載せて省略する。
#container {
position: relative;
height: 100vh;
width: 100vw;
}
#sidebar {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 25%;
background-color: #c4faff;
box-shadow: 2px 0 5px rgba(0,0,0,0.5);
padding: 10px;
transform: translateX(0); /* 初期位置 */
transition: transform 0.3s ease-in-out;
z-index: 1000;
}
#map {
height: 100%;
width: 100%;
}
#toggleButton {
position: absolute;
top: 40%;
right: -40px;
width: 40px;
height: 40px;
cursor: pointer;
}
#placeIframe {
width:100%;
height:95%;
}
難しすぎてわからない、というところはないと思う。初期位置を0にすることで最初にサイドバーが表示されている状態にすることができる。もし隠したい場合は-100%としておけば最初は隠れて表示されるだろう。
最後に
今回はサーバーの立ち上げと技術選定の理由について説明した。何か作るときには技術選定をきちんとすることをお勧めする。最初に構成をきちんと考えてからコードを書き始めることで、書いていて困ることが少なくなるので、最初に時間をかけてでもするべきだと感じた。次の回でフロントの動きをつけたりするための app.js について説明していく。