NodeJS, RactJS, and SQL Server sample

New sample that implements ReactJS SPA with nodejs.
This commit is contained in:
Jovan Popovic
2017-04-08 22:54:30 +02:00
parent 011a66e020
commit 1630c3ad6e
11 changed files with 442 additions and 0 deletions
@@ -0,0 +1,5 @@
node_modules/*
bin/*.dll
obj/*
*.sln
*.log
@@ -0,0 +1,21 @@
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(express.static('wwwroot'));
app.use(bodyParser.urlencoded());
app.use('/api/comments', require('./routes/comments'));
// catch 404 and forward to error handler
app.use(function (req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
app.set('port', process.env.PORT || 3000);
var server = app.listen(app.get('port'), function() {
console.log('Express server listening on port ' + server.address().port);
});
module.exports = app;
@@ -0,0 +1,3 @@
#!/usr/bin/env node
var debug = require('debug')('nodejs_express4_rest_api');
var app = require('../app');
@@ -0,0 +1,85 @@
function createConnection() {
var config = {
server : "<SERVER>",
userName: "<USERNAME>",
password: "<PASSWORD>",
// If you're on Azure, you will need this:
options: { encrypt: true, database: 'CommentsDb' }
};
var Connection = require('tedious').Connection;
var connection = new Connection(config);
return connection;
}
function createRequest(query, connection) {
var Request = require('tedious').Request;
var req =
new Request(query,
function (err, rowCount) {
if (err) {
console.trace(err);
throw err;
}
connection && connection.close();
console.log('Connection closed');
});
return req;
}
function stream (query, connection, output, defaultContent) {
var request = query;
if (typeof query == "string") {
request = createRequest(query, connection);
}
var empty = true;
request.on('row', function (columns) {
if(empty) {
console.log('Response fetched from SQL Database!');
empty = false;
}
output.write(columns[0].value);
});
request.on('done', function (rowCount, more, rows) {
_OnDone(empty, defaultContent, output);
});
request.on('doneProc', function (rowCount, more, rows) {
_OnDone(empty, defaultContent, output);
});
executeRequest (request, connection);
}
function _OnDone(empty, defaultContent, output) {
if(empty) {
output.write(defaultContent);
console.log('No results from database - default content is returned.');
}
try {
console.log('Closing Http Response output.');
output.end();
} catch (err) {
console.error(err);
}
}
function executeRequest (request, connection) {
connection.on('connect', function (err) {
if (err) {
console.trace(err);
throw err;
}
connection.execSql(request);
});
}
module.exports.createConnection = createConnection;
module.exports.createRequest = createRequest;
module.exports.executeRequest = executeRequest;
module.exports.stream = stream;
@@ -0,0 +1,19 @@
{
"name": "nodejs-express4-rest-api",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"description": "nodejs-express4-rest-api",
"author": {
"name": "Jovan Popovic",
"email": "jovanpop@microsoft.com"
},
"dependencies": {
"body-parser": "^1.15.2",
"debug": "^2.2.0",
"express": "^4.14.0",
"tedious": "^1.14.0"
}
}
@@ -0,0 +1,34 @@
var express = require('express');
var router = express.Router();
var db = require('../db.js');
var TYPES = require('tedious').TYPES;
/* GET comments. */
router.get('/', function (req, res) {
db.stream("select * from comments for json path", db.createConnection(), res, '[]');
});
/* GET single comment. */
router.get('/:id', function (req, res) {
var conn = db.createConnection();
var request = db.createRequest("select * from comments where id = @id for json path, without_array_wrapper", conn);
request.addParameter('id', TYPES.Int, req.params.id);
db.stream(request, conn, res, '{}');
});
/* POST create comment. */
router.post('/', function (req, res) {
var connection = db.createConnection();
var request = db.createRequest("insert into Comments values (@author, @text)", connection);
request.addParameter('author', TYPES.NVarChar, req.body.author);
request.addParameter('text', TYPES.NVarChar, req.body.text);
db.executeRequest(request, connection);
});
module.exports = router;
@@ -0,0 +1,47 @@
--CREATE DATABASE TodoDb;
--USE TodoDb;
DROP TABLE IF EXISTS Todo
DROP PROCEDURE IF EXISTS createTodo
DROP PROCEDURE IF EXISTS updateTodo
GO
CREATE TABLE Todo (
id int IDENTITY PRIMARY KEY,
title nvarchar(30) NOT NULL,
description nvarchar(4000),
completed bit,
dueDate datetime2 default (dateadd(day, 3, getdate()))
)
GO
INSERT INTO Todo (title, description, completed, dueDate)
VALUES
('Install SQL Server 2016','Install RTM version of SQL Server 2016', 0, '2017-03-08'),
('Get new samples','Go to github and download new samples', 0, '2016-03-09'),
('Try new samples','Install new Management Studio to try samples', 0, '2016-03-12')
GO
create procedure dbo.createTodo(@todo nvarchar(max))
as begin
insert into Todo
select *
from OPENJSON(@todo)
WITH ( title nvarchar(30), description nvarchar(4000),
completed bit, dueDate datetime2)
end
GO
create procedure updateTodo(@id int, @todo nvarchar(max))
as begin
update Todo
set title = json.title, description = json.description,
completed = json.completed, dueDate = json.dueDate
from OPENJSON( @todo )
WITH( title nvarchar(30), description nvarchar(4000),
completed bit, dueDate datetime2) AS json
where id = @id
end
go
select * from todo for json path
@@ -0,0 +1,59 @@
body {
background: #fff;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 15px;
line-height: 1.7;
margin: 0;
padding: 30px;
}
a {
color: #4183c4;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
code {
background-color: #f8f8f8;
border: 1px solid #ddd;
border-radius: 3px;
font-family: "Bitstream Vera Sans Mono", Consolas, Courier, monospace;
font-size: 12px;
margin: 0 2px;
padding: 0 5px;
}
h1, h2, h3, h4 {
font-weight: bold;
margin: 0 0 15px;
padding: 0;
}
h1 {
border-bottom: 1px solid #ddd;
font-size: 2.5em;
}
h2 {
border-bottom: 1px solid #eee;
font-size: 2em;
}
h3 {
font-size: 1.5em;
}
h4 {
font-size: 1.2em;
}
p, ul {
margin: 15px 0;
}
ul {
padding-left: 30px;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>React Tutorial</title>
<!-- Not present in the tutorial. Just for basic styling. -->
<link rel="stylesheet" href="css/base.css" />
<script src="https://npmcdn.com/react@15.3.0/dist/react.js"></script>
<script src="https://npmcdn.com/react-dom@15.3.0/dist/react-dom.js"></script>
<script src="https://npmcdn.com/babel-core@5.8.38/browser.min.js"></script>
<script src="https://npmcdn.com/jquery@3.1.0/dist/jquery.min.js"></script>
<script src="https://npmcdn.com/remarkable@1.6.2/dist/remarkable.min.js"></script>
</head>
<body>
<div id="content"></div>
<script type="text/babel" src="scripts/example.js"></script>
<script type="text/babel">
// To get started with this tutorial running your own code, simply remove
// the script tag loading scripts/example.js and start writing code here.
</script>
</body>
</html>
@@ -0,0 +1,147 @@
/**
* 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 Comment = React.createClass({
rawMarkup: function() {
var md = new Remarkable();
var rawMarkup = md.render(this.props.children.toString());
return { __html: rawMarkup };
},
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={this.rawMarkup()} />
</div>
);
}
});
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
handleCommentSubmit: function(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: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
this.setState({data: comments});
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
});
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function(comment) {
return (
<Comment author={comment.author} key={comment.id}>
{comment.text}
</Comment>
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
});
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 (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Your name"
value={this.state.author}
onChange={this.handleAuthorChange}
/>
<input
type="text"
placeholder="Say something..."
value={this.state.text}
onChange={this.handleTextChange}
/>
<input type="submit" value="Post" />
</form>
);
}
});
ReactDOM.render(
<CommentBox url="/api/comments" pollInterval={2000} />,
document.getElementById('content')
);