JavaScriptだけでWebサイト開発

node.js + Express + mongodb系 + React でサービスを作るメモ

node.js+Express+mongooseの掲示板をReact view(SSR)で実装

前回の「node.js+Express+mongooseで掲示板を作ってみる」ではview engineをpugで実装しました。 これを機能はそのままに Reactをview engineとして使ってみます。

目次

1. express-react-views パッケージインストール

express-react-views は React, React-DOM を用いたテンプレートをサーバーサイドでコンパイル(トランスパイル)してHTMLを出力するパッケージです。
他のviewエンジンと役割は同じです。

プロジェクトディレクトリ内kakiapp/でインストールします

$ cd kakiapp
$ npm install express-react-views react react-dom
+ express-react-views@0.10.4
+ react@16.0.0
+ react-dom@16.0.0

前回使ったpubは削除します

$ npm uninstall pug

2. app.js で view engine を変更する

app.js

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var index = require('./routes/index');
var users = require('./routes/users');
var up = require('./routes/up');

var app = express();

//// 1. mongoose connection
var mongoose = require('mongoose');  // mongoose 利用
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost/app1', {useMongoClient: true}); // 接続
//// end 1

//// 2. mongoose model 定義
// Schema 定義
var postSchema = mongoose.Schema({
    kakikomi: String,
    name: String
});
// model
var Post = mongoose.model('Post',postSchema);
// Post modelを router moduleで共有する
app.set('Post',Post);
//// end 2

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jsx');
app.engine('jsx', require('express-react-views').createEngine());

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', index);
app.use('/users', users);
//// 3. 投稿画面
app.use('/up', up);
//// end 3

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// 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;

今回の変更はviewにpugを使用していた以下のところ

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

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jsx');
app.engine('jsx', require('express-react-views').createEngine());

と変更しました。views/ ディレクトリの テンプレートを全てをjsxで書き直します

3. ViewテンプレートをReact,JSXで記述

スタイルなしバージョン

ページレイアウト(共通)

layout.jsx

var React = require('react');

class Layout extends React.Component {
  render() {
    return (
      <html>
        <head><title>{this.props.title} - 掲示板</title></head>
        <body>
          <header>
            <h1>掲示板</h1>
            <nav>
              <ul>
                <li><a href="/">トップ</a></li>
                <li><a href="/up">投稿</a></li>
              </ul>
            </nav>
          </header>
          {this.props.children}
        </body>
      </html>
    );
  }
}

module.exports = Layout;

トップページ

index.jsx

var React = require('react');
var Layout = require('./layout');

class Index extends React.Component {
  render() {
    var list = [];
    for (var i in this.props.posts) {
      list.push(
        <li>
          <div className="box">
            <div>{this.props.posts[i].kakikomi}</div>
            <p className="name">{this.props.posts[i].name}</p>
          </div>
        </li>
      );
    }

    return (
      <Layout title={this.props.title}>
        <div>
          <h2>{this.props.title}</h2>
          <ul>
            {list}
          </ul>
        </div>
      </Layout>
    );
  }
}

module.exports = Index;

投稿ページ

up.jsx

var React = require('react');
var Layout = require('./layout');

class Up extends React.Component {
  render() {
    return (
      <Layout>
        <h2>投稿フォーム</h2>
        <form action="/up" method="POST">
          <label>内容</label>
          <textarea name="kakikomi"></textarea>
          <label>名前</label>
          <input type="text" name="name" />
          <button type="submit">投稿</button>
        </form>
      </Layout>
    );
  }
}

module.exports = Up;

投稿後ページ

up_post.jsx

var React = require('react');
var Layout = require('./layout');

class UpPost extends React.Component {
  render() {
    return (
      <Layout>
        <h2>投稿しました</h2>
        <p>{this.props.kakikomi}</p>
        <p>{this.props.name}</p>
      </Layout>
    );
  }
}

module.exports = UpPost;

4. Reactテンプレートへのスタイルの適用

layout.jsx

var React = require('react');

var style = {
  header: {marginBottom:"40px"},
  navul: {display:"flex",listStyle:"none" },
  navli: {marginRight:"20px"}
}

class Layout extends React.Component {
  render() {
    return (
      <html>
        <head>
          <title>{this.props.title} - 掲示板</title>
          <link rel="stylesheet" href="/stylesheets/style.css" />
        </head>
        <body>
          <header style={style.header}>
            <h1>掲示板</h1>
            <nav>
              <ul style={style.navul}>
                <li style={style.navli}><a href="/">トップ</a></li>
                <li style={style.navli}><a href="/up">投稿</a></li>
              </ul>
            </nav>
          </header>
          {this.props.children}
        </body>
      </html>
    );
  }
}

module.exports = Layout;

index.jsx

var React = require('react');
var Layout = require('./layout');

var style = {
  ul: {listStyle:"none"},
  name: {fontSize:"0.8em",color:"#555"},
  box: {border:"1px solid #888", padding:"20px", marginBottom:"40px"}
}

class Index extends React.Component {
  render() {
    var list = [];
    for (var i in this.props.posts) {
      list.push(
        <li>
          <div style={style.box}>
            <div>{this.props.posts[i].kakikomi}</div>
            <p style={style.name}>{this.props.posts[i].name}</p>
          </div>
        </li>
      );
    }

    return (
      <Layout title={this.props.title}>
        <div>
          <h2>{this.props.title}</h2>
          <ul style={style.ul}>
            {list}
          </ul>
        </div>
      </Layout>
    );
  }
}

module.exports = Index;

up.jsx

var React = require('react');
var Layout = require('./layout');

var style = {
    textarea: {width:"100%", height:"60px"},
    button: {marginTop:"30px"}
}

class Up extends React.Component {
  render() {
    return (
      <Layout>
        <h2>投稿フォーム</h2>
        <form action="/up" method="POST">
          <label>内容</label>
          <textarea name="kakikomi" style={style.textarea}></textarea>
          <label>名前</label>
          <input type="text" name="name" />
          <button type="submit" style={style.button}>投稿</button>
        </form>
      </Layout>
    );
  }
}

module.exports = Up;

やったこと

  • layout.jsxでは外部ファイルstyle.css と 埋め込みを試しています
  • その他はstyleをオブジェクトで定義し、各コンポーネント内に埋め込み
  • スタイルのパラメータのハイフンを含むものはローワーキャメルケースにする必要がある。

ブラウザでの動作

5. まとめ

  • express-react-viewsをインストール
  • app.js で jsx に express-react-viewsのエンジンを登録
  • テンプレートをReactで書く
  • スタイルをオブジェクトにしてjs内で定義・適用してみる
©ichi-bit