前回はNode.JSでsqlite3をつかう方法を紹介しましたが、今回は簡易掲示板のようなものをつくります。
Contents
Node.JSとsqlite3で簡易掲示板もどきを作る
まずnpmでexpressとejsをインストールします。
1 2 3 |
npm install sqlite3 npm install express npm install ejs |
そしてindex.ejs、new_update.ejs、err.ejsというファイルを作成します。
index.ejs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <title>テスト</title> </head> <body> <div id = "container"> <a href="./newpost">新規投稿</a><br> <% for (let i = 0; i < rows.length; i++) { %> <% let row = rows[i] %> <div> <p> <%= row.name %> <a href="./update<%= row.id %>">更新</a> <a href="./delete<%= row.id %>">削除</a><br> <%- row.body.replace(/\n/g, '<br>') %> <%- '<br>' %> 投稿:<%- row.createtime %> 最終更新:<%- row.updatetime %> </p> </div> <% } %> </div> </body> </html> |
new_update.ejs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title><%-new_update%></title> </head> <body> <div id = "container"> <form action="<%-action%>" method="post"> <label for="name">名前</label> <input id="name" name="name" value="<%-oldname%>"> <br> <textarea id="body" name="body" cols="50" rows="10" value="a"><%-oldbody%></textarea> <br> <input type="submit" value="送信する"> </form> </div> </body> </html> |
err.ejs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <title>エラー</title> </head> <body> <div id = "container"> <%= err %> </div> </body> </html> |
ローカルサーバーを立てる
まずはローカルサーバーを立てます。これでhttp://localhost:65003/でアクセスできます。
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const sqlite3 = require("sqlite3"); const express = require('express'); const fs = require('fs'); const ejs = require('ejs'); const qs = require('querystring'); const app = express(); const port = 65003; // データベースのパスは決まっているので定数として宣言する const db_path = './test.db'; var server = app.listen(port, function() { console.log("listening at port %s", server.address().port); }); |
トップページにアクセスされたときの処理
トップページにアクセスされたらこれまでに登録されているデータを表示します。
select * from table_postsでテーブルからすべてのレコードを取得して表示させます。
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
const index_ejs = fs.readFileSync('./index.ejs','utf8'); app.get('/', (req, res) => GetResponseIndex(req, res)); // ローカル環境だと'/'だけで動いたが、サーバーにアップすると'/index.html'を書かないと // ページが存在しないというエラーになる app.get('/index.html', (req, res) => GetResponseIndex(req, res)); function GetResponseIndex(req, res){ const db = new sqlite3.Database(db_path); db.all("select * from table_posts", (error, rows) => { let content = ejs.render(index_ejs, {rows: rows}); res.writeHead(200, {'Content-Type':'text/html'}); res.write(content); res.end(); }); db.close(); } |
新しくデータを登録する処理
次に新しくデータを登録する処理を示します。トップページに./newpostへのリンクがあるので、これをクリックしたら新規投稿のページを表示させます。
投稿されたデータを更新するときも同じnew_update.ejsを使います。更新するときはすでに投稿されているデータを表示させます。新規投稿のときはなにも登録されていないので空欄ですが、名前の部分は「名無しさん」にしておきます。
index.js
1 2 3 4 5 6 7 8 9 |
const new_update_ejs = fs.readFileSync('./new_update.ejs','utf8'); app.get('/newpost', (req, res) => { let content = ejs.render(new_update_ejs, { new_update: '新規投稿' ,action: './newpost', oldname: '名無しさん', oldbody: ''}); res.writeHead(200, {'Content-Type':'text/html'}); res.write(content); res.end(); }); |
新規投稿ページの[送信する]ボタンがクリックされたらPOSTで送られたデータ処理をおこないます。長文の書き込みが行なわれると困るので、送信データのサイズは5KB以内に制限しています。制限を超えた場合は「送信データのサイズは5KB以内にしてください」と表示させるとともに、is413フラグをセットしてデータベースへの登録ができないようにします。
is413フラグがfalseの場合はデータベースにデータを登録してトップページにリダイレクトします。すると登録されたデータ、名前と投稿文、投稿時刻と更新時刻(新規投稿の場合は両者は同じ時刻になる)が一番下に表示されます。
それから書き込まれた文字のなかに<や>があるときは注意が必要です。そのままだとクロスサイトスクリプティング攻撃といってページのなかにスクリプトを埋め込むことができてしまいます。
そのスクリプトが悪質なものであった場合、悪意があるユーザーにほかのユーザーの個人情報が盗まれてしまうかもしれないのです。もっとも現状では盗めるのはユーザー名と暗号化されたパスワードだけですが、気をつけるに越したことはありません。
そこで<や>がある場合は<や>におきかえます。これだとスクリプトとしては機能しません。ただの文字として表示されるだけです。
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
const err_ejs = fs.readFileSync('./err.ejs','utf8'); app.post('/newpost', (req, res) => { let body = ''; let is413 = false; req.on('data', function(data) { body += data; var maxData = 5 * 1000; if(data.length > maxData) { res.writeHead(413); let content = ejs.render(err_ejs, { err: '送信データのサイズは5KB以内にしてください'}); res.write(content); res.end(); is413 = true; } }); req.on('end', () => { if(is413) return; let post_data = qs.parse(body); const db = new sqlite3.Database(db_path); db.run( "insert into table_posts(name, body, createtime, updatetime) values(?,?,datetime('now', '+9 hours'),datetime('now', '+9 hours'))", post_data.name.replace(/</g, "<").replace(/>/g, ">"), post_data.body.replace(/</g, "<").replace(/>/g, ">")); db.close(); res.writeHead(302, {'Location':'./'}); res.end(); }); }); |
更新の処理
次に更新の処理を示します。投稿者の名前の横に[更新 削除]というリンクがあります。urlはhttp://localhost:65003/update<id>です。/update<id>にアクセスされた場合はデータ更新用のページを表示させます。これは新規投稿のページと同じnew_updateを使っています。ページタイトルと送信ボタンをおしたらどこにPOSTリクエストされるのかが違います。そして最初に更新対象になるデータ(投稿者名と投稿文)を表示させます。
またデータベース内にidが同じものが見つからない場合(ページ内のリンクをクリックするのではなく、ユーザーが自分でurlをブラウザに入力した場合など)は「404 ページが見つからない」というエラーメッセージを表示させます。
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
app.get('/update:id', (req, res) => { let params = req.params; let query = `select * from table_posts where id = ${params.id.replace(/'/g, "''")}`; const db = new sqlite3.Database(db_path); db.get(query, (err, row) => { res.writeHead(200, {'Content-Type':'text/html'}); if(row != null){ let content = ejs.render(new_update_ejs, { new_update: '更新', action: `./update${params.id}`, oldname: row.name, oldbody: row.body}); res.write(content); } else { let content = ejs.render(err_ejs, { err: '404 ページが見つからない'}); res.write(content); } res.end(); }); db.close(); }); |
送信ボタンをおしたら同じページにPOSTされます。その処理を示します。
ここでも送信データのサイズは5KB以内に制限します。制限を超えた場合は「送信データのサイズは5KB以内にしてください」のメッセージが表示され、データベースの更新はおこなわれません。正常に処理がおこなわれるときは、新しい投稿者名、投稿文でデータベースの更新がおこなわれます。また最終更新時刻も更新処理がおこなわれた時刻に変更されます。最後にトップページにリダイレクトします。
それからここでもクロスサイトスクリプティング攻撃に対する対応が必要です。
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
app.post('/update:id', (req, res) => { let params = req.params; let body = ''; let is413 = false; req.on('data', function(data) { body += data; var maxData = 5 * 1000; if(data.length > maxData) { res.writeHead(413); let content = ejs.render(err_ejs, { err: '送信データのサイズは5KB以内にしてください'}); res.write(content); res.end(); is413 = true; } }); req.on('end', () => { if(is413) return; let post_data = qs.parse(body); let query = `select * from table_posts where id = ${params.id}.replace(/'/g, "''")`; const db = new sqlite3.Database(db_path); // 更新しようとしているデータが存在するかチェックする db.get(query, (err, row) => { if(row != null){ db.run("update table_posts set name = ? where id = ?", post_data.name.replace(/</g, "<").replace(/>/g, ">"), params.id); db.run("update table_posts set body = ? where id = ?", post_data.body.replace(/</g, "<").replace(/>/g, ">"), params.id); db.run("update table_posts set updatetime = datetime('now', '+9 hours') where id = ?", params.id); } res.writeHead(302, {'Location':'./'}); res.end(); }); db.close(); }); }); |
削除の処理
削除の処理を示します。投稿者の名前の横にある[削除]のリンクがクリックされたらデータベースから投稿を削除します。urlはhttp://localhost:65003/delete<id>です。データベース内にあるidが同じものが削除されます。そして処理が終わったらトップページにリダイレクトされます。
index.js
1 2 3 4 5 6 7 8 9 10 11 12 |
app.get('/delete:id', (req, res) => { let params = req.params; const db = new sqlite3.Database(db_path); db.serialize(() => { db.run("delete from table_posts where id = ?", params.id); res.writeHead(302, {'Location':'./'}); res.end(); }); db.close(); }); |
存在しないページにアクセスしたとき
最後に存在しないページにアクセスしたときの処理を示します。この場合は「404 ページが見つからない」と表示させます。
index.js
1 2 3 4 5 6 |
app.use((req, res) => { let content = ejs.render(err_ejs, { err: '404 ページが見つからない'}); res.writeHead(200, {'Content-Type':'text/html'}); res.write(content); res.end(); }); |
とりあえず完成!
始めましてしょうたと申します。めんどくさい質問になりそうですが、現在nodejsでmodulesのexpress等を使用してサーバーを建ててAPIの勉強中です。
sqlite3に関してなのですがうまくいきません。他で質問したりしているのですが、modulesのsqlite3はnodejsのmodulesだとか、参考の動画をみてその通りにならないので何故かなと思います。
window10、vscodeから直接cmdを使用しています。
階層はこんな感じです。
basic
-node_modules
-package.json
-app
-app.js
-db
-database.sqlite3
-sqlite-tool
-sqlite3.exe
node_modulesからsqlite3をnpm installして、
“dependencies”: {
“sqlite3”: “^5.0.8”
}
と致しました。
sqlite3コマンドを打っても認識されないので、sqlite3.exeコマンドが打てるように、PATHは通さず、sqlite3.exeを直接実行して、データベースを作成して、vscodeには拡張機能としてsqliteをインストール致しました。
https://www.youtube.com/watch?v=x4ZrmnqoS1Y&list=PLX8Rsrpnn3IVW5P1H1s_AOP0EEyMyiRDA&index=3
動画というのはこちらです。同じようにいかない理由が分からないので色々調べていたら、こちらのサイトにたどり着いたのでご質問させて頂きます。
宜しくお願い致します。