中安拓也のブログ

プログラミングについて書くブログ

Express(Node.js) + SQLite でパパッとブログを作る

パパッとは作れてないです。

はじめに

Express(JavaScriptベースのサーバサイドフレームワーク)に入門したくなったので、簡単なブログを作ってみます。Node.jsとnpmはインストールされている前提で進めていくので、インストールされてない方は公式サイトでインストールしてください。

なお、完成版のソースコードは、GitHubに置いています。

github.com

バージョン情報

  • npm: 5.6.0
  • Node.js: 8.11.3
  • Express: 4.16.0

エディタは Visual Studio Code を使用

1.アプリのひな形を作る

Expressアプリケーションのひな形を作成してくれるライブラリであるExpress Generator を使って、アプリの基本部分を作成していきます。

# Express Generatorをグローバルインストール
npm i -g express-generator

Express Generator のインストールが完了したら、下記のコマンドでアプリを作成します。

# ex-blogのところには、アプリ名を入れてください
express -e ex-blog

-eというオプションがついていますが、これは「テンプレートエンジンにEJSを使用する」という意味です。-eを省略すると、テンプレートエンジンにJadeが採用された状態でアプリケーションが作成されます。

下記コマンドを実行すると、アプリが起動するので、ブラウザを立ち上げて、http://localhost:3000/を入力してください。「Express」とブラウザに表示されれば、雛形の作成は完了です。

cd ex-blog
npm i
# アプリ起動
npm start

2. SQLiteのインストール

データベースについては、環境構築とか設定が楽そうなのでSQLiteを使います。公式サイトに行って環境(OSとかの)にあったやつをダウンロードしてください。

3. SQLiteのGUIツールもインストール

GUIツールもついでに落としましょう。OSにあったインストーラをダウンロードしてください。DB Browser for SQLite

4. データベースを設計する

f:id:l08084:20180707205211p:plain

DB Browser for SQLite を起動して「New Database」ボタンをクリックすると、データベースファイルの保存ダイアログが現れるので、mydb.sqlite3という名称で、ex-blogフォルダの配下に新規作成してください。

f:id:l08084:20180707205703p:plain

データベースファイルを作成したら、投稿したブログを保存する用のテーブルを作成しましょう。設定については、下記の画像を参考にしてください。テーブル名をpostに設定しています。

f:id:l08084:20180707210429p:plain

上記の画像では、idのTypeをINTEGERに設定して右側に見えるチェックボックスをすべてONにしていますが、こうすることでidがオートインクリメントの主キーになります。

f:id:l08084:20180707211058p:plain

注意点ですが、テーブルの作成に限らず、データベースの設定が終わったら「Write Changes」ボタンを押すのを忘れないようにしてください。押さないと変更が適用されません。

最後にターミナルでアプリケーションフォルダ(ex-blog)をカレントディレクトリに設定して、SQLite3モジュールをインストールすると、ExpressからSQLiteにアクセスする準備が完了します。

npm i sqlite3

5.コードを書く

Express Generator が作成した雛形のプログラムファイル・テンプレートファイルを更新して、ブログを作っていきます。

5-1. 記事の投稿画面を作る

まず、views/write.ejsファイルを新規作成して、下記のように記入してください。このテンプレートファイルは、記事投稿画面として使用されます。

views/write.ejs

<!DOCTYPE html>
<html>
  <head>
    <title>Express Blog</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
      <h1>新しい記事を投稿</h1>
        <form action="/write" method="POST">
            <table>
                <tr>
                    <!-- 記事のタイトルを入力する欄 -->
                    <th>Title</th>
                    <td>
                        <input type="text" class="title-input" name="title" />
                    </td>
                </tr>
                <tr>
                    <!-- 記事の内容を入力する欄 -->
                    <th>Content</th>
                    <td>
                        <textarea name="content"></textarea>
                    </td>
                </tr>
                <tr>
                    <!-- 記事の投稿ボタン -->
                    <th></th>
                    <td>
                        <button type="submit">投稿</button>
                    </td>
                </tr>
        </table>
        </form>
  </body>
</html>

続いて、記事投稿画面のルーティングスクリプト(投稿画面に遷移した時の処理)を作成していきます。routes/write.jsファイルを新規作成してください。

routes/write.js

var express = require('express');
var router = express.Router();
// sqlite3モジュールのインポート
var sqlite3 = require('sqlite3');

