node.js実践編(Markdown導入編)

# 準備
以下のコマンドで必要なパッケージをインストールします。

```
npm install markdown-it
```
1から作る場合は以下のパッケージをインストールします。

```
npm install express-session
npm install express-validator
npm install sqlite3
npm install knex
npm install bookshelf
npm install markdown-it
```

# モデル作成
以下のコマンドでモデルを作成します。

```
npx sequelize-cli model:generate --name Markdata --attributes userId:integer,title:string,content:text
```
モデルファイルを修正、追記します。

```javascript:models/markdata.js
'use strict';
module.exports = (sequelize, DataTypes) => {
  const Markdata = sequelize.define('Markdata', {
    userId: {
      type: DataTypes.INTEGER,
      validate: {
        notEmpty: {
          msg: "利用者は必須です。"
        }
      }
    },
    title: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: {
          msg: "タイトルは必須です。"
        }
      }
    },
    content: {
      // 長さの制限無し
      type: DataTypes.TEXT,
      validate: {
        notEmpty: {
          msg: "コンテンツは必須です。"
        }
      }
    }
  }, {});
  Markdata.associate = function(models) {
    Markdata.belongsTo(models.User);
  };
  return Markdata;
};
```

```javascript:models/users.js
User.associate = function(models) {
    // 主モデル
    User.hasMany(models.Board);
    // 以下を追記
    User.hasMany(models.Markdata);
  };
```
以下のコマンドでマイグレーションを行います。

```
npx sequelize-cli db:migrate
```
# ルーティング作成
以下のファイルを作成します。

```javascript:routes/marks.js
const express = require('express');
const router = express.Router();
const db = require('../models/index');
const { Op } = require("sequelize");
const MarkdownIt = require('markdown-it');
const markdown = new MarkdownIt();

const pnum = 10;

// ログインのチェック
function check(req, res) {
    if (req.session.login == null) {
        // ログイン後に戻る値
        req.session.back = '/md';

        res.redirect('/users/login');
        return true;
    } else {
        return false;
    }
}

// トップページ
router.get('/', (req, res, next) => {
    if (check(req, res)){ return };
    db.Markdata.findAll({
        where:{userId: req.session.login.id},
        limit:pnum,
        order: [
            ['createdAt', 'DESC']
        ]
    }).then(mds => {
        var data = {
            title: 'Markdown Search',
            login: req.session.login,
            message: '※最近の投稿データ',
            form: {find:''},
            content: mds
        };
        res.render('md/index', data);
    });
});

// 検索フォームの送信処理
router.post('/', (req, res, next) => {
    if (check(req, res)){ return };
    db.Markdata.findAll({
        where:{
            userId: req.session.login.id,
            content: {[Op.like]:'%' + req.body.find + '%'},
        },
        order: [
            ['createdAt', 'DESC']
        ]
    }).then( mds => {
        var data = {
            title: 'Markdown Search',
            login: req.session.login,
            message: '※"' + req.body.find + '" で検索された最近の投稿データ',
            form: req.body,
            content:mds
        };
        res.render('md/index', data);
    });
});

// 新規作成ページの表示
router.get('/add', (req, res, next) => {
    if (check(req, res)){ return };
    res.render('md/add', { title: 'Markdown/Add' });
});

// 新規フォームの送信処理
router.post('/add', (req, res, next) => {
    if (check(req, res)){ return };
    db.sequelize.sync()
        .then(() => db.Markdata.create({
            userId: req.session.login.id,
            title: req.body.title,
            content: req.body.content,
        })
        .then(model => {
            res.redirect('/md');
        })
        );
});

// '/mark'へアクセスした際のリダイレクト
router.get('/mark', (req, res, next) => {
    res.redirect('/md');
    return;
});

// 指定IDのMarkdata表示
router.get('/mark/:id', (req,res, next) => {
    if (check(req, res)){ return };
    db.Markdata.findOne({
        where: {
            id: req.params.id,
            userId: req.session.login.id
        },
    })
    .then((model) => {
        makepage(req, res, model, true);
    });
});

// Markdataの更新処理
router.post('/mark/:id', (req, res, next) => {
    if (check(req, res)){ return };
    db.Markdata.findByPk(req.params.id)
        .then(md => {
            md.content = req.body.source;
            md.save().then((model) => {
                makepage(req, res, model, false);
            });
        })
});

// 指定IDのMarkdaraの表示ページ作成
function makepage(req, res, model, flg) {
    var footer;
    if (flg){
        var d1 = new Date(model.createdAt);
        var dstr1 = d1.getFullYear() + '-' + (d1.getMonth() + 1) + '-' + d1.getDate();
        var d2 = new Date(model.updatedAt);
        var dstr2 = d2.getFullYear() + '-' + (d2.getMonth() + 1) + '-' + d2.getDate();
        footer = '(created: ' + dstr1 + ', updated: ' + dstr2 + ')';
    } else {
        footer = 'UPdating date and time information...'
    }
    var data = {
        title: 'Markdown',
        id: req.params.id,
        head: model.title,
        footer: footer,
        content: markdown.render(model.content),
        source: model.content
    };
    res.render('md/mark', data);
}

module.exports = router;
```
app.jsに登録します。

