Node.jsで会員登録システムを導入しよう

osamu38 edited this page May 8, 2018 · 97 revisions

Node.jsで会員登録システムを導入しよう

目次

  • 会員登録システムを知ろう
  • データベースを会員登録用に変更しよう
  • 新規登録機能を実装しよう
  • ログイン機能を実装しよう
  • ログインしたユーザーを表示させてみよう
  • ログアウト機能を実装しよう

会員登録システムを知ろう

ついにきましたね!会員登録!

会員登録はそこまで複雑なシステムではありません。ただセッションという機能は新しく覚える必要があります。

セッションはサーバーサイドで値を保持できる機能です。

クライアントのJavaScriptを使ってる場合、ページを超えての値の保持はできません。ですがサーバーサイドではセッションを使うことによって可能になります。

つまりセッションに値が保持されている場合はログイン状態。ない場合は非ログイン状態と判断することができます。

処理の流れは以下のようになります。

ページ HTML Node.js データベース
新規登録 ユーザー情報を入力しPOST
新規登録 POSTされたデータをデータベースになげる
新規登録 データベースにユーザー情報を追加
新規登録 ログインページにリダイレクト
ログイン ユーザー情報を入力してPOST
ログイン POSTされたデータをデータベースになげる
ログイン データベースにユーザー情報があるか検索
ログイン ある場合はセッションにユーザーIDを保存
ログイン トップにリダイレクト
トップ セッションにあるユーザーIDをデータベースになげる①
トップ データベースにユーザー情報があるか検索②
トップ ある場合はViewにユーザー情報を渡す③
トップ ユーザー情報をViewに表示④

このあとは違うページに遷移してもセッションにユーザーIDがあるので、①〜④の流れで動きます。

データベースを会員登録用に変更しよう

会員登録システムを実装する上でユーザーの情報を保持する必要があるため、usersテーブルが必要になります。またそれに伴ってboardsテーブルとmessagesテーブルにuser_idを紐付けてあげましょう。

users

カラム 長さ 符号ナシ ゼロフィル バイナリ ヌル許可 キー デフォルト 追加情報
user_id INT 11 false false false false PRI auto_increment
user_name VARCHAR 255 false false false false None
email VARCHAR 255 false false false false None
password VARCHAR 255 false false false false None
created_at DATETIME false false false false None

boards(user_idを追加)

カラム 長さ 符号ナシ ゼロフィル バイナリ ヌル許可 キー デフォルト 追加情報
user_id INT 11 false false false false None

messages(user_idを追加)

カラム 長さ 符号ナシ ゼロフィル バイナリ ヌル許可 キー デフォルト 追加情報
user_id INT 11 false false false false None

新規登録機能を実装しよう

ではさっそく新規登録機能を実装しましょう。

app.jsを以下のように書き換えます。

// 中略
var boards = require('./routes/boards');
var register = require('./routes/register'); // 追加

var app = express();
// 中略



// 中略
app.use('/boards', boards);
app.use('/register', register); // 追加

