Node.jsで会員登録システムを導入しよう - osamu38/node-express-curriculum GitHub Wiki
- 会員登録システムを知ろう
- データベースを会員登録用に変更しよう
- 新規登録機能を実装しよう
- ログイン機能を実装しよう
- ログインしたユーザーを表示させてみよう
- ログアウト機能を実装しよう
ついにきましたね!会員登録!
会員登録はそこまで複雑なシステムではありません。ただセッションという機能は新しく覚える必要があります。
セッションはサーバーサイドで値を保持できる機能です。
クライアントのJavaScriptを使ってる場合、ページを超えての値の保持はできません。ですがサーバーサイドではセッションを使うことによって可能になります。
つまりセッションに値が保持されている場合はログイン状態。ない場合は非ログイン状態と判断することができます。
処理の流れは以下のようになります。
ページ | HTML | Node.js | データベース |
---|---|---|---|
新規登録 | ユーザー情報を入力しPOST | ||
新規登録 | POSTされたデータをデータベースになげる | ||
新規登録 | データベースにユーザー情報を追加 | ||
新規登録 | ログインページにリダイレクト | ||
ログイン | ユーザー情報を入力してPOST | ||
ログイン | POSTされたデータをデータベースになげる | ||
ログイン | データベースにユーザー情報があるか検索 | ||
ログイン | ある場合はセッションにユーザーIDを保存 | ||
ログイン | トップにリダイレクト | ||
トップ | セッションにあるユーザーIDをデータベースになげる① | ||
トップ | データベースにユーザー情報があるか検索② | ||
トップ | ある場合はViewにユーザー情報を渡す③ | ||
トップ | ユーザー情報をViewに表示④ |
このあとは違うページに遷移してもセッションにユーザーIDがあるので、①〜④の流れで動きます。
会員登録システムを実装する上でユーザーの情報を保持する必要があるため、usersテーブルが必要になります。またそれに伴ってboardsテーブルとmessagesテーブルにuser_id
を紐付けてあげましょう。
カラム | 型 | 長さ | 符号ナシ | ゼロフィル | バイナリ | ヌル許可 | キー | デフォルト | 追加情報 |
---|---|---|---|---|---|---|---|---|---|
user_id | INT | 11 | false | false | false | false | PRI | auto_increment | |
user_name | VARCHAR | 255 | false | false | false | false | None | ||
VARCHAR | 255 | false | false | false | false | None | |||
password | VARCHAR | 255 | false | false | false | false | None | ||
created_at | DATETIME | false | false | false | false | None |
カラム | 型 | 長さ | 符号ナシ | ゼロフィル | バイナリ | ヌル許可 | キー | デフォルト | 追加情報 |
---|---|---|---|---|---|---|---|---|---|
user_id | INT | 11 | false | false | false | false | None |
カラム | 型 | 長さ | 符号ナシ | ゼロフィル | バイナリ | ヌル許可 | キー | デフォルト | 追加情報 |
---|---|---|---|---|---|---|---|---|---|
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
の配列の要素がある(既に同じメールアドレスが存在する)場合にemailExists
はtrue
になり、そうでない(同じメールアドレスが存在しない)場合は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();
で次の処理(routes
やboards
)にいきます。
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で詳細ページを作ってみよう
次のページ:ユーザー情報を投稿に紐付けてみよう