diff --git a/.gitignore b/.gitignore
index daeba5f9..10075f46 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
*~
node_modules
.DS_Store
+.idea
+npm-debug.log
\ No newline at end of file
diff --git a/README.md b/README.md
index 4862f5df..e1b7f592 100644
--- a/README.md
+++ b/README.md
@@ -1,55 +1,41 @@
-[](https://heroku.com/deploy)
+# Isomorphic React and Express
-# React Tutorial
+This repository shows how React can be used with the Express framework isomorphically; that is to use the same code on both the server and browser to render the same sections of pages. This uses the comment box example from the [React tutorial](http://facebook.github.io/react/docs/tutorial.html) but in addition to rendering the comment box in the browser, it pre-renders it on the server, using the same code.
-This is the React comment box example from [the React tutorial](http://facebook.github.io/react/docs/tutorial.html).
+There are also a few other additions. I've set up `webpack` so that it bundles up the code to be used in the browser. I also didn't particularly like the `JSX` template render methods being in the same file as the controller / component code (despite what React says, they are not just "views"; they behave very similar to Angular's controllers). And lastly since I had to server-side rendering, I've had to use a view engine. I've chose `Swig` as unlike `Jade` it uses proper HTML and so means I have one fewer language to learn (which fits into the philosophy of isomorphism).
-## To use
-
-There are several simple server implementations included. They all serve static files from `public/` and handle requests to `/api/comments` to fetch or add data. Start a server with one of the following:
+Isomorphic validation is also available: see more information below under `Validation`.
-### Node
+Naturally this means all of the other server language implementations have been removed - Python, PHP etc.
-```sh
-npm install
-node server.js
-```
+## Implementation
-### Python
+The example file from the tutorial is now in a publicly-inaccessible location at `components/components.js`. This exports each component in `module.exports`. The templates are in another file which is `require`d from there, `templates.jsx`.
-```sh
-pip install -r requirements.txt
-python server.py
-```
+In the browser, the main entry point method which calls `ReactDOM.render`, is added as a global window method, `renderCommentBox` in `components/browser.js`, which is used as the entry script for `webpack`. This is all compiled into `public/scripts/bundle.js` which is what the browser includes. The settings for the `webpack` are found in `webpack.config.js`; several libraries like React itself and the markdown parser are set to be externals, which means they are not bundled up so that the browser can load those from a CDN (Content Delivery Network).
-### Ruby
-```sh
-ruby server.rb
-```
+On the browser, `components/server.js` is `require`d, after the `node-jsx` module is set up, in order that the JSX syntax can be understood. This exports a `renderCommentBox` method which returns a static string after calling `ReactDOMServer.renderToString` from the `react-dom/server` module. The route for `/` simply calls this method and passes it as a variable to the `views/index.html` view.
-### PHP
-```sh
-php server.php
-```
+## Validation
-### Go
-```sh
-go run server.go
-```
+There is also client-side and server-side validation which again is isomorphic. This uses `react-validation-mixin` for React from which the rules are defined by `validatorjs`. These are connected using a [react-validatorjs-strategy](https://github.com/TheChech/react-validatorjs-strategy). The schemas are defined in `components/schemas.js`.
-### Perl
+Extra functionality has been added to `components/templates.jsx` and `components/components.js` to handle the validation and server-side to the `/api/comments` POST end point in `server.js`.
-```sh
-cpan Mojolicious
-perl server.pl
-```
+## To use
-And visit . Try opening multiple tabs!
+ npm install
+
+Next, to compile the browser code into `public/scripts/bundle.js`:
-## Changing the port
+ npm run webpack:dev
+
+Or if you want to compile it to a minified version without the source map:
-You can change the port number by setting the `$PORT` environment variable before invoking any of the scripts above, e.g.,
+ npm run webpack
+
+And then start the server:
-```sh
-PORT=3001 node server.js
-```
+ npm start
+
+And visit .
diff --git a/comments.json b/comments.json
index 7bef77ad..393dce6e 100644
--- a/comments.json
+++ b/comments.json
@@ -9,4 +9,4 @@
"author": "Paul O’Shannessy",
"text": "React is *great*!"
}
-]
+]
\ No newline at end of file
diff --git a/components/browser.js b/components/browser.js
new file mode 100644
index 00000000..cd6e31c5
--- /dev/null
+++ b/components/browser.js
@@ -0,0 +1,30 @@
+var components = require('./components');
+var ReactDOM = require('react-dom');
+var window = require('window');
+var $ = require('jquery');
+
+/**
+ * Load the comments via AJAX before rendering the comment box with the DOM.
+ * This will avoid the server rendered comments being replaced with nothing by JS.
+ * If the AJAX call fails, then just render no comments after logging the error.
+ */
+window.renderCommentBox = function () {
+ var url = "/api/comments";
+
+ $.get(url).then(
+ (comments) => {
+ return comments;
+ },
+ (err) => {
+ console.error(url, err);
+ return [];
+ }
+ )
+ .always((comments) => {
+ ReactDOM.render(React.createElement(components.CommentBox, {
+ url: url,
+ pollInterval: 2000,
+ comments: comments
+ }), document.getElementById('content'));
+ });
+};
diff --git a/components/components.js b/components/components.js
new file mode 100644
index 00000000..2f0d1f86
--- /dev/null
+++ b/components/components.js
@@ -0,0 +1,149 @@
+/**
+ * This file provided by Facebook is for non-commercial testing and evaluation
+ * purposes only. Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+var React = require('react');
+var marked = require('marked');
+var validation = require('react-validation-mixin');
+var $ = require('jquery');
+var strategy = require('react-validatorjs-strategy');
+
+var templates = require('./templates.jsx');
+var schemas = require('./schemas');
+
+var Comment = React.createClass({
+ displayName: 'Comment',
+ rawMarkup() {
+ var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
+ return {__html: rawMarkup};
+ },
+
+ render: templates.comment
+});
+
+var CommentBox = React.createClass({
+ displayName: 'CommentBox',
+ loadCommentsFromServer() {
+ $.get(this.props.url).then(
+ (data) => {
+ this.setState({data: data});
+ },
+ (err) => {
+ console.error(this.props.url, err);
+ }
+ );
+ },
+ handleCommentSubmit(comment) {
+ var comments = this.state.data;
+ // Optimistically set an id on the new comment. It will be replaced by an
+ // id generated by the server. In a production application you would likely
+ // not use Date.now() for this and would have a more robust system in place.
+ comment.id = Date.now();
+ var newComments = comments.concat([comment]);
+ this.setState({data: newComments});
+
+ $.ajax({
+ url: this.props.url,
+ dataType: 'json',
+ type: 'POST',
+ data: comment,
+ success: (data) => {
+ this.setState({data: data});
+ },
+ error: function (xhr, status, err) {
+ this.setState({data: comments});
+ console.error(this.props.url, status, err.toString());
+ }.bind(this)
+ });
+ },
+ getInitialState() {
+ return {data: this.props.comments};
+ },
+ componentDidMount() {
+ this.loadCommentsFromServer();
+ setInterval(this.loadCommentsFromServer, this.props.pollInterval);
+ },
+ render() {
+ return templates.commentBox.apply(this, [CommentList, CommentForm]);
+ }
+});
+
+var CommentList = React.createClass({
+ displayName: 'CommentList',
+ render() {
+ return templates.commentList.apply(this, [Comment]);
+ }
+});
+
+var CommentForm = React.createClass({
+ displayName: 'CommentForm',
+ getInitialState() {
+ // Define the rules and custom messages for each field.
+ this.validatorTypes = schemas.commentForm;
+
+ return {author: '', text: ''};
+ },
+ /**
+ * Activate the validation rule for the element on blur
+ *
+ * @param {Event} e
+ */
+ activateValidation(e) {
+ strategy.activateRule(this.validatorTypes, e.target.name);
+ this.props.handleValidation(e.target.name)(e);
+ },
+ /**
+ * Set the state of the changed variable and then when set, call validator
+ *
+ * @param {Event} e
+ */
+ handleChange(e) {
+ var state = {};
+ state[e.target.name] = e.target.value;
+
+ this.setState(state, () => {
+ this.props.handleValidation(e.target.name)(e);
+ });
+ },
+ /**
+ * Validate the form and if valid, submit the comment
+ *
+ * @param {Event} e
+ */
+ handleSubmit(e) {
+ e.preventDefault();
+
+ this.props.validate((error) => {
+ if (!error) {
+ // this.props.onCommentSubmit is actually CommentBox.handleCommentSubmit
+ this.props.onCommentSubmit(this.state);
+ this.setState({author: '', text: ''});
+ }
+ });
+ },
+ getValidatorData() {
+ return this.state;
+ },
+ getClassName(field) {
+ return this.props.isValid(field) ? '' : 'has-error';
+ },
+ render: templates.commentForm
+});
+
+CommentForm = validation(strategy)(CommentForm);
+
+module.exports = {
+ Comment,
+ CommentBox,
+ CommentList,
+ CommentForm
+};
diff --git a/components/schemas.js b/components/schemas.js
new file mode 100644
index 00000000..2092a3df
--- /dev/null
+++ b/components/schemas.js
@@ -0,0 +1,14 @@
+var strategy = require('react-validatorjs-strategy');
+
+module.exports = {
+ commentForm: strategy.createInactiveSchema(
+ {
+ author: 'required',
+ text: 'required|min:10|max:50'
+ },
+ {
+ 'min.text': 'Enter a message between 10 and 50 characters',
+ 'max.text': 'Enter a message between 10 and 50 characters'
+ }
+ )
+};
diff --git a/components/server.js b/components/server.js
new file mode 100644
index 00000000..f8f9e0c0
--- /dev/null
+++ b/components/server.js
@@ -0,0 +1,15 @@
+var components = require('./components');
+var React = require('react');
+var ReactDOMServer = require('react-dom/server');
+
+module.exports = {
+ /**
+ * Just return a static string to render on the server
+ *
+ * @param {Object} params
+ * @returns {String}
+ */
+ renderCommentBox(params) {
+ return ReactDOMServer.renderToString(React.createElement(components.CommentBox, params));
+ }
+};
diff --git a/components/templates.jsx b/components/templates.jsx
new file mode 100644
index 00000000..864b7a1e
--- /dev/null
+++ b/components/templates.jsx
@@ -0,0 +1,82 @@
+var React = require('react');
+
+module.exports = {
+ comment() {
+ return (
+
- );
- }
-});
-
-var CommentForm = React.createClass({
- getInitialState: function() {
- return {author: '', text: ''};
- },
- handleAuthorChange: function(e) {
- this.setState({author: e.target.value});
- },
- handleTextChange: function(e) {
- this.setState({text: e.target.value});
- },
- handleSubmit: function(e) {
- e.preventDefault();
- var author = this.state.author.trim();
- var text = this.state.text.trim();
- if (!text || !author) {
- return;
- }
- this.props.onCommentSubmit({author: author, text: text});
- this.setState({author: '', text: ''});
- },
- render: function() {
- return (
-
- );
- }
-});
-
-ReactDOM.render(
- ,
- document.getElementById('content')
-);
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 632a1efa..00000000
--- a/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-Flask==0.10.1
diff --git a/server.go b/server.go
deleted file mode 100644
index 934a4cfc..00000000
--- a/server.go
+++ /dev/null
@@ -1,112 +0,0 @@
-/**
- * This file provided by Facebook is for non-commercial testing and evaluation
- * purposes only. Facebook reserves all rights not expressly granted.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-package main
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "net/http"
- "os"
- "sync"
- "time"
-)
-
-type comment struct {
- ID int64 `json:"id"`
- Author string `json:"author"`
- Text string `json:"text"`
-}
-
-const dataFile = "./comments.json"
-
-var commentMutex = new(sync.Mutex)
-
-// Handle comments
-func handleComments(w http.ResponseWriter, r *http.Request) {
- // Since multiple requests could come in at once, ensure we have a lock
- // around all file operations
- commentMutex.Lock()
- defer commentMutex.Unlock()
-
- // Stat the file, so we can find its current permissions
- fi, err := os.Stat(dataFile)
- if err != nil {
- http.Error(w, fmt.Sprintf("Unable to stat the data file (%s): %s", dataFile, err), http.StatusInternalServerError)
- return
- }
-
- // Read the comments from the file.
- commentData, err := ioutil.ReadFile(dataFile)
- if err != nil {
- http.Error(w, fmt.Sprintf("Unable to read the data file (%s): %s", dataFile, err), http.StatusInternalServerError)
- return
- }
-
- switch r.Method {
- case "POST":
- // Decode the JSON data
- var comments []comment
- if err := json.Unmarshal(commentData, &comments); err != nil {
- http.Error(w, fmt.Sprintf("Unable to Unmarshal comments from data file (%s): %s", dataFile, err), http.StatusInternalServerError)
- return
- }
-
- // Add a new comment to the in memory slice of comments
- comments = append(comments, comment{ID: time.Now().UnixNano() / 1000000, Author: r.FormValue("author"), Text: r.FormValue("text")})
-
- // Marshal the comments to indented json.
- commentData, err = json.MarshalIndent(comments, "", " ")
- if err != nil {
- http.Error(w, fmt.Sprintf("Unable to marshal comments to json: %s", err), http.StatusInternalServerError)
- return
- }
-
- // Write out the comments to the file, preserving permissions
- err := ioutil.WriteFile(dataFile, commentData, fi.Mode())
- if err != nil {
- http.Error(w, fmt.Sprintf("Unable to write comments to data file (%s): %s", dataFile, err), http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- w.Header().Set("Cache-Control", "no-cache")
- w.Header().Set("Access-Control-Allow-Origin", "*")
- io.Copy(w, bytes.NewReader(commentData))
-
- case "GET":
- w.Header().Set("Content-Type", "application/json")
- w.Header().Set("Cache-Control", "no-cache")
- w.Header().Set("Access-Control-Allow-Origin", "*")
- // stream the contents of the file to the response
- io.Copy(w, bytes.NewReader(commentData))
-
- default:
- // Don't know the method, so error
- http.Error(w, fmt.Sprintf("Unsupported method: %s", r.Method), http.StatusMethodNotAllowed)
- }
-}
-
-func main() {
- port := os.Getenv("PORT")
- if port == "" {
- port = "3000"
- }
- http.HandleFunc("/api/comments", handleComments)
- http.Handle("/", http.FileServer(http.Dir("./public")))
- log.Println("Server started: http://localhost:" + port)
- log.Fatal(http.ListenAndServe(":"+port, nil))
-}
diff --git a/server.js b/server.js
index b5a7218a..c8eced52 100644
--- a/server.js
+++ b/server.js
@@ -14,19 +14,46 @@ var fs = require('fs');
var path = require('path');
var express = require('express');
var bodyParser = require('body-parser');
+var swig = require('swig');
+var strategy = require('react-validatorjs-strategy');
+var schemas = require('./components/schemas');
+
+require('node-jsx').install();
+var components = require('./components/server');
+
var app = express();
+app.engine('html', swig.renderFile);
+app.set('view engine', 'html');
+app.set('views', __dirname + '/views');
+app.set('view cache', false);
+
var COMMENTS_FILE = path.join(__dirname, 'comments.json');
app.set('port', (process.env.PORT || 3000));
-app.use('/', express.static(path.join(__dirname, 'public')));
+app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
-// Additional middleware which will set headers that we need on each request.
+app.get('/', function(req, res) {
+ fs.readFile(COMMENTS_FILE, function(err, data) {
+ if (err) {
+ console.error(err);
+ process.exit(1);
+ }
+
+ res.render('index', {
+ commentBox: components.renderCommentBox({
+ comments: JSON.parse(data)
+ })
+ });
+ });
+});
+
+// Additional middleware which will set headers that we need on each /api request.
app.use(function(req, res, next) {
- // Set permissive CORS header - this allows this server to be used only as
+ // Set permissive CORS header - this allows the api routes to be used only as
// an API server in conjunction with something like webpack-dev-server.
res.setHeader('Access-Control-Allow-Origin', '*');
@@ -45,32 +72,47 @@ app.get('/api/comments', function(req, res) {
});
});
-app.post('/api/comments', function(req, res) {
- fs.readFile(COMMENTS_FILE, function(err, data) {
- if (err) {
- console.error(err);
- process.exit(1);
- }
- var comments = JSON.parse(data);
- // NOTE: In a real implementation, we would likely rely on a database or
- // some other approach (e.g. UUIDs) to ensure a globally unique id. We'll
- // treat Date.now() as unique-enough for our purposes.
- var newComment = {
- id: Date.now(),
- author: req.body.author,
- text: req.body.text,
- };
- comments.push(newComment);
- fs.writeFile(COMMENTS_FILE, JSON.stringify(comments, null, 4), function(err) {
+app.post('/api/comments', function(req, res, next) {
+ // Validate the request and if it fails, call the error handler
+ strategy.validateServer(req.body, schemas.commentForm).then(() => {
+ fs.readFile(COMMENTS_FILE, function(err, data) {
if (err) {
console.error(err);
process.exit(1);
}
- res.json(comments);
+ var comments = JSON.parse(data);
+ // NOTE: In a real implementation, we would likely rely on a database or
+ // some other approach (e.g. UUIDs) to ensure a globally unique id. We'll
+ // treat Date.now() as unique-enough for our purposes.
+ var newComment = {
+ id: Date.now(),
+ author: req.body.author,
+ text: req.body.text,
+ };
+ comments.push(newComment);
+ fs.writeFile(COMMENTS_FILE, JSON.stringify(comments, null, 4), function(err) {
+ if (err) {
+ console.error(err);
+ process.exit(1);
+ }
+ res.json(comments);
+ });
});
- });
+ })
+ .catch(next);
});
+/**
+ * If a validation error, output a 400 JSON response containing the error messages.
+ * Otherwise, use the default error handler.
+ */
+app.use(function(err, req, res, next) {
+ if (err instanceof strategy.Error) {
+ res.status(400).json(err.errors);
+ } else {
+ next(err, req, res);
+ }
+});
app.listen(app.get('port'), function() {
console.log('Server started: http://localhost:' + app.get('port') + '/');
diff --git a/server.php b/server.php
deleted file mode 100644
index 75fae215..00000000
--- a/server.php
+++ /dev/null
@@ -1,53 +0,0 @@
- round(microtime(true) * 1000),
- 'author' => $_POST['author'],
- 'text' => $_POST['text']
- ];
-
- $comments = json_encode($commentsDecoded, JSON_PRETTY_PRINT);
- file_put_contents('comments.json', $comments);
- }
- header('Content-Type: application/json');
- header('Cache-Control: no-cache');
- header('Access-Control-Allow-Origin: *');
- echo $comments;
- } else {
- return false;
- }
-}
diff --git a/server.pl b/server.pl
deleted file mode 100644
index c3212b9c..00000000
--- a/server.pl
+++ /dev/null
@@ -1,38 +0,0 @@
-# This file provided by Facebook is for non-commercial testing and evaluation
-# purposes only. Facebook reserves all rights not expressly granted.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-# FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-use Time::HiRes qw(gettimeofday);
-use Mojolicious::Lite;
-use Mojo::JSON qw(encode_json decode_json);
-
-app->static->paths->[0] = './public';
-
-any '/' => sub { $_[0]->reply->static('index.html') };
-
-any [qw(GET POST)] => '/api/comments' => sub {
- my $self = shift;
- my $comments = decode_json (do { local(@ARGV,$/) = 'comments.json';<> });
- $self->res->headers->cache_control('no-cache');
- $self->res->headers->access_control_allow_origin('*');
-
- if ($self->req->method eq 'POST')
- {
- push @$comments, {
- id => int(gettimeofday * 1000),
- author => $self->param('author'),
- text => $self->param('text'),
- };
- open my $FILE, '>', 'comments.json';
- print $FILE encode_json($comments);
- }
- $self->render(json => $comments);
-};
-my $port = $ENV{PORT} || 3000;
-app->start('daemon', '-l', "http://*:$port");
diff --git a/server.py b/server.py
deleted file mode 100644
index 5cf598df..00000000
--- a/server.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# This file provided by Facebook is for non-commercial testing and evaluation
-# purposes only. Facebook reserves all rights not expressly granted.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-# FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-import json
-import os
-import time
-from flask import Flask, Response, request
-
-app = Flask(__name__, static_url_path='', static_folder='public')
-app.add_url_rule('/', 'root', lambda: app.send_static_file('index.html'))
-
-@app.route('/api/comments', methods=['GET', 'POST'])
-def comments_handler():
-
- with open('comments.json', 'r') as file:
- comments = json.loads(file.read())
-
- if request.method == 'POST':
- newComment = request.form.to_dict()
- newComment['id'] = int(time.time() * 1000)
- comments.append(newComment)
-
- with open('comments.json', 'w') as file:
- file.write(json.dumps(comments, indent=4, separators=(',', ': ')))
-
- return Response(json.dumps(comments), mimetype='application/json', headers={'Cache-Control': 'no-cache', 'Access-Control-Allow-Origin': '*'})
-
-if __name__ == '__main__':
- app.run(port=int(os.environ.get("PORT",3000)))
diff --git a/server.rb b/server.rb
deleted file mode 100644
index 698f4339..00000000
--- a/server.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# This file provided by Facebook is for non-commercial testing and evaluation
-# purposes only. Facebook reserves all rights not expressly granted.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-# FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-require 'webrick'
-require 'json'
-
-# default port to 3000 or overwrite with PORT variable by running
-# $ PORT=3001 ruby server.rb
-port = ENV['PORT'] ? ENV['PORT'].to_i : 3000
-
-puts "Server started: http://localhost:#{port}/"
-
-root = File.expand_path './public'
-server = WEBrick::HTTPServer.new Port: port, DocumentRoot: root
-
-server.mount_proc '/api/comments' do |req, res|
- comments = JSON.parse(File.read('./comments.json', encoding: 'UTF-8'))
-
- if req.request_method == 'POST'
- # Assume it's well formed
- comment = { id: (Time.now.to_f * 1000).to_i }
- req.query.each do |key, value|
- comment[key] = value.force_encoding('UTF-8') unless key == 'id'
- end
- comments << comment
- File.write(
- './comments.json',
- JSON.pretty_generate(comments, indent: ' '),
- encoding: 'UTF-8'
- )
- end
-
- # always return json
- res['Content-Type'] = 'application/json'
- res['Cache-Control'] = 'no-cache'
- res['Access-Control-Allow-Origin'] = '*'
- res.body = JSON.generate(comments)
-end
-
-trap('INT') { server.shutdown }
-
-server.start
diff --git a/public/index.html b/views/index.html
similarity index 50%
rename from public/index.html
rename to views/index.html
index c6494446..a93558cd 100644
--- a/public/index.html
+++ b/views/index.html
@@ -4,19 +4,19 @@
React Tutorial
-
-
-
-
+
+
+
+
-
-
-
+