// catch 404 and forward to error handler
app.use(function(req, res, next) {
// 中略

routes/register.jsを作成します。

var express = require('express');
var router = express.Router();
var moment = require('moment');
var connection = require('../mysqlConnection');

router.get('/', function(req, res, next) {
  res.render('register', {
    title: '新規会員登録'
  });
});

router.post('/', function(req, res, next) {
  var userName = req.body.user_name;
  var email = req.body.email;
  var password = req.body.password;
  var createdAt = moment().format('YYYY-MM-DD HH:mm:ss');
  var query = 'INSERT INTO users (user_name, email, password, created_at) VALUES ("' + userName + '", ' + '"' + email + '", ' + '"' + password + '", ' + '"' + createdAt + '")';
  connection.query(query, function(err, rows) {
    res.redirect('/login');
  });
});

module.exports = router;

ここも特別なことをしていませんね。最後に/loginにリダイレクトしていますが、まだ作っていないのでエラー画面が出ると思いますが、気にしないでください。

このままの処理でも大丈夫のように思えるのですが、同じメールアドレスで複数のアカウントを作ることが可能になってしまいます。そのため同じメールアドレスがある場合に登録できない処理を追加しましょう。

routes/register.jsを以下のように書き換えます。

// 中略
router.post('/', function(req, res, next) {
  var userName = req.body.user_name;
  var email = req.body.email;
  var password = req.body.password;
  var createdAt = moment().format('YYYY-MM-DD HH:mm:ss');
  var emailExistsQuery = 'SELECT * FROM users WHERE email = "' + email + '" LIMIT 1'; // 追加
  var registerQuery = 'INSERT INTO users (user_name, email, password, created_at) VALUES ("' + userName + '", ' + '"' + email + '", ' + '"' + password + '", ' + '"' + createdAt + '")'; // 変更
  connection.query(emailExistsQuery, function(err, email) {
    var emailExists = email.length;
    if (emailExists) {
      res.render('register', {
        title: '新規会員登録',
        emailExists: '既に登録されているメールアドレスです'
      });
    } else {
      connection.query(registerQuery, function(err, rows) {
        res.redirect('/login');
      });
    }
  });
});
// 中略

var emailExistsQuery = 'SELECT * FROM users WHERE email = "' + email + '" LIMIT 1';

これはPOSTされたメールアドレスと一致するユーザー情報を1件返すクエリーです。

そして次の行でvar emailExists = email.length;とありますが、emailの配列の要素がある(既に同じメールアドレスが存在する)場合にemailExiststrueになり、そうでない(同じメールアドレスが存在しない)場合はfalseになります。

つまりこの下のif文は

既に同じメールアドレスが存在する場合

      res.render('register', {
        title: '新規会員登録',
        emailExists: '既に登録されているメールアドレスです'
      });

同じメールアドレスが存在しない場合

      connection.query(registerQuery, function(err, rows) {
        res.redirect('/login');
      });

といったように処理を分けています。

これでメールアドレスの重複チェックが完了しました。ではViewファイルの実装をやりましょう。

views/register.ejsを作成します。

<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="format-detection" content="telephone=no">
<link rel='stylesheet' href='/stylesheets/style.css'>
</head>
<body>
<div class="wrapper">
    <p class="main-title"><%= title %></p>
    <form action="/register" method="post" class="board-form">
        <span class="label">ユーザー名</span><input type="text" name="user_name" class="input" required><br>
        <br>
        <span class="label">Eメール</span><input type="email" name="email" class="input" required><br>
        <br>
        <span class="label">パスワード</span><input type="password" name="password" class="input" required><br>
        <br>
        <button type="submit" class="submit">新規会員登録</button>
    </form>
    <% if (typeof emailExists !== 'undefined') { %>
        <p class="error"><%= emailExists %></p>
    <% } %>
    <a href="/" class="btn">トップへもどる</a>
</div>
</body>
</html>

View画面もこれで完了。ではさっそく/registerにアクセスして、ユーザー登録してみましょう。

無事ユーザー登録完了できましたね!

では次に既に登録しているメールアドレスと同じアドレスで登録してみましょう。

うんうん。警告がちゃんとされていますね!これで会員登録の実装は完了です。この調子でログイン機能を実装しましょう。

ログイン機能を実装しよう

ログイン機能を実装する前にNode.jsでセッションを利用するためのmodule、express-sessionを導入しましょう。

$ npm install express-session --save

app.jsを以下のように書き換えます。

// 中略
var bodyParser = require('body-parser');
var session = require('express-session'); // 追加

var routes = require('./routes/index');
var users = require('./routes/users');
var boards = require('./routes/boards');
var register = require('./routes/register');
var login = require('./routes/login'); // 追加

var app = express();
// 中略



// 中略
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true
}));

app.use('/', routes);
app.use('/users', users);
app.use('/boards', boards);
app.use('/register', register);
app.use('/login', login); // 追加

// catch 404 and forward to error handler
app.use(function(req, res, next) {
// 中略

ここではセッションが使えるようにapp.jsに色々と設定していますが(ついでにログインページのルーティングも設定しています)、何を設定しているかは今説明しても少し難しいので、特に説明しません。

知りたい人はここを見てください。

routes/login.jsを作成します。

var express = require('express');
var router = express.Router();
var connection = require('../mysqlConnection');

router.get('/', function(req, res, next) {
  if (req.session.user_id) {
    res.redirect('/');
  } else {
    res.render('login', {
      title: 'ログイン'
    });
  }
});

router.post('/', function(req, res, next) {
  var email = req.body.email;
  var password = req.body.password;
  var query = 'SELECT user_id FROM users WHERE email = "' + email + '" AND password = "' + password + '" LIMIT 1';
  connection.query(query, function(err, rows) {
    var userId = rows.length? rows[0].user_id: false;
    if (userId) {
      req.session.user_id = userId;
      res.redirect('/');
    } else {
      res.render('login', {
        title: 'ログイン',
        noUser: 'メールアドレスとパスワードが一致するユーザーはいません'
      });
    }
  });
});

module.exports = router;

まず先にPOSTの処理から説明していきます。

var query = 'SELECT user_id FROM users WHERE email = "' + email + '" AND password = "' + password + '" LIMIT 1';

今回のqueryはEメールとパスワードが一致しているユーザーを1件だけ返すSQLです。

var userId = rows.length? rows[0].user_id: false;

userIdという変数にユーザーのユーザーのデータが返ってきた場合のみuser_idを代入しており、データが返ってこない(該当するユーザーが存在しない)場合はfalseを代入しています。

なので次のif文の条件式では

該当するユーザーがいる場合

      req.session.user_id = userId;
      res.redirect('/');

ここでは'/'にリダイレクトする前にセッションにユーザーIDを保存しています。←超重要

該当するユーザーがいない場合

      res.render('login', {
        title: 'ログイン',
        noUser: 'メールアドレスとパスワードが一致するユーザーはいません'
      });

ここでは、Viewファイルに対してエラー用の文言を渡しています。

次にGETの処理を説明します。

ログインページは既にログインしているユーザーが来るページではありません。そのため既にログインしている(req.session.user_idがある)ユーザーは/にリダイレクトするような処理を入れています。

ログインするためのロジックはこれで完了したので、次にViewの実装をしましょう。

views/login.ejsを作成します。

<!DOCTYPE html>
<html>
<head>
<title><% title %></title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="format-detection" content="telephone=no">
<link rel='stylesheet' href='/stylesheets/style.css'>
</head>
<body>
<div class="wrapper">
    <p class="main-title"><%= title %></p>
    <form action="/login" method="post" class="board-form">
        <span class="label">Eメール</span><input type="email" name="email" class="input" required><br>
        <br>
        <span class="label">パスワード</span><input type="password" name="password" class="input" required><br>
        <br>
        <button type="submit" class="submit">ログイン</button>
    </form>
    <% if (typeof noUser !== 'undefined') { %>
        <p class="error"><%= noUser %></p>
    <% } %>
    <a href="/" class="btn">トップへもどる</a>
</div>
</body>
</html>

基本的にはいつも通りですが、if文の書き方が少しだけ特殊です。

まぁこんな風に書けばいいのかーぐらいに思っておいてください。

ではさっそくViewができたのでlocalhost:3000/loginにアクセスしてみましょう。

画面の方は大丈夫そうですね!

ではさっそくログインしてみます。

うまく/にリダイレクトされていますね。この状態で/loginにアクセスしても/にリダイレクトされると思うので試してみてください。

では次にメールアドレスを異なったものを入力してPOSTしてみます。

うんうん。エラー用の文言がちゃんと表示されているのがわかると思います。

ログインしたユーザーを表示させてみよう

ここまで確認ができたのでトップページが表示される前にuser_idをもとにユーザー名をトップに表示させる処理を導入したいと思います。

app.jsを以下のように書き換えます。

// 中略
var login = require('./routes/login');

var setUser = require('./setUser'); // 追加

var app = express();
// 中略



// 中略
}));

