React Tutorial Example (Express)


[React](https://facebook.github.io/react/index.html) は *JavaScrit* による **フロントエンド** の *View* ライブラリです。
[React tutorial](https://facebook.github.io/react/docs/tutorial.html)をあえてスクリプトを使わないでアプリケーションを作ります。
*JavaScrit* のない世界から *React* への **パラダイムシフト** を体感してみましょう。

## Learning

* React tutorial に *Express* を利用する。
* View に *React (Virtual) DOM* ではないテクノロジーを利用する。

## Environment

* node: v4.4.5
* npm: v3.9.6

## Comment Box Form

* 完成される [Source Code](https://github.com/ogom/express-comment-box-example) のファイルリストです。

```bash
$ tree -a -I node_modules
.
├── app.js
├── bin
│   └── www
├── db
│   └── comments.json
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── comments.js
│   └── index.js
└── views
    ├── error.ejs
    └── index.ejs
```

*Let's hands-on*

### Initial application

* [Express application generator](https://github.com/expressjs/generator) で *Webアプリケーション* を作成します。

```bash
$ npm install express-generator -g
$ express --ejs express-comment-box-example
$ cd express-comment-box-example && npm install
```

### Add package

* *Markdown parser* は [remarkable](https://github.com/jonschlinkert/remarkable) を使います。

```bash
$ npm install remarkable --save
```

### Add Database

* JSON形式の *フラットファイルデータベース* でデータを永続化します。

```json:db/comments.json
[
  {"id": 1, "author": "Pete Hunt", "text": "This is one comment"},
  {"id": 2, "author": "Jordan Walke", "text": "This is *another* comment"}
]
```

### Replace stylesheet

* Stylesheet は [React Tutorial](https://github.com/reactjs/react-tutorial) からダウンロードします。

```bash
$ curl https://raw.githubusercontent.com/reactjs/react-tutorial/master/public/css/base.css \
-o ./public/stylesheets/style.css
```

### Add View

* [EJS](https://github.com/mde/ejs) の *テンプレートエンジン* でHTMLの要素を設定します。
  * このHTMLに `<script>` の要素はありません!

```html:views/index.ejs
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <div className="commentBox">
      <h1>Comments</h1>
      <div className="commentList">
        <% comments.forEach(function(comment){ %>
        <div className="comment">
          <h2 className="commentAuthor">
            <%= comment.author %>
          </h2>
          <%- comment.text -%>
        </div>
        <% }) %>
      </div>
      <form className="commentForm" action="/" method="post">
        <input type="text" placeholder="Your name" name="author" />
        <input type="text" placeholder="Say something..." name="text" />
        <input type="submit" value="Post" />
      </form>
    </div>
  </body>
</html>
```

### Add Route

* データを *フラットファイルデータベース* から読み込んでレンダリングする `router.get` を設定します。
* データを *フラットファイルデータベース* に読み書きしてレンダリングする `router.post` を設定します。

```javascript:routes/index.js
var express = require('express');
var router = express.Router();
var fs = require('fs');
var Remarkable = require('remarkable');
var md = new Remarkable();

/* GET home page. */
router.get('/', function(req, res, next) {
  fs.readFile('db/comments.json', function(err, data) {
    var comments = JSON.parse(data);
    comments = comments.map(function(comment, index, array) {
      return {author: comment.author, text: md.render(comment.text)};
    });
    res.render('index', {title: 'Comment Box Example', comments: comments});
  });
});

/* POST home page. */
router.post('/', function(req, res) {
  fs.readFile('db/comments.json', function(err, data) {
    var comments = JSON.parse(data);
    req.body.id = Date.now();
    comments.push(req.body);
    fs.writeFile('db/comments.json', JSON.stringify(comments, null, 4), function(err) {
      comments = comments.map(function(comment, index, array) {
        return {author: comment.author, text: md.render(comment.text)};
      });
      res.render('index', {title: 'Comment Box Example', comments: comments});
    });
  });
});

module.exports = router;
```

### Start HTTP Server

* `npm start` コマンドで *Webアプリケーション* を実行します。
* ブラウザで http://localhost:3000 をロードすると **Comment Box Example** が表示されます。

```bash
$ npm start
$ open http://localhost:3000
```

![express](https://dl.dropboxusercontent.com/u/14690051/images/notes/lang/node/comment-box/express.png)

## Comment Box API

[React tutorial](https://facebook.github.io/react/docs/tutorial.html) で利用する API も作りましょう。

### Replace Route

* `routes/index.js` をJSON形式のレスポンスにするだけです。
* CORS(Cross-Origin Resource Sharing)の設定もします。

```bash
$ mv ./routes/users.js ./routes/comments.js
```

```javascript:routes/comments.js
var express = require('express');
var router = express.Router();
var fs = require('fs');

/* GET comments listing. */
router.get('/', function(req, res, next) {
  fs.readFile('db/comments.json', function(err, data) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.json(JSON.parse(data));
  });
});

/* POST comment creating. */
router.post('/', function(req, res) {
  fs.readFile('db/comments.json', function(err, data) {
    var comments = JSON.parse(data);
    comments.push(req.body);
    fs.writeFile('db/comments.json', JSON.stringify(comments, null, 4), function(err) {
      res.setHeader('Access-Control-Allow-Origin', '*');
      res.json(comments);
    });
  });
});

module.exports = router;
```

### Replace Config

* `comments` のモジュールの設定をします。

```diff:app.js
 var routes = require('./routes/index');
-var users = require('./routes/users');
+var comments = require('./routes/comments');

 app.use('/', routes);
-app.use('/users', users);
+app.use('/api/comments', comments);
```

### API Testing

* `curl` コマンドで取得できるか確認します。

```bash
$ curl http://localhost:3000/api/comments
[{"id":1,"author":"Pete Hunt","text":"This is one comment"},{"id":2,"author":"Jordan Walke","text":"This is *another* comment"}]
```

* `curl` コマンドで追加できるか確認します。

```bash
$ curl http://localhost:3000/api/comments \
--header 'Content-Type:application/json' \
--data '{"author":"foo","text":"*bar*"}'
[{"id":1,"author":"Pete Hunt","text":"This is one comment"},{"id":2,"author":"Jordan Walke","text":"This is *another* comment"},{"author":"foo","text":"*bar*"}]
```

*Congrats!*

----

1つのViewに **CommentBox**, **CommentList**, **Comment**, **CommentForm** が集まっているので構成を部品に分離したいです。
次は [React tutorial を *Hot Module Replacement*](http://qiita.com/ogomr/items/9eb64ff5f7d4fcd08514) で進めましょう！