var db = new sqlite3.Database('mydb.sqlite3');
// /write にgetメソッドでリクエストすると、write.ejsをレンダリング
router.get('/', (req, res, next) => res.render('write'));

// /write にpostメソッドでリクエストした時の処理
router.post('/', (req, res, next) => {
    // 投稿画面のフォームからタイトルと本文を取得
    const title = req.body.title;
    const content = req.body.content;
    // 投稿日付を取得
    const createdtime = new Date();
    // 投稿記事をDBにinsertする
    db.run(
        'insert into post (title, content, createdtime) values (?, ?, ?)',
        title,
        content,
        createdtime
    );
    // ホーム画面(index.ejs)にリダイレクト
    res.redirect('/');
});

module.exports = router;

記事投稿画面へのルーティングを追加するために、app.jsにも修正を加えます。(add this!とコメントされている部分を追加すればOKです。)

app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
// add this!
// write.jsのロード
var writeRouter = require('./routes/write');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
// add this!
// アドレスの割当
app.use('/write', writeRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

レイアウトもテキトーに整えましょう

public/stylesheets/style.css

body {
  padding: 50px;
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}

a {
  color: #00B7FF;
}

input[type="text"],
textarea {
    width: 400px;
    padding: 0.8em;
    outline: none;
    border: 1px solid #DDD;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    border-radius: 3px;
    font-size: 16px;
}
textarea {
    width: 600px;
    height: 200px;
}

input[type="text"]:focus,
textarea:focus {
    box-shadow: 0 0 7px #3498db;
    border: 1px solid #3498db;
}

button {
  border:solid 1px #ccc;
  padding:15px 30px;
  margin: 20px 0;
  font-family:Arial, sans-serif;
  font-size:1.2em;
  text-transform:uppercase;
  font-weight:bold;
  color:#333;
  cursor:pointer;
}

この段階で、npm run startコマンドでアプリを起動した後に、http://localhost:3000/writeにブラウザでアクセスすると、こんな感じの画面が表示されるはずです。

f:id:l08084:20180708161039p:plain
記事投稿画面

5-2. 記事の閲覧画面(ホーム画面)を作る

続いて、投稿した記事を閲覧する画面の方も作っていきましょう。テンプレートファイルのviews/index.ejsを下記のように書き換えてください。

views/index.ejs

<!DOCTYPE html>
<html>
  <head>
    <title>Express Blog</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1>Express Blog</h1>
    <a href="/write">Write</a>
    <% for(var i=0; i<posts.length; i++) {%>
    <h2><%= posts[i].title%></h2>
    <p><%- posts[i].content%></p>
    <% } %>
  </body>
</html>

ポイントとしては、posts[i].contentの部分で、改行タグ<br>を出力するために<%= %>ではなく、エスケープなしで展開してくれる<%- %>を使っている部分に若干注意が必要です。

    <h2><%= posts[i].title%></h2>
    <p><%- posts[i].content%></p>

記事閲覧画面のルーティングスクリプトも書き換えます。DBからの記事一覧取得と改行コードを改行タグ<br>に変換する処理を行なっています。

routes/index.js

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

var sqlite3 = require('sqlite3');

var db = new sqlite3.Database('mydb.sqlite3');

/* GET home page. */
router.get('/', (req, res, next) => {
  db.serialize(() => {
    // DBから投稿されたブログをすべて取得
    db.all('select * from post', (err, rows) => {
      if (!err && rows) {
        // 改行コードを<br>に変換
        const newRows = rows.map(row => {
          if (row.content) {
            row.content = row.content.replace(/\r?\n/g, '<br>');
          }
          return row;
        });
        console.log(newRows);
        // postsパラメータを渡した状態で、index.ejsをレンダリング
        res.render('index', { posts: newRows });
      }
    });
  });
});

module.exports = router;

6.動作確認

これでいったん完成です。npm run startコマンドの後にブラウザでhttp://localhost:3000/下記のような、ブログのホーム画面が表示されるはずです。

f:id:l08084:20180708172517p:plain
ホーム画面

記事の投稿画面から記事を投稿して、ホーム画面に記事が表示されるかなども確認してみてください。

f:id:l08084:20180708172841p:plain

つまづいたところ

  • アプリから、DBのpostテーブルを呼び出す処理でno such tableみたいなエラーが出まくる
    • テーブルを作った後に、DB Browser for SQLiteでWrite Changeボタンを押しておらず、変更が適用されていなかったのが原因だった