Reputation: 3453
I built medium sized web application in Node.js
. At first, I didn't plan it will grow until medium size and didn't know whether it will be used at all. Now, as the client's users started to use it, there is a need for the 2 additional features:
Since until now I used server-side templates (i.e. all of my API
routes responded with HTML
), I need to make huge change to support API
responding with JSON
just for mobile apps, so I've decided to make refactoring of whole app to support both of these 2 things simultaneously.
Reading through some online resources (namely Single Page App Book) and comparing available JavaScript frameworks (Angular vs Backbone vs React vs Ember), I've came to conclusions presented below. My question is, am I missing something? So, here's how I'm planning to extend my web app:
React
API
routes will still respond with HTML
and that HTML
will be server-rendered using server-side React
, but those React UI components will be also included on browser side, which will support single page application features.REST
API
, probably based on JSON API
standard for server to communicate with single page application and mobile web applications.API
routes (API
responding with pages - HTML
, and with data - JSON
) will be done using Express
router which will execute controllers (server components) for encapsulating combined manipulation of data with data access layer (server component).Mongoose
models. Since this would take longer while to implement and refactor it, I would like to be sure that I'm on the right track. Am I missing here something?
Upvotes: 2
Views: 1386
Reputation: 4945
It will make your app easier to build and maintain if you include the flux pattern. I suggest you take a look at some of the starter projects and pick one that compliments your own style. Here is an example from https://github.com/calitek/ReactPatterns React.14/ReFluxSuperAgent. It may seem complex but the pattern offers a good separation of concerns and flexibility.
server.js
'use strict';
let bodyParser = require('body-parser');
let express = require('express');
let favicon = require('serve-favicon');
let path = require('path');
let port = Number(3500);
let routes = require('./routes');
let app = express();
let server = app.listen(port);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use('/', express.static('ui-dist'));
app.use('/routes', routes);
app.use(favicon(path.join(__dirname, '..', 'ui-dist', 'img', 'favicon.ico')));
app.get('/', function(req, res){ res.sendfile(__dirname + '/index.html', [], null); });
'use strict';
let express = require('express');
let router = express.Router();
let getSetData = require('./routes/getsetdata');
router.get('/getData', function(req, res) {
let getDataDone = function(data){ res.send(data); };
getSetData.getData(getDataDone);
});
router.post('/setData', function(req, res) {
let setDataDone = function(data){ res.send(data); };
console.log(req.body);
getSetData.setData(req.body, setDataDone);
});
module.exports = router;
getsetdata.js
'use strict';
var fs = require('fs');
var rootDataPath = './data';
var getData = function(doneCallBack) {
var filePath = rootDataPath + '/basic.json';
var jsonReadCallBack = function(err, data){
if (err) doneCallBack('Data readFile error ' + filePath);
else {
var jsonData = JSON.parse(data.toString());
doneCallBack(jsonData);
}
};
fs.readFile(filePath, jsonReadCallBack);
};
var setData = function(data, doneCallBack) {
var filePath = rootDataPath + '/basic.json';
var writeFileCallBack = function (err) {
if (err) console.log('error saving Data.json file ');
doneCallBack('ok');
};
fs.writeFile(filePath, JSON.stringify(data, null, 2), writeFileCallBack);
};
module.exports.getData = getData;
module.exports.setData = setData;
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ReactPatterns-ReFluxWebSocket</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.min.css">
<link rel="stylesheet" href="app.min.css"/>
</head>
<body class="bodyStyle main">
<header class="text-center header" >
<span class="Title">ReactPatterns-ReFluxSuperAgent by Janaka</span>
</header>
<section id="react" class="content"></section>
<script src="app.min.js"></script>
</body>
</html>
app.js
'use strict';
import React from 'react';
import ReactDom from 'react-dom';
import AppCtrl from './components/app.ctrl.js';
import Actions from './flux/Actions';
import ApiStore from './flux/Api.Store';
window.ReactDom = ReactDom;
Actions.apiInit();
ReactDom.render( <AppCtrl />, document.getElementById('react') );
api.store.js
import Reflux from 'reflux';
import Actions from './Actions';
import ApiFct from './../utils/api.js';
let ApiStoreObject = {
newData: {
"React version": "0.14",
"Project": "ReFluxSuperAgent",
"currentDateTime": new Date().toLocaleString()
},
listenables: Actions,
apiInit() { ApiFct.setData(this.newData); },
apiInitDone() { ApiFct.getData(); },
apiSetData(data) { ApiFct.setData(data); }
}
const ApiStore = Reflux.createStore(ApiStoreObject);
export default ApiStore;
api.js
import request from 'superagent';
import Actions from '../flux/Actions';
let uri = 'http://localhost:3500';
module.exports = {
getData() { request.get(uri + '/routes/getData').end((err, res) => { this.gotData(res.body); }); },
gotData(data) { Actions.gotData1(data); Actions.gotData2(data); Actions.gotData3(data); },
setData(data) { request.post('/routes/setData').send(data).end((err, res) => { Actions.apiInitDone(); }) },
};
basic.store.js
import Reflux from 'reflux';
import Actions from './Actions';
import AddonStore from './Addon.Store';
import MixinStoreObject from './Mixin.Store';
function _GotData(data) { this.data1 = data; BasicStore.trigger('data1'); }
let BasicStoreObject = {
init() { this.listenTo(AddonStore, this.onAddonTrigger); },
data1: {},
listenables: Actions,
mixins: [MixinStoreObject],
onGotData1: _GotData,
onAddonTrigger() { BasicStore.trigger('data2'); },
getData1() { return this.data1; },
getData2() { return AddonStore.data2; },
getData3() { return this.data3; }
}
const BasicStore = Reflux.createStore(BasicStoreObject);
export default BasicStore;
app.ctrl.js
import React from 'react';
import BasicStore from './../flux/Basic.Store';
let AppCtrlSty = {
height: '100%',
padding: '0 10px 0 0'
}
const getState = () => {
return {
Data1: BasicStore.getData1(),
Data2: BasicStore.getData2(),
Data3: BasicStore.getData3()
};
};
class AppCtrlRender extends React.Component {
render() {
let data1 = JSON.stringify(this.state.Data1, null, 2);
let data2 = JSON.stringify(this.state.Data2, null, 2);
let data3 = JSON.stringify(this.state.Data3, null, 2);
return (
<div id='AppCtrlSty' style={AppCtrlSty}>
React 1.4 ReFlux with SuperAgent<br/><br/>
Data1: {data1}<br/><br/>
Data2: {data2}<br/><br/>
Data3: {data3}<br/><br/>
</div>
);
}
}
export default class AppCtrl extends AppCtrlRender {
constructor() {
super();
this.state = getState();
}
componentDidMount() { this.unsubscribe = BasicStore.listen(this.storeDidChange.bind(this)); }
componentWillUnmount() { this.unsubscribe(); }
storeDidChange(id) {
switch (id) {
case 'data1': this.setState({Data1: BasicStore.getData1()}); break;
case 'data2': this.setState({Data2: BasicStore.getData2()}); break;
case 'data3': this.setState({Data3: BasicStore.getData3()}); break;
default: this.setState(getState());
}
}
}
Upvotes: 1