【问题标题】:Single page app with REST API architecture具有 REST API 架构的单页应用程序
【发布时间】:2016-02-09 10:44:52
【问题描述】:
我在Node.js 中构建了中型Web 应用程序。起初,我并没有计划它会长到中等大小,也不知道它是否会被使用。现在,随着客户的用户开始使用它,需要另外两个功能:
由于直到现在我使用服务器端模板(即我所有的API 路由都以HTML 响应),我需要做出巨大的改变以支持API 以JSON 响应仅用于移动应用程序,所以我决定重构整个应用程序以同时支持这两件事。
阅读了一些在线资源(即Single Page App Book)并比较了可用的JavaScript 框架(Angular vs Backbone vs React vs Ember),我得出了以下结论。我的问题是,我错过了什么吗?所以,这就是我打算如何扩展我的网络应用程序:
- 我将在
React 中重写我所有的 UI 组件
- 所有当前的
API 路由仍会以HTML 响应,并且HTML 将使用服务器端server-rendered 响应server-rendered,但这些React UI 组件也将包含在浏览器端,这将支持单页应用程序功能。
- 我会写专门的
RESTAPI,可能是基于JSON API服务器与单页应用程序和移动Web应用程序通信的标准。
-
API 路由(API 响应页面 - HTML 和数据 - JSON)都将使用 Express 路由器完成,该路由器将执行 控制器(服务器组件) 用于封装与数据访问层(服务器组件)的数据组合操作。
-
数据访问层基本上由
Mongoose 模型组成。
由于实施和重构它需要更长的时间,我想确保我走在正确的轨道上。我在这里错过了什么吗?
【问题讨论】:
标签:
javascript
node.js
api
reactjs
【解决方案1】:
如果您包含通量模式,它将使您的应用更易于构建和维护。我建议你看看一些入门项目,然后选择一个符合你自己风格的项目。这是来自https://github.com/calitek/ReactPatternsReact.14/ReFluxSuperAgent 的示例。它可能看起来很复杂,但该模式提供了良好的关注点和灵活性分离。
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); });
路由.js
'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());
}
}
}