【Progate】Node.jsコース 学習内容まとめ

## 自己紹介
29歳。文系卒。新卒入社でWebディレクターを２年半経験。
その後は番組の字幕制作の仕事に従事。
現在はエンジニア転職を目指して勉強中。

## Node.js
Node.jsとは、Javascriptをサーバーサイドで動かすための実行環境…です(まだ自分の中で解釈がふわふわしてます)。

こちらの記事が詳しいです。

[Node.jsとはなにか？なぜみんな使っているのか？](https://qiita.com/non_cal/items/a8fee0b7ad96e67713eb)

## Node.jsの印象
学習前：JavaをJSで書けるようになったみたいな感じ？

学習後："所感"とか言ってる場合じゃねえ

## つまり
私にはあまりにも複雑なため、まとめます。
自分が後に振り替える際、理解しやすいよう最適化しています(重複している内容もあり、全体的には冗長になっています)。

## 準備編

Node.jsを使ってwebアプりを開発するためのフレームワーク、Expressを使用します。

### expressのインストール
~~~terminal:terminal
$ npm install express
~~~

### フォルダ構成
~~~
[app]
　┗ app.js
　┗ [views]
    　┗ XXX.ejs…
　┗[public]
    　┗[css]
         ┗style.css
    　┗[images]
         ┗XXX.png…
         

※おそらくスタンダードな構成※
~~~

### パッケージの読み込み & 使用するための準備
~~~javascript:app.js
// expressの読み込み
const express = require('express');

//expressを使用するための準備
const app = express();
~~~

### サーバー立て
~~~javascript:app.js
// localhost:3000でサーバーを起動
app.listen(3000);
~~~

### アクセス時の処理(GET)
~~~javascript:app.js
// req…リクエスト情報(クライアント→サーバー)
// res…レスポンス情報(サーバー→クライアント)
app.get('/ルート', (req,res) => {

    // [/ディレクトリ名]にアクセスしたときの処理

    // ページ遷移
    res.render('XXX.ejs');

    }
)
~~~

## EJS編

### CSSの適用
~~~javascript:app.js
//CSSフォルダの階層を指定
app.use(express.static('public'));
~~~
~~~html:ejs
<!--上記で指定した階層を起点としたパスを指定-->
<link rel="stylesheet"
href="/css/style.css">

<!--画像も同様-->
<img src="/images/XXX.png">
~~~

### 基礎
~~~javascript:ejs
//<% 定義文 %>　←変数の定義
<p><% const 変数名; %></p>

//<%= 変数名 %> ←値を表示
<p><%= 変数名 %></p>
~~~

### forEachで配列を一覧表示
~~~javascript:ejs
// 配列の要素(オブジェクト)をアロー関数の引数に渡して
// 順繰りに処理していくイメージ
<% 配列名.forEach((変数名)=>{ %>
    <li>
        <span><%= 変数名.プロパティ名 %></span>
    </li>
<% }); %>
~~~



## DB編
### MySQLインストール

~~~terminal:terminal
$ npm install mysql
~~~
### MySQL接続

~~~javascript:app.js
// パッケージ読み込み
const mysql = require('mysql');

// 接続情報を定数connectionに代入
const connection = mysql.createConnection({
    host: 'localhost', // ホスト名
    user: 'root',      // ユーザー名
    password: '',      // パスワード
    database: 'test'   // データベース名
});
~~~

### クエリの実行
~~~javascript:app.js
connection.query(
    'クエリ',
    
    // errorにはクエリ失敗時のエラー情報が入る
    // resultsには実行結果(取得情報)が入る
    (error,results)=>{
        // クエリ実行後の処理
    }
);
~~~

### EJSに値を渡す

~~~javascript:app.js
(error,results)=>{
    res.render('XXX.ejs',{プロパティ名:results})
}
~~~

### データベース変更(post)
postリクエスト時は、入力する項目がなくてもフォームを使う。
~~~javascript:ejs
//フォームにpostを指定
<form action="/ルート"> method="post">
~~~

~~~javascript:app.js
app.post('/ルート',(req,res)=>{
    //処理内容
    connection.query(
    'クエリ',
    (error,results)=>{
        // クエリ実行後の処理
    });
});
~~~

### フォームに入力した値を受け取る
~~~javascript:ejs
    // formタグ内のinputタグでname属性を準備
    <input type="text" name="name属性">
~~~

~~~javascript:app.js
//フォームの値を受け取るために必要な定型文
app.use(express.urlencoded({extended:false}));

// req.body.name属性に入力値が入る
app.post('/ルート',(req,res)=>{
    // 例
    console.log(req.body.name属性);
});
~~~

### フォームの値をクエリに使用
~~~javascript:app.js
... connection.query(
    // ? ←フォームの値
    'INSERT INTO テーブル名 (カラム名) VALUES (?)',
    
    // ? に渡す要素(フォームの値)を指定
    [req.body.name属性値],

    (error,results)=>{
        // クエリ実行後の処理
    });
~~~


### リダイレクト
指定したURLにリクエストさせる仕組み。フォームの送信後に使うことが多い。
（正しく送信できているか、ユーザーが確認できる）
~~~javascript:app.js
res.redirect('/ルート');
~~~

## ルートパラメータ
URLの特定の位置に動的な値を埋め込むためのパラメータ。特定レコードの削除や編集などに使用する。
~~~javascript:ejs
// 例：送信先URLにDBカラムのidを含める
<form> action="/ルート/<%= id %>" method="post">
~~~

~~~javascript:app.js
// ルーティングにルートパラメータ(:パラメータ名)を指定
app.post('/ルート/:id',(req,res)=>{

    //req.params.パラメータ名で値を受け取ることも可能
    //例：レコード削除
    'DELETE FROM テーブル名 WHERE id = ?',
    [req.params.id],
    (error,results)=>{
    }
});
~~~

## デバッグ
エラー内容から予測を立ててコードを確認していく。
### エラー内容を出力
~~~javascript:app.js
...(error,results)=>{
        console.log(error);
}
~~~

## ログイン機能
ログイン機能は「ログイン画面」「ユーザー認証」「セッション管理」び3つから成立している。

### ユーザー認証(本人確認)
同じ名前のルートが2つ存在していても、getメソッドとpostメソッドで異なるルーティングとして扱われる。

~~~javascript:ejs
//フォームを用意
<form action="/login" method="post">
    <input type="text" name="email">
    <input type="password" name="password">
~~~
~~~javascript:app.js
app.post('/login',(req,res)=>{
    //送信されたemailを代入
    const email = req.body.email;

    connection.query(
        // 一致するemailを持つユーザー情報を取得
        'SELECT * FROM users WHERE email = ?',
        [email],
        
        (error,results)=>{
        // ユーザー情報が1つ以上ある場合のみ認証処理に進む
            if(results.length > 0){
            
            //認証処理
                if(req.body.password === results(0).password){
                console.log('認証に成功しました');
                res.redirect('/ルート')
                }else{
                console.log('認証に失敗しました');
                res.redirect('/ルート')
                }   
            }else{
            res.redirect('/login');
            }
        
        }
    )
})
~~~
### セッション管理
必要なパッケージをインストール
~~~terminal
$ npm install express-session
~~~
~~~javascript:app.js
// パッケージを使うための準備
const session = require('express-session');

app.use(
  session({
    secret: 'my_secret_key',
    resave: false,
    saveUninitialized: false,
  })
);
~~~

~~~javascript:app.js
app.post('/login',(req,res)=>{
    ...
    // ユーザーIDをセッション情報に保存
    request.session.userId = results[0].id
})

~~~

### app.use関数
全てのリクエストに対応。ルーティングの一番上に記述することで、毎回実行することができる。「req」「res」「next」の3つの引数を受け取ることができる。

「next」は関数(next();)として使用。実行すると、リクエストに一致する次の処理に移る。
~~~javascript:app.js
app.use((req,res,next)=>{

    //処理
    next();
});
~~~

### app.useからejsに値を渡す
~~~javascript:app.js
app.use((req,res,next)=>{

    // res.localsオブジェクトを使用
    res.locals.プロパティ名 = 値;
});
~~~

~~~javascript:ejs
// res不要
<p><%= locals.プロパティ名%></p>
~~~

### ログアウト
セッション情報のデータを削除する。
~~~javascript:app.js
app.get('/logout',(req,res)=>{

    // errorには失敗したときの情報が入る
    req.session.destroy( (error)=>{
    
        //実行後の処理(リダイレクトなど)
    });
});
~~~

### ログイン状態の判別

~~~javascript:app.js
if(req.session.userId === undefined){

    // isLoggedInプロパティを用意
    // ログインしていないとき
    res.locals.isLoggedIn = false;
    }else{
    
    //ログインしているとき
    res.locals.isLoggedIn = true;
    }

~~~

~~~javascript:ejs
// 例:ログイン時にログアウトリンクを表示
<% if(locals.isLoggedIn){ %>
    <li><a href="/logout">ログアウト</li>
<% } else { %>
    <li><a href="/login">ログイン</li>
<% } %>
~~~

## 新規登録機能

~~~javascript:ejs
// フォームを準備する(name属性を忘れずに)
<form action="/signup" method="post">
    <input type="text" name="username">
    <input type="text" name="email">
    <input type="password" name="password">
</form>
~~~

~~~javascript:app.js
app.post('/signup',

    // ■■フォームエラーチェック用のミドルウェア関数■■
    (req,res,next)=>{
    
        // フォームの値を定数に代入
        const username = req.body.username;
        const email = req.body.email;
        const password = req.body.password;
    
        // エラーメッセージ用の配列を用意
        const errors = [];
    
        // 入力値が空の場合
        // コンソールにエラーメッセージ表示
        if(username === ''){
            errors.push('ユーザー名が空です');
        }
        if(email === ''){
            errors.push('メールアドレスが空です');
        }
        if(password === ''){
            errors.push('パスワードが空です');
        }
        console.log(errors);
    
        // エラーがある場合の処理
        if(errors.length > 0){
            // エラーメッセージを画面に渡す
            res.render('signup.ejs',{errors:errors});
        } else {
        
        // エラーがない場合、次の処理へ進む
            next();
        }
    },
    
        // ■■重複チェック用のミドルウェア関数■■
     (req,res,next)=>{
        
            // フォームの値を定数に代入
            const email = req.body.email

            // 配列errorsを定義
            const errors = [];
    
            // 検索による重複チェック
            connection.query(
                'SELECT * FROM users WHERE email = ?',
                [email],
                (error,results)=>{
    
                    // エラーがある場合
                    // エラーメッセージを追加し、画面に渡す
                    if(results.length > 0){
                        errors.push('ユーザー登録に失敗しました');
                        res.render('signup.ejs',{errors:errors});
                    } else {
    
                        // エラーがない場合、次の処理へ進む
                        next();
                    }
                
                }
            )
     },

    // ■■DB追加処理用のミドルウェア関数■■
    (req,res)=>{

        // フォームの値を定数に代入
        const username = req.body.username;
        const email = req.body.email;
        const password = req.body.password;
        
        connection.query(
            'INSERT TO テーブル名 (username,email,password) VALUES (?,?,?)',
            [username,email,password],
            (error,results)=>{
    
            // 登録と同時にログインする処理
            // INSERTが完了すると、自動的にresults.insertIdにidが代入される
            req.session.userId = results.insertId;
            req.session.username = username;
            
            // 登録後の遷移先
                res.redirect('/ルート');
            });
    });
~~~

~~~javascript:ejs
// 渡されたエラーメッセージを表示
// ※この実装により、getでの描画にも配列errorsを引数として渡すことが必須に
<% if(errors.length > 0) { %>
    <ul>
        <% errors.forEach(error => { %>
            <li><%= error %></li>
        <% }); %>
    </ul>
<% } %>
~~~

## bcrypt
パスワードなどの重要な情報をハッシュ化するもの。

### インストール
~~~terminal:terminal
$ npm install bcrypt
~~~

~~~javascript:app.js
// パッケージを使用する準備
const bcrypt = require('bcrypt');
~~~

### パスワードをハッシュ化
~~~javascript:app.js
// 新規登録のミドルウェア関数内でhashメソッドを使用
// 第一引数：パスワード
// 第二引数：パスワードの強さ
// 第三引数：error情報,ハッシュ化されたパスワード
bcrypt.hash(password,10,(error,hash)=>{
    'INSERT INTO テーブル名 (username,email,password) VALUES (?,?,?)',
    // ハッシュ化したパスワードをpasswordカラムに追加
    [username,email,hash],
});
~~~

### ハッシュ化されたパスワードでログイン
~~~javascript:app.js
// 普通の文字列のパスワード
const plain = req.body.password;

// ハッシュ化されたパスワード
const hash = results[0].password;

// compareメソッドで比較(isEqualに比較結果が入る)
bcrypt.compare(plain,hash,(error,isEqual)=>{

    // 一致している場合、ログイン処理を行う
    if(isEqual){
        req.session.userId = results[0].id;
        req.session.username = results[0].username;
        res.redirect('/list');
    } else {
        // 一致していない場合の処理
        res.redirect('/login');
    }
});
~~~

### 

## include
ヘッダーやフッターなどを一つのファイルで管理するパッケージ。





~~~javascript:ejs
// 拡張子は不要
// 呼び出したい箇所に記述
<%- include('ファイル名'); %>
~~~



## まとめ
少しでも身につけばと、処理の流れを反すうしながら手打ち入力でまとめました。まとめる前よりは格段に理解度が増しましたが、まだまだ不十分な状態だと思います。ですが、ここにまとめたことは今後の自分にとって絶対に役立つと思います。

なお、レッスン中にいちばん多かったミスは、関数の閉じカッコエラーと、redirectの遷移先の記述方法(ファイル名を書くところをルートで記述)でした。

今後も継続して学習を続けていきたいと思います。

[Progate](https://prog-8.com/)