N予備校「プログラミング入門Webアプリ」を受講しています。
今回は第3章20節〜26節です。
承認されたユーザーだけが使える匿名掲示板の作成。
気をつけたい箇所や気付いた点だけをまとめました。
設計の進め方
1. システム要件の定義 2. UIの設計: ページに何をどう配置するか 3. URIの設計: パスの作り方(RESTful) 4. モジュールの設計URIとモジュール
:::note リクエストを具体的な処理に振り分けることをルーティング、 リクエストに対し具体的な処理をする関数を リクエストハンドラという :::サーバーを起動するもの
リクエストを処理するもの
ルーティングを行うもの
など、それぞれ役割を決めて実装する。
メインとなる機能から実装していくのが良い。
処理を実装する前にそれぞれの機能などをコメントする。
覚えておきたいこと
:::note テンプレートエンジンなどを使う場合、のちにhtmlになるものはviewsディレクトリに格納する。 :::以下はPOSTされた時の処理
// POSTの処理
let body =[];
req.on('data', (chunk) => {
body.push(chunk);
}).on('end', ()=>{
// クエリから値を取得
body = Buffer.concat(body).toString();
const params = new URLSearchParams(body);
const content = params.get('content');
console.info(`投稿されました: ${content}`);
});
querystring
が非推奨になったのでURLSearchParam
を使う。URIエンコードされた文字列のデコードも同時に行うので、decodeURIComponent
はいらない。
認証機能
`http-auth`モジュールの、ファイルを利用した認証admin:apple
guest1:1234
guest2:5678
const basic = auth.basic({
realm: 'Enter username and password.',
file: './users.htpasswd'
})
Basic認証では、特定のURLにアクセスした際、ステータスコード 401 - Unauthorized を返すことでログアウトされる。
function handleLogout(req, res){
handleStatus(req, res, 401, 'ログアウトしました');
}
function handleNotFound(req, res){
handleStatus(req, res, 404, 'ページが見つかりません');
}
function handleBadRequest(req, res){
handleStatus(req, res, 400, '未対応のメソッドです');
}
/**
* HTTPレスポンスの共通処理
* @param {Object} req HTTP Request
* @param {Object} res HTTP Response
* @param {Number} statusCode
* @param {String} msg Message
*/
function handleStatus(req, res, statusCode, msg){
res.writeHead(statusCode, {
'Content-Type' : 'text/plain; charset=utf-8'
});
res.end(msg);
}
データベース
今回はPostgreSQLを`sequelize`モジュールで操作。services:
app:
depends_on:
- db
db:
image: postgres:12
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: secret_board
TZ: "Asia/Tokyo"
docker-compose.yml
にdb
というサービス名でPostgreSQLのコンテナを追加。
仮想環境上でapp
とdb
の二つのサーバーが稼働する状態。
Sequelize
yarn add sequelize@6.5.0
yarn add pg@8.5.1
yarn add pg-hstore@2.3.3
データベースの構成を設定することを、データモデリングという
// sequelizeの基本設定
const { Sequelize, DataTypes } = require('sequelize');
// データベース全体の設定
const sequelize = new Sequelize(
// IDとパスワードを渡す
'postgres://postgres:postgres@db/secret_board',
{
// 起動ログなどのログを出力しない
logging: false
}
);
// データモデリング
const Post = sequelize.define(
// テーブル名
'Post',
{
// データ定義
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
content: {
type: DataTypes.TEXT
},
postedBy: {
type: DataTypes.STRING
},
trackingCookie: {
type: DataTypes.STRING
}
},
{
// テーブル名の固定
freezeTableName: true,
// createdAt, updatedAtを自動追加
timestamps: true
}
);
// このファイルの起動時にデータベースの設定を同期する
Post.sync();
// データモデリングしたオブジェクトをモジュールとして公開
module.exports = Post;
insert
```javascript const Post = require('./post'); Post.create({ //登録したいデータ }).then(() => { //登録後に実行してほしい処理 }) ``` 非同期処理となる(Promiseオブジェクトが返ってくる)ので、thenメソッドを利用する。コンソール上でのデータ確認
dbコンテナへ入るdocker-compose exec db bash
su postgres
: ユーザーの変更(今回はpostgresというユーザー)
psql
: Postgres用コンソールに入る
¥c secret_borad
: データベースに接続(今回はsecret_board
というデータベース)
¥q
: 終了
select
データの全件取得// データの全件取得
Post.findAll({order:[['id', 'DESC']]}).then((posts) => {
res.end(
pug.renderFile('./views/posts.pug', { posts })
);
});
renderFile
の引数に変数を指定することで、pugで参照可能となる。
delete
`destroy`で指定したキーのデータを削除 投稿者自身か管理者のみが削除できるようにするfunction handleDelete(req, res){
switch(req.method){
case 'POST':
let body = [];
req.on('data', (chunk) => {
body.push(chunk);
}).on('end', () =>{
body = Buffer.concat(body).toString();
const params = new URLSearchParams(body);
const id = params.get('id');
Post.findByPk(id).then((post) => {
if(req.user === post.postedBy || req.user === 'admin'){
post.destroy().then(() => {
handleRedirectPosts(req, res);
})
}
})
})
break;
default:
util.handleBadRequest(req, res);
break;
}
}
curlなどで第三者がdeleteを送る可能性があるため、サーバー側でもユーザーのチェックをすること。
実行するとデータベースから実際にデータを削除することを物理削除、実際にデータを削除する代わりに「削除された」ことを表すフラグを立ててデータが削除されたとみなすことを論理削除という。
削除処理は慎重に。実用的にも論理削除が良い。
データベースの永続化
dockerを破棄するとデータも破棄されるので、PC側の別の場所にファイルを保存する。db:
image: postgres:12
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: secret_board
TZ: "Asia/Tokyo"
volumes:
- ../secret-board-db:/var/lib/postgresql/data
PC側: ../secret-board-db
docker側: var/lib/postgresql/data
docker起動時に同期させる
トラッキングCookie
:::note ユーザーの行動を追跡するために付与される Cookie のことをトラッキング Cookie と呼ぶ。 :::cookies
というnpmモジュールを利用する
yarn add cookies@0.8.0
const Cookies = require('cookies');
const trackingIdKey = 'tracking_id';
// TrackingCookie
const cookies = new Cookies(req, res);
addTrackingCookie(cookies);
function addTrackingCookie(cookies){
// Cookieが存在しない場合
if(!cookies.get(trackingIdKey)){
// ランダムな数値を登録
const trackingId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
// 有効期限を翌日(24時間後)まで
const tomorrow = new Date(Date.now() + (1000 * 60 * 60 * 24));
cookies.set(trackingIdKey, trackingId, {expires: tomorrow});
}
}
MAX_SAFE_INTEGER
: JavaScriptで扱える最大値
PUG側
```pug each post in posts - let isPostedAdmin = (post.postedBy === 'admin') if isPostedAdmin h3 #{post.id} : 管理人 ★ else h3 #{post.id} : ID:#{post.trackingCookie} p!= post.content p 投稿日時: #{post.createdAt} - let isAdmin = (user === 'admin') if isAdmin p 投稿者: #{post.postedBy} - let isDeletable = (user === post.postedBy || isAdmin) if isDeletable form(method="post" action="/posts?delete=1") input(type="hidden" name="id" value=post.id) button(type="submit") 削除 hr ```each in
渡されてきたコレクションをループ。
-
pugテンプレート内にJavaScriptを直接記述することができる。
p!= post.content
pugテンプレート内で文字列に含まれたタグを認識させる。