```javascript:app.js
var marksRouter = require('./routes/marks');

app.use('/md', marksRouter);
```

# テンプレート作成
以下のファイルを作成します。

```html:views/md/index.ejs
<!DOCTYPE html>
<html lang="ja">

    <head>
        <meta charset="UTF-8">
        <meta http-equiv="content-type" content="text/html">
        <title><%= title %></title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous">
        <link rel="stylesheet" href="/stylesheets/style.css" />
    </head>

    <body class="container">

        <header>
            <h1 class="display-4">
                <%= title %>
            </h1>
        </header>
        <div role="main">
            <p class="h5 my-4">Hi,
                <span><%= login.name %></span>!<br>
                Welcome to <%= title %>.
            </p>
            <form action="/md" method="POST">
                <div class="form-group">
                    <label for="find">FIND</label>
                    <input type="text" name="find" id="find" value="<%= form.find %>" class="form-control">
                </div>
                <input type="submit" value="検索" class="btn btn-primary">
            </form>
            <p class="my-4 h5"><%= message %></p>
            <table class="table">
                <% for (var i in content) { %>
                <% var ob = content[i]; %>
                <tr>
                    <td>
                        <a href="/md/mark/<%= ob.id %>" class="text-dark">
                            <%= ob.title %>
                        </a>
                    </td>
                </tr>
                <% } %>
            </table>
            <p> </p>
            <p><a href="/md/add">※データを登録</a></p>
        </div>
    </body>
</html>
```

```html:views/md/add.ejs
<!DOCTYPE html>
<html lang="ja">

    <head>
        <meta charset="UTF-8">
        <meta http-equiv="content-type" content="text/html">
        <title><%= title %></title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous">
        <link rel="stylesheet" href="/stylesheets/style.css" />
    </head>

    <body class="container">

        <header>
            <h1 class="display-4 text-primary">
                <%= title %>
            </h1>
        </header>
        <div role="main">
            <form action="/md/add" method="post">
                <div class="form-group">
                    <label>TITLE</label>
                    <input type="text" name="title" id="title" class="form-control">
                </div>
                <div class="form-group">
                    <label>CONTENT</label>
                    <textarea name="content" id="content" rows="10" class="form-control"></textarea>
                </div>
                <input type="submit" value="送信" class="btn btn-primary">
            </form>
            <p class="mt-4"><a href="/md">&lt;&lt; Top へ戻る</a></p>
        </div>
    </body>
</html>
```

```html:views/md/mark.ejs
<!DOCTYPE html>
<html lang="ja">

    <head>
        <meta charset="UTF-8">
        <meta http-equiv="content-type" content="text/html">
        <title><%= title %></title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous">
        <link rel="stylesheet" href="/stylesheets/style.css" />
    </head>

    <body class="container">

        <header>
            <h1 class="display-4">
                <%= title %>
            </h1>
        </header>
        <div role="main">
            <p class="h5"><%= head %></p>
            <form action="/md/mark/<%=id %>" method="post">
                <div class="form-group">
                    <label for="source">SOURCE</label>
                    <textarea name="source" id="source" rows="5" class="form-control"><%= source %></textarea>
                </div>
                <input type="submit" value="更新" class="btn btn-primary">
            </form>
            <div class="card mt-4">
                <div class="card-header text-center h5">
                    Preview
                </div>
                <div class="card-body">
                    <%- content %>
                </div>
                <div class="card-footer text-muted text-right">
                    <%= footer %>
                </div>
            </div>
            <p class="mt-4"><a href="/md">&lt;&lt; Top へ戻る</a></p>
        </div>
    </body>
</html>
```

# 結果

## ログイン画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1111200/20d8293a-5d0d-c44e-33ec-b69e408da3c5.png)

## Markdown一覧
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1111200/536410d2-96a0-efa1-3b70-b366cec46f15.png)

## Markdown追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1111200/238886e2-947d-edfd-38e7-fd3b930b6495.png)

## Markdown更新
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1111200/9e5e6e26-798c-b664-8146-9d8ef8833ab5.png)