node.jsとjQueryを使ってユーザー新規登録モーダルとログインモーダルを作ってみた

画面遷移をせずにユーザー新規登録機能を作ってみました。
そこまで大した出来ではないです。
（実は理想としていたものより少々乖離しています。）

node.jsやjQueryの導入はこちらでは省いています。
また、新規登録、ログインモーダル以外の要素もありません。ご了承ください。

node.jsのバージョンは　`v14.15.4` です。
また、MySQLを使用しています。

# 1.必要なものをインストール

```:ターミナル
$ npm install express  //expressをインストールします
$ npm install mysql  //mysqlと接続できます
$ npm install express-session   //登録機能です
$ npm install bcrypt  //パスワードをハッシュ化してくれる機能です
```

これでインストールは以上です。

今回、ユーザー新規登録には、
`username` , `email` , `password`
の情報が必須となる設定にしています。

# 2.ルーティングのコードを書いていく

ディレクトリ構造は以下のようになっております
(node_modulesなどは省略しています。）

registation_app/
├ public/
│　　├ css/
│　　│　　└style.css/
│　　├ script.js/
│　　└ validate.js/
├ views/
│　　├ log_in.ejs/
│　　├ sign_up.ejs/
│　　├ top.ejs/
│　　└ uniq_error.ejs/
└ app.js/


```javascript:app.js

// インストールしたものを適用させます
const express = require('express');
const mysql = require('mysql');
const session = require('express-session');
const app = express();
const bcrypt = require('bcrypt');

app.use(express.static('public'));
app.use(express.urlencoded({extended: false}));

// DBと接続します
const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'パスワードを入力',
  database: 'DB名を入力'
});

//　トップ画面のルーティング
app.get('/', (req, res) => {
  res.render('top.ejs');
});

// 重複するメールアドレスがある場合の画面遷移
app.get('/uniq_error',(req,res) =>{
  res.render('uniq_error.ejs');
});

// 新規登録のルーティング
app.post('/sign_up',
(req, res, next) => {
  const email = req.body.email;
  
  connection.query(
    'SELECT * FROM users WHERE email = ?',
    [email],
    (error, results) => {
      if (results.length > 0) {
        res.render('uniq_error.ejs');
      } else {
        next();
      }
    }
  );
  
},

(req,res) => {
  bcrypt.hash(password,10,(error,hash) => {
    connection.query(
      'INSERT INTO users (username, email, password) VALUES (?, ?, ?)',
      [username, email, hash],
      (error, results) => {
        req.session.userId = results.insertId;
        req.session.username = username;
        res.redirect('/');
      }
    );
  });
});

// ログインのルーティング
app.post('/log_in', (req, res) => {
  const email = req.body.email;
  connection.query(
    'SELECT * FROM users WHERE email = ?',
    [email],
    (error, results) => {
      if (results.length > 0) {
        const plain = req.body.password;
        const hash = results[0].password;
        bcrypt.compare(plain,hash,(error,isEqual) => {
          if(isEqual){
          req.session.userId = results[0].id;
            req.session.username = results[0].username;
            res.redirect('/');
          } else {
            res.redirect('/');
          }
        });
      } else {
        res.redirect('/');
      }
    }
  );
});

// ログアウトのルーティング
app.get('/log_out', (req, res) => {
  req.session.destroy(error => {
    res.redirect('/');
  });
});

// ローカルホスト3000に接続
app.listen(3000);
});
```

# 3.トップ画面などのコーディング

```html:top.ejs
<!DOCTYPE html>
<html>
  <head>
<meta charset="utf-8">
<title>registration_APP</title>
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script type="text/javascript" src="script.js"></script>
<script type="text/javascript" src="validate.js"></script>
  </head>
  <body>
<header>
  <div class="header-wrapper" >
    <a href="/" class="header-logo-left">registration_APP</a>
    <% if (locals.isLoggedIn) { %>
      <a class="header-logo-right" href="/log_out">ログアウト</a>
    <% } else { %>
    <div>
      <a class="header-logo-right login-show">ログイン</a>
      <a class="header-logo-right signup-show">新規登録</a>
      <% } %>
  </div>
  </div>
</header>
    <%- include('sign_up'); %>
    <%- include('log_in'); %>
  </body>
</html>

```
`<%- include('sign_up'); %>`で新規登録モーダル
`<%- include('log_in'); %>`でログインモーダルを呼び出します。

ある特定のページでのみモーダルを出現させるのであればこの記述をする必要はありませんが、
複数のページでモーダルを出したい場合、このようにすると使いまわせるので便利です。

```html:sign_up.ejs
//新規登録モーダル

<div class="signup-modal-wrapper" id="signup-modal">
  <div class="modal">
    <div id="signup-close-modal">
      <i class="fa fa-2x fa-times"></i>
    </div>
    <div id="signup-form">
      <h2>新規登録</h2>
      <form action="/sign_up" method="post" id="sign_up-form">
        <p class="error-message" id="sign_up-username-error-message"></p>
        <input class="form-control" type="text" placeholder="ユーザー名" name="username" id="sign_up-username">
        <p class="error-message" id="sign_up-email-error-message"></p>
        <input class="form-control" type="text" placeholder="メールアドレス" name="email" id="sign_up-email">
        <p class="error-message" id="sign_up-password-error-message"></p>
        <input class="form-control" type="password" placeholder="パスワード" name="password" id="sign_up-password">
        <input id="submit-btn" type="submit" value="登録する">
      </form>
    </div>
  </div>
</div>
```

```html:log_in.ejs
//ログインモーダル

<div class="login-modal-wrapper" id="login-modal">
  <div class="modal">
    <div id="login-close-modal">
      <i class="fa fa-2x fa-times"></i>
    </div>
    <div id="login-form">
      <h2>ログイン</h2>
      <form action="/log_in" method="post">
        <input class="form-control" type="text" placeholder="メールアドレス" name="email">
        <input class="form-control" type="password" placeholder="パスワード" name="password">
        <input id="login-btn" type="submit" value="ログインする">
      </form>
    </div>
  </div>
</div>
```

これでトップ画面で二種類のモーダルをだせるようになりました。
（この段階では二種類のモーダルは現れた状態になっています。）
この後CSSを使ってモーダルを隠し、jQueryで表示させるアクションを作成します。

# 4.CSSをコーディング
CSSは全て書くととても長くなるので、必要最低限のところだけ
モーダルの大きさや文字の大きさ、背景色などはお好みで。

```css:style.css

/* ヘッダーの『新規登録』『ログイン』部分 */
.header-logo-right {
  display: inline;
}

.header-logo-right:hover {
  cursor: pointer;
}


/* モーダル部分 */
.signup-modal-wrapper,.login-modal-wrapper{
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 100;
  display: none;
}

.modal {
  position: absolute;
  top: 20%;
  left: 34%;
  background-color: #e6ecf0;
  padding: 20px 0 40px;
  border-radius: 10px;
  width: 450px;
  height: auto;
  text-align: center;
}

.fa-times {
  position: absolute;
  top: 12px;
  right: 12px;
  color: rgba(128, 128, 128, 0.46);
  cursor: pointer;
}

#signup-form,#login-form{
  width: 100%;
}
```

これで新規登録、ログインボタンにカーソルを合わせるとポインタが変わります
さらにモーダル部分は隠れました。
それではjQueryを使ってモーダルが現れるように処理しましょう。

# 5.Jqueryをコーディング

```javascript:script.js
$(function(){
  $('.signup-show').click(function(){
    $('#signup-modal').fadeIn();
  });
  
  $('#signup-close-modal').click(function(){
    $('#signup-modal').fadeOut();
  });
});

$(function(){
  $('.login-show').click(function(){
    $('#login-modal').fadeIn();
  });
  
  $('#login-close-modal').click(function(){
    $('#login-modal').fadeOut();
  });
});
```

これで、『新規登録』を押すと『新規登録モーダル』が
『ログイン』を押すと『ログインモーダル』が出るようになりました。


これで、新規登録並びにログインモーダルが完成です。

・・・と言いたい所さんですが、このままだと空白でも登録ができてしまいます。
と言うわけでバリデーションをかけていきましょう

# 6.バリデーションをかける
今回はjQuery側でバリデーションチェックをします。
挙動としては、入力に問題がある場合、送信はできず問題箇所にエラー文が出るようにします。

```javascript:validate.js
$(function() {
  $('#sign_up-form').submit(function(){
    var usernameValue = $('#sign_up-username').val();
    var emailValue = $('#sign_up-email').val();
    var passwordValue = $('#sign_up-password').val();
    var errorCount = 0;

    if(usernameValue === ""){
      $('#sign_up-username-error-message').text('ニックネームを入力してください');
      errorCount += 1;
    } else {
      $('#sign_up-username-error-message').text('');
    }

    if(emailValue === ""){
      $('#sign_up-email-error-message').text('メールアドレスを入力してください');
      errorCount += 1;
    } else if(!emailValue.match(/^[a-zA-Z0-9_.+-]+@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$/)){
      $('#sign_up-email-error-message').text('メールアドレスが正しくありません');
      errorCount += 1;
    } else {
      $('#sign_up-email-error-message').text('');
    }

    if(passwordValue === ""){
      $('#sign_up-password-error-message').text('パスワードを入力してください');
      errorCount += 1;
    } else if(!passwordValue.match(/^(?=.*[a-zA-Z])(?=.*[0-9])[0-9a-zA-Z]{6,}$/) ){
      $('#sign_up-password-error-message').text('パスワードは半角英数字６文字以上が必要です');
      errorCount += 1;
    } else {
      $('#sign_up-password-error-message').text('');
    }

    if(errorCount !== 0){
      return false;
    }
  });
});
```

これでバリデーションチェックができます。
解説としては`errorCount`を０と最初に定義し、問題箇所１つにつき`errorCount`が１上昇していきます。
そして最後、`errorCountが`０ではない場合送信できないように設定してあります。
そして全てのチェックが通れば`errorCount`は０なので送信し、ユーザー登録できる。
と言う仕掛けになっております。

これにて完成です。
と思ったら間違えです。
このままだと同じメールアドレスが登録できてしまいます。

ここから先が私の作りたかったモーダルとの理想と実力の無さが出た場所でした。

# 7.実現したかった内容

ここで同じアドレスが登録されている場合にも`errorCount`を１上昇させる処理をしたかったのですが、
ajax通信が何をやってもうまくいきませんでした。
jQuery単体では厳しいようで、PHPが必要なようです。
(jQueryだけでどうにかならないか色々勉強中です。)

仕方ないので、node.js側で同じメールアドレスがある場合は弾き、アラートを出す妥協案にしましたが、これまた失敗。
理由はnode.jsではalertメソッドが使えないようです。

# 8.苦し紛れに出した自分なりの答え

アラートが出せずにしばらく考え、出した結論が
『既に登録されているメールアドレスがあるため登録できませんでした』

と書かれたページへ遷移することでした。
コードで言うと、最初のapp.jsに既に記述してありますが、再び記述すると

```javascript;app.js
app.post('/sign_up',
(req, res, next) => {
  const email = req.body.email;
  
  connection.query(
    'SELECT * FROM users WHERE email = ?',
    [email],
    (error, results) => {
      if (results.length > 0) {
        res.render('uniq_error.ejs');
      } else {
        next();
      }
    }
  );
```
こちらの部分になります。
これで重複するメールアドレスの場合は`uniq_error.ejs`に遷移します。

ちなみに`uniq_error.ejs`には
『既に登録されているメールアドレスがあるため登録できませんでした』
の文字しかありません。

# 9.終わりに
バリデーションチェックまでは苦戦しながらも進められたのですが、メールアドレスの重複チェックで躓き、数十時間もがき苦しみました。

まだまだ理解が足りない証拠ですね。
PHPの知識もそのうち入れたいと思います。