app.use('/', setUser, routes); // 変更
app.use('/users', users);
app.use('/boards', setUser, boards); // 変更
app.use('/register', register);
// 中略

app.use('/', setUser, routes);

routesの前にsetUserが渡されていますね。こうすることによってroutesの処理が実行される前にsetUserを実行します。

boardsでも同様にsetUserを渡しておきます。

setUser.jsを作成します。

var connection = require('./mysqlConnection');

module.exports = function(req, res, next) {
  var userId = req.session.user_id;
  if (userId) {
    var query = 'SELECT user_id, user_name FROM users WHERE user_id = ' + userId;
    connection.query(query, function(err, rows) {
      if (!err) {
        res.locals.user = rows.length? rows[0]: false;
      }
    });
  }
  next();
};

ここではセッションにuser_idがセットされているか確認します。

セッションにuser_idがある場合はuser_idをもとにユーザーの情報を取得してViewファイルに対して値を渡します。

ここでres.locals.user = ~という処理がありますが、これは今までres.render('index', { user: user });のようにViewファイルに値を渡していましたが、実はこのように渡すことも可能です。

セッションにuser_idがない場合は何もしません。

最後にnext();で次の処理(routesboards)にいきます。

views/index.ejsを以下のように書き換えます。

<!-- 中略 -->
    </ul>
    <% if (typeof user !== 'undefined') { %>
        <span class="login-user"><%= user.user_name %>さんとしてログインしています</span>
    <% } %>
</div>
<!-- 中略 -->

views/board.ejsを以下のように書き換えます。

<!-- 中略 -->
    <a href="/" class="btn">トップへもどる</a>
    <% if (typeof user !== 'undefined') { %>
        <span class="login-user"><%= user.user_name %>さんとしてログインしています</span>
    <% } %>
</div>
<!-- 中略 -->

ではViewファイルも対応したので、ログインした状態で//boardsにアクセスしてみましょう。

右下にログインしてるユーザーが表示されていますね。今までではログインしている状態を見た目でわからなかったですが、このように表示されているとわかりやすいですね。

ログアウト機能を実装しよう

ログアウト機能は非常に簡単です。ただセッションを破棄すればいいだけです。

app.jsを以下のように書き換えます。

// 中略
var login = require('./routes/login');
var logout = require('./routes/logout'); // 追加

var setUser = require('./setUser');
// 中略



// 中略
app.use('/login', login);
app.use('/logout', logout); // 追加

// catch 404 and forward to error handler
app.use(function(req, res, next) {
// 中略

routes/logout.jsを作成します。

var express = require('express');
var router = express.Router();

router.get('/', function(req, res, next) {
  req.session.destroy();
  res.redirect('/login');
});

module.exports = router;

ロジックはこれだけです。/logoutにアクセスするとreq.session.destroy();でセッションを破棄するので、ログアウトされている状態になります。

このようにして会員登録機能は実装されているというのがわかったでしょうか?ちなみにサーバーを再起動するとセッションが消えてしまうので、注意しましょう!

前のページ:Node.jsで詳細ページを作ってみよう

次のページ:ユーザー情報を投稿に紐付けてみよう

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.