- Project structure
- Installing prerequisites
- Creating server
- Render pages with Nunjucks
- Middleware to render *.html requests with Nunjucks
- Accept data from users
- Work with MongoDB via Mongoose
- Create Patient model
- Creating POST /patient route
- Creating GET /patient route
- Adjusting home page to list available operations
- Routers
- Creating Patient router
- Using Patient router
- Integrating AngularJS
- Integrating $resource
- Retrieving list of patients
- Create new patient
Create root folder for our application:
mkdir myapp
cd myappCreate folder for models & routes:
mkdir models
mkdir routesCreate folder for frontend:
mkdir public
mkdir public/lib
mkdir public/src
mkdir public/src/js
mkdir public/src/css
mkdir public/src/images
mkdir public/viewsInitialize Node project and install necessary modules:
npm init
npm i express nunjucks mongoose body-parser -SCreate index.js file.
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.listen(3000, function () {
console.log('Listening http://localhost:3000...');
});Run it on the console with node index.
On the console you should see text:
Listening http://localhost:3000...
Navigate to the http://localhost:3000 on the browser.
As response you should see the text Hello World!.
Add 'use strict' into beginning of the index.js to enable strict mode.
Include nunjucks to use template engine in views.
var nunjucks = require('nunjucks');Include built-in path module to work with paths:
var path = require('path');Set views path to public/views:
const VIEW_PATH = path.join(__dirname, 'public', 'views');Configure nunjucks, bind it with our express app and set view engine to it:
// configure Nunjucks templating
nunjucks.configure(
VIEW_PATH, // tell nunjucks, that all views are in views/ folder
{express: app} // tell nunjucks, that this is express app
);
// set view engine to custom extension - njk
app.set('view engine', 'njk');Change handler for root / to the following code:
app.get('/', function (req, res) {
res.render('main', {myname: 'John Doe'});
});In the above code we are rendering main.njk view with myname='John Doe'.
Create public/views/main.njk view with the following content:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Application</title>
</head>
<body>
Hello, {{ myname }}!
</body>
</html>Restart application and navigate to the home page in the browser: http://localhost:3000/.
You should see the text Hello, John Doe!.
Create middleware to render all *.html requests with *.njk files:
app.use('*.html', function (req, res) { // render all html files with Nunjucks
// show original URL in the console
console.log('Original URL: %s', req.originalUrl);
// generate path to view
let view_path = path.join(VIEW_PATH, req.originalUrl);
// replace *.html to *.njk
view_path = view_path.replace(/\..+/, '.njk');
console.log('File to render: %s', view_path);
// render template
res.render(view_path);
});Now, if we navigate to the page http://localhost:3000/patient.html, then node will render the view public/views/patient.njk with Nunjucks.
Now our index.js:
'use strict';
var express = require('express');
var app = express();
var nunjucks = require('nunjucks');
var path = require('path');
const VIEW_PATH = path.join(__dirname, 'public', 'views');
// configure Nunjucks templating
nunjucks.configure(
VIEW_PATH, // tell nunjucks, that all views are in views/ folder
{express: app} // tell nunjucks, that this is express app
);
// set view engine to custom extension - njk
app.set('view engine', 'njk');
app.use('*.html', function (req, res) { // render all html files with Nunjucks
// show original URL in the console
console.log('Original URL: %s', req.originalUrl);
// generate path to view
let view_path = path.join(VIEW_PATH, req.originalUrl);
// replace *.html to *.njk
view_path = view_path.replace(/\..+$/, '.njk');
console.log('File to render: %s', view_path);
// render template
res.render(view_path);
});
app.get('/', function (req, res) {
res.render('main', {myname: 'John Doe'});
});
app.listen(3000, function () {
console.log('Listening http://localhost:3000...');
});Include body-parser module into our application. It parses request body and makes them available via req.body.
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: false}));Also change root route handler to the following:
app.get('/', function (req, res) {
res.render('main');
});Change view main.njk to accept data from a user:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Application</title>
</head>
<body>
<form action="/patient/create" method="post">
<label>
First Name: <input type="text" name="firstName">
</label>
<label>
Last Name: <input type="text" name="lastName">
</label>
<button>Create New Patient</button>
</form>
</body>
</html>It will send POST request to a route /patient/create, so let's create route handler for it:
app.post('/patient/create', function (req, res) {
console.log('Request body:', req.body);
res.render('info', {message: 'Patient successfully created.', url: '/'});
});For now, the handler shows parsed request body on the console, also renders a view public/views/info.njk with a given message and return URL. Let's create that view:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Application | Info</title>
</head>
<body>
<h3>Message</h3>
<p>{{ message }}</p>
<a href="{{ url }}">Back</a>
</body>
</html>Restart the application and navigate to the home page. Enter first and last names and submit the form. Application will render info page with message and the URL to return.
'use strict';
var express = require('express');
var app = express();
var nunjucks = require('nunjucks');
var path = require('path');
var bodyParser = require('body-parser');
const VIEW_PATH = path.join(__dirname, 'public', 'views');
// configure Nunjucks templating
nunjucks.configure(
VIEW_PATH, // tell nunjucks, that all views are in views/ folder
{express: app} // tell nunjucks, that this is express app
);
// set view engine to custom extension - njk
app.set('view engine', 'njk');
app.use(bodyParser.urlencoded({extended: false}));
app.use('*.html', function (req, res) { // render all html files with Nunjucks
// show original URL in the console
console.log('Original URL: %s', req.originalUrl);
// generate path to view
let view_path = path.join(VIEW_PATH, req.originalUrl);
// replace *.html to *.njk
view_path = view_path.replace(/\..+$/, '.njk');
console.log('File to render: %s', view_path);
// render template
res.render(view_path);
});
app.get('/', function (req, res) {
res.render('main');
});
app.post('/patient/create', function (req, res) {
console.log('Request body:', req.body);
res.render('info', {message: 'Patient successfully created.', url: '/'});
});
app.listen(3000, function () {
console.log('Listening http://localhost:3000...');
});NOTE: Make sure you have running MongoDB!
Include mongoose into our application:
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/mydb');
var db = mongoose.connection;Move all code into open event handler, it means our server will start after connection established to the DB:
db.once('open', function () {
const VIEW_PATH = path.join(__dirname, 'public', 'views');
// configure Nunjucks templating
nunjucks.configure(
VIEW_PATH, // tell nunjucks, that all views are in views/ folder
{express: app} // tell nunjucks, that this is express app
);
// set view engine to custom extension - njk
app.set('view engine', 'njk');
app.use(bodyParser.urlencoded({extended: false}));
app.use('*.html', function (req, res) { // render all html files with Nunjucks
// show original URL in the console
console.log('Original URL: %s', req.originalUrl);
// generate path to view
let view_path = path.join(VIEW_PATH, req.originalUrl);
// replace *.html to *.njk
view_path = view_path.replace(/\..+$/, '.njk');
console.log('File to render: %s', view_path);
// render template
res.render(view_path);
});
app.get('/', function (req, res) {
res.render('main');
});
app.listen(3000, function () {
console.log('Listening http://localhost:3000...');
});
});Create models/Patient.js with the following content:
var mongoose = require('mongoose');
var PatientSchema = mongoose.Schema({
firstName: {type: String, required: true, maxlength: 50},
lastName: {type: String, required: true, maxlength: 50}
});
var Patient = mongoose.model('Patient', PatientSchema);
module.exports = Patient;Create models/index.js file with the following content:
var PatientModel = require('./Patient');
//var UserModel = require('./User');
module.exports.Patient = PatientModel;
//module.exports.User = UserModel;We need models/index.js to include all models at once. In it we can list other models.
Include our models into index.js (our application entry point) file:
var models = require('./models');This route will create new patient and save it into DB.
Remove /patient/create route and create post /patient route with the following handler:
app.post('/patient', function (req, res) {
console.log('Request body:', req.body);
// create model and fill fields from request body
let newPatient = new models.Patient(req.body);
// try to save patient
newPatient.save(function (err) {
// if there is error, show it and stop handler with return
if (err) {
return res.render('info', {message: 'Error occured: ' + err.message, url: '/'});
}
// all right, show success message
res.render('info', {message: 'Patient successfully created.', url: '/'});
});
});Above handler will try to save a patient and shows appropriate message (error or success).
Move patient creation form into own view public/views/create.njk:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Application | New Patient</title>
</head>
<body>
<a href="/">Back</a>
<h3>New Patient</h3>
<form action="/patient" method="post">
<label>
First Name: <input type="text" name="firstName">
</label>
<label>
Last Name: <input type="text" name="lastName">
</label>
<button>Create New Patient</button>
</form>
</body>
</html>This route will show list of existing patients.
Create new route with the following handler:
app.get('/patient', function (req, res) {
models.Patient.find(function (err, patients) {
if (err) {
return res.render('info', {message: 'Error occured: ' + err.message, url: '/'});
}
console.log('List of patients:', patients);
res.render('patients', {patients: patients});
});
});Create new view for it public/views/patients.njk:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Application | Patients</title>
</head>
<body>
<a href="/">Back</a>
<h3>Patients</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>First Name</th>
<th>Last Name</th>
<th></th>
</tr>
</thead>
<tbody>
{% for patient in patients %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ patient.firstName }}</td>
<td>{{ patient.lastName }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>Here we use for/endfor tag of Nunjucks.
Replace the content of the public/views/main.njk with this:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Application</title>
</head>
<body>
<ul>
<li><a href="/patient">Patients</a></li>
<li><a href="/create.html">New Patient</a></li>
</ul>
</body>
</html>'use strict';
var express = require('express');
var app = express();
var nunjucks = require('nunjucks');
var path = require('path');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var models = require('./models');
mongoose.connect('mongodb://localhost:27017/mydb');
var db = mongoose.connection;
db.once('open', function () {
const VIEW_PATH = path.join(__dirname, 'public', 'views');
// configure Nunjucks templating
nunjucks.configure(
VIEW_PATH, // tell nunjucks, that all views are in views/ folder
{express: app} // tell nunjucks, that this is express app
);
// set view engine to custom extension - njk
app.set('view engine', 'njk');
app.use(bodyParser.urlencoded({extended: false}));
app.use('*.html', function (req, res) { // render all html files with Nunjucks
// show original URL in the console
console.log('Original URL: %s', req.originalUrl);
// generate path to view
let view_path = path.join(VIEW_PATH, req.originalUrl);
// replace *.html to *.njk
view_path = view_path.replace(/\..+$/, '.njk');
console.log('File to render: %s', view_path);
// render template
res.render(view_path);
});
app.get('/', function (req, res) {
res.render('main');
});
app.get('/patient', function (req, res) {
models.Patient.find(function (err, patients) {
if (err) {
return res.render('info', {message: 'Error occured: ' + err.message, url: '/'});
}
console.log('List of patients:', patients);
res.render('patients', {patients: patients});
});
});
app.post('/patient', function (req, res) {
console.log('Request body:', req.body);
// create model and fill fields from request body
let newPatient = new models.Patient(req.body);
// try to save patient
newPatient.save(function (err) {
// if there is error, show it and stop handler with return
if (err) {
return res.render('info', {message: 'Error occured: ' + err.message, url: '/'});
}
// all right, show success message
res.render('info', {message: 'Patient successfully created.', url: '/'});
});
});
app.listen(3000, function () {
console.log('Listening http://localhost:3000...');
});
});- Restart the application.
- Navigate to the home page in the browser.
- Try to create new patient
- Submit the form without filling fields, you should see error message;
- Submit the form with correct data, you should see success message;
- Navigate to Patients page, you should see list of existing patients
If we have /patient and /user routes keeping all route handlers in index.js is not the best practice.
To improve maintainability of our code base we will move group of routes into own router.
Create routes/Patient.js file. Create router object and include our models too.
'use strict';
var router = require('express').Router();
var models = require('../models');Move .get('/patient') and .post(/patient) handlers into routes/Patient.js. Replace app. with router. and /patient with /. Also export router instance.
router
.get('/', function (req, res) {
models.Patient.find(function (err, patients) {
if (err) {
return res.render('info', {message: 'Error occured: ' + err.message, url: '/'});
}
console.log('List of patients:', patients);
res.render('patients', {patients: patients});
});
})
.post('/', function (req, res) {
console.log('Request body:', req.body);
// create model and fill fields from request body
let newPatient = new models.Patient(req.body);
// try to save patient
newPatient.save(function (err) {
// if there is error, show it and stop handler with return
if (err) {
return res.render('info', {message: 'Error occured: ' + err.message, url: '/'});
}
// all right, show success message
res.render('info', {message: 'Patient successfully created.', url: '/'});
});
});
module.exports = router;Create routes/index.js with the following content:
var PatientRouter = require('./Patient');
// var UserRouter = require('./user');
module.exports.Patient = PatientRouter;
// module.exports.User = UserRouter;It allows us to include all routers with one require statement.
Complete routes/Patient.js:
'use strict';
var router = require('express').Router();
var models = require('../models');
router
.get('/', function (req, res) {
models.Patient.find(function (err, patients) {
if (err) {
return res.render('info', {message: 'Error occured: ' + err.message, url: '/'});
}
console.log('List of patients:', patients);
res.render('patients', {patients: patients});
});
})
.post('/', function (req, res) {
console.log('Request body:', req.body);
// create model and fill fields from request body
let newPatient = new models.Patient(req.body);
// try to save patient
newPatient.save(function (err) {
// if there is error, show it and stop handler with return
if (err) {
return res.render('info', {message: 'Error occured: ' + err.message, url: '/'});
}
// all right, show success message
res.render('info', {message: 'Patient successfully created.', url: '/'});
});
});
module.exports = router;Remove including models from index.js
var models = require('./models'); // remove this lineInclude router into index.js
var routers = require('./routes');Replace app.get('/patient', ...) and app.post('/patient', ...) with this line:
app.use('/patient', routers.Patient);It means, use routers.Patient router for all routes starting with /patient URL.
That's why we replaced /patient to / in get/post router handlers.
'use strict';
var express = require('express');
var app = express();
var nunjucks = require('nunjucks');
var path = require('path');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var routers = require('./routes');
mongoose.connect('mongodb://localhost:27017/mydb');
var db = mongoose.connection;
db.once('open', function () {
const VIEW_PATH = path.join(__dirname, 'public', 'views');
// configure Nunjucks templating
nunjucks.configure(
VIEW_PATH, // tell nunjucks, that all views are in views/ folder
{express: app} // tell nunjucks, that this is express app
);
// set view engine to custom extension - njk
app.set('view engine', 'njk');
app.use(bodyParser.urlencoded({extended: false}));
app.use('*.html', function (req, res) { // render all html files with Nunjucks
// show original URL in the console
console.log('Original URL: %s', req.originalUrl);
// generate path to view
let view_path = path.join(VIEW_PATH, req.originalUrl);
// replace *.html to *.njk
view_path = view_path.replace(/\..+$/, '.njk');
console.log('File to render: %s', view_path);
// render template
res.render(view_path);
});
app.get('/', function (req, res) {
res.render('main');
});
app.use('/patient', routers.Patient);
app.listen(3000, function () {
console.log('Listening http://localhost:3000...');
});
});Initialize bower:
bower initCreate .bowerrc file on root path with the following content:
{
"directory": "public/lib"
}Install necessary libraries:
bower install angularjs -S
bower install angular-ui-router -S
bower install angular-resource -SIn index.js after app.set('view engine', ...); add following line:
app.use('/assets', express.static(path.join(__dirname, 'public')));It will serve assets from public directory. For example, /assets/js/angular.js means download it from /public/js/angular.js.
Include libraries into public/views/main.njk just before the closing </body> tag:
<script src="/assets/lib/angular/angular.min.js"></script>
<script src="/assets/lib/angular-ui-router/release/angular-ui-router.min.js"></script>
<script src="/assets/lib/angular-resource/angular-resource.min.js"></script>
</body>Create public/src/js/app.js with the following content:
angular.module('MyApp', ['ngResource', 'ui-router'])
.controller('PatientCtrl', function ($scope) {
});This is our angular application. In it we require ngResource and ui-router modules.
Include it in the public/views/main.njk too just before closing body tag.
<script src="/assets/src/js/app.js"></script>Convert our main page into angular application putting ng-app directive into <html> tag:
<html lang="en" ng-app="MyApp">We will change public/views/main.njk to show menu and container for views:
<ul>
<li><a ui-sref="patientList">Patients</a></li>
<li><a ui-sref="patientCreate">New Patient</a></li>
</ul>
<ui-view/>Values in the ui-sref attributes are states of UI Router.
<ui-view/> is the container for our partial views. When states changed partials loads into that container.
Now let's create/configure states in public/src/js/app.js:
angular.module('MyApp', ['ngResource', 'ui.router'])
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('patientList', {
url: '/patient/list', // URL to show in the address bar
templateUrl: '/patients.html', // partial view to load into <ui-view/>
controller: 'PatientsCtrl' // controller to use in that partial view
})
.state('patientCreate', {
url: '/patient/create',
templateUrl: '/create.html',
controller: 'PatientCtrl'
});
// by default go to this address (patientList state)
$urlRouterProvider.otherwise('/patient/list');
})
.controller('PatientCtrl', function ($scope) {
})
.controller('PatientsCtrl', function ($scope) {
});As you know, our views public/views/patients.njk and public/views/create.njk are partials, not full HTML source, so we leave only necessary parts of them and delete unnecessary HTML tags.
File public/views/patients.njk:
<h3>Patients</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>First Name</th>
<th>Last Name</th>
<th></th>
</tr>
</thead>
<tbody>
{% for patient in patients %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ patient.firstName }}</td>
<td>{{ patient.lastName }}</td>
</tr>
{% endfor %}
</tbody>
</table>File public/views/create.njk:
<h3>New Patient</h3>
<form action="/patient" method="post">
<label>
First Name: <input type="text" name="firstName">
</label>
<label>
Last Name: <input type="text" name="lastName">
</label>
<button>Create New Patient</button>
</form>File public/views/main.njk:
<!doctype html>
<html lang="en" ng-app="MyApp">
<head>
<meta charset="UTF-8">
<title>My Application</title>
</head>
<body>
<ul>
<li><a ui-sref="patientList">Patients</a></li>
<li><a ui-sref="patientCreate">New Patient</a></li>
</ul>
<ui-view/>
<script src="/assets/lib/angular/angular.min.js"></script>
<script src="/assets/lib/angular-ui-router/release/angular-ui-router.min.js"></script>
<script src="/assets/lib/angular-resource/angular-resource.min.js"></script>
<script src="/assets/src/js/app.js"></script>
</body>
</html>'use strict';
var express = require('express');
var app = express();
var nunjucks = require('nunjucks');
var path = require('path');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var routers = require('./routes');
mongoose.connect('mongodb://localhost:27017/mydb');
var db = mongoose.connection;
db.once('open', function () {
const VIEW_PATH = path.join(__dirname, 'public', 'views');
// configure Nunjucks templating
nunjucks.configure(
VIEW_PATH, // tell nunjucks, that all views are in views/ folder
{express: app} // tell nunjucks, that this is express app
);
// set view engine to custom extension - njk
app.set('view engine', 'njk');
app.use('/assets', express.static(path.join(__dirname, 'public')));
app.use(bodyParser.urlencoded({extended: false}));
app.use('*.html', function (req, res) { // render all html files with Nunjucks
// show original URL in the console
console.log('Original URL: %s', req.originalUrl);
// generate path to view
let view_path = path.join(VIEW_PATH, req.originalUrl);
// replace *.html to *.njk
view_path = view_path.replace(/\..+$/, '.njk');
console.log('File to render: %s', view_path);
// render template
res.render(view_path);
});
app.get('/', function (req, res) {
res.render('main');
});
app.use('/patient', routers.Patient);
app.listen(3000, function () {
console.log('Listening http://localhost:3000...');
});
});For now you cannot create new patient. Also patients page does not show list of patients. But navigation should work. We will implement creating new patients and showing list of patients features later.
- Restart the application, then navigate to
http://localhost:3000. In the address bar you should see the URLhttp://localhost:3000/#/patient/list, because our UI Router loads it by default. - Try to navigate by
PatientsandNew Patientlinks. On the page only URL on the address bar and container for our views should be changed. Navigation links should remain as is.
After integrating $resource we will have following functions:
Patient.query()- to retrieve list of patientsPatient.get()- to retrieve one patient byidPatient.$save()- to create new patientPatient.$update()- to update existing patient byidPatient.$delete()- to delete existing patient byid
Create factory to work with Patient model in public/src/js/app.js:
angular.module('MyApp', ['ngResource', 'ui.router'])
.config(function ($stateProvider, $urlRouterProvider) {
// ... doesn't changed ...
})
.factory('Patient', function ($resource) {
return $resource(
'/patient/:id', // URL to patient backend API
{id: '@_id'}, // obtain id from _id field of patient object
{
update: {
method: 'PUT' // for .update() method use PUT request
}
}
);
})
.controller('PatientCtrl', function ($scope) {
})
.controller('PatientsCtrl', function ($scope, Patient) {
$scope.patients = Patient.query(); // get list of patients
});Change public/views/patients.njk:
<h3>Patients</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>First Name</th>
<th>Last Name</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="patient in patients">
<td>{{ $index+1 }}</td>
<td>{{ patient.firstName }}</td>
<td>{{ patient.lastName }}</td>
</tr>
</tbody>
</table>We removed {%for/endfor%} nunjucks tags.
Instead used angular's ng-repeat tag.
Nunjucsk uses {{ }} tags to render values, Angular uses same syntax, so there is conflict.
We have to configure Nunjucks's tags to something else, for example, to {{{ }}}, change nunjucks.configure(...) in index.js to the following:
// configure Nunjucks templating
nunjucks.configure(
VIEW_PATH, // tell nunjucks, that all views are in views/ folder
{
express: app, // tell nunjucks, that this is express app
tags: {
variableStart: '{{{',
variableEnd: '}}}'
}
}
);Add body parser for JSON format in index.js after app.use(bodyParser.urlencoded({extended: false})); call:
app.use(bodyParser.json());Change GET / request handler in routes/Patient.js to response in JSON format, instead of rendering HTML page:
router
.get('/', function (req, res) {
models.Patient.find(function (err, patients) {
if (err) {
return res.json({message: 'Error occured: ' + err.message});
}
console.log('List of patients:', patients);
res.json(patients);
});
})Restart the application and test patients list page. It should show the list of patients.
File index.js:
'use strict';
var express = require('express');
var app = express();
var nunjucks = require('nunjucks');
var path = require('path');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var routers = require('./routes');
mongoose.connect('mongodb://localhost:27017/mydb');
var db = mongoose.connection;
db.once('open', function () {
const VIEW_PATH = path.join(__dirname, 'public', 'views');
// configure Nunjucks templating
nunjucks.configure(
VIEW_PATH, // tell nunjucks, that all views are in views/ folder
{
express: app, // tell nunjucks, that this is express app
tags: {
variableStart: '{{{',
variableEnd: '}}}'
}
}
);
// set view engine to custom extension - njk
app.set('view engine', 'njk');
app.use('/assets', express.static(path.join(__dirname, 'public')));
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
app.use('*.html', function (req, res) { // render all html files with Nunjucks
// show original URL in the console
console.log('Original URL: %s', req.originalUrl);
// generate path to view
let view_path = path.join(VIEW_PATH, req.originalUrl);
// replace *.html to *.njk
view_path = view_path.replace(/\..+$/, '.njk');
console.log('File to render: %s', view_path);
// render template
res.render(view_path);
});
app.get('/', function (req, res) {
res.render('main');
});
app.use('/patient', routers.Patient);
app.listen(3000, function () {
console.log('Listening http://localhost:3000...');
});
});File routes/Patient.js:
'use strict';
var router = require('express').Router();
var models = require('../models');
router
.get('/', function (req, res) {
models.Patient.find(function (err, patients) {
if (err) {
return res.json({message: 'Error occured: ' + err.message});
}
console.log('List of patients:', patients);
res.json(patients);
});
})
.post('/', function (req, res) {
console.log('Request body:', req.body);
// create model and fill fields from request body
let newPatient = new models.Patient(req.body);
// try to save patient
newPatient.save(function (err) {
// if there is error, show it and stop handler with return
if (err) {
return res.render('info', {message: 'Error occured: ' + err.message, url: '/'});
}
// all right, show success message
res.render('info', {message: 'Patient successfully created.', url: '/'});
});
});
module.exports = router;File public/src/js/app.js:
angular.module('MyApp', ['ngResource', 'ui.router'])
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('patientList', {
url: '/patient/list', // URL to show in the address bar
templateUrl: '/patients.html', // partial view to load into <ui-view/>
controller: 'PatientsCtrl' // controller to use in that partial view
})
.state('patientCreate', {
url: '/patient/create',
templateUrl: '/create.html',
controller: 'PatientCtrl'
});
// by default go to this address (patientList state)
$urlRouterProvider.otherwise('/patient/list');
})
.factory('Patient', function ($resource) {
return $resource(
'/patient/:id', // URL to patient backend API
{id: '@_id'}, // obtain id from _id field of patient object
{
update: {
method: 'PUT' // for .update() method use PUT request
}
}
);
})
.controller('PatientCtrl', function ($scope) {
})
.controller('PatientsCtrl', function ($scope, Patient) {
$scope.patients = Patient.query(); // get list of patients
});File public/views/patients.njk:
<h3>Patients</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>First Name</th>
<th>Last Name</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="patient in patients">
<td>{{ $index+1 }}</td>
<td>{{ patient.firstName }}</td>
<td>{{ patient.lastName }}</td>
</tr>
</tbody>
</table>To actually create new patient we need to implement functionality in the PatientCtrl controller in public/src/js/app.js. Change PatientCtrl to the following:
angular.module('MyApp', ['ngResource', 'ui.router'])
// ...
.controller('PatientCtrl', function ($scope, Patient, $state) {
$scope.patient = new Patient(); // create empty patient object
$scope.addPatient = function () {
// send ajax request to create new patient
$scope.patient.$save(function (resp) {
console.log('Save response:', resp);
// after save go to the patients list
$state.go('patientList');
});
}
})
// ...We inject Patient service into the controller to access its methods. We also inject $state provider to programmatically navigate between states. We use $state.go('patientList') to show Patients list page.
We will change our form in public/views/create.njk to angular form:
<h3>New Patient</h3>
<form name="formPatient" novalidate>
<label>
First Name: <input type="text" name="firstName" ng-model="patient.firstName" required>
</label>
<label>
Last Name: <input type="text" name="lastName" ng-model="patient.lastName" required>
</label>
<button ng-click="addPatient()" ng-disabled="formPatient.$invalid">Create New Patient</button>
</form>novalidate attribute turn off HTML5 built in validations, AngularJS has own validations, so we don't need them. We also set name attribute of the form, then we can use it, for example, to enable/disable button according to the form state, if form is invalid (formPatient.$invalid == true), then we will disable Create New Patient button, if form is valid (formPatient.$invalid == false) then enable that button. To enable/disable the button we use ng-disabled attribute, if it is true, then button will be disabled, otherwise it will be enabled.
ng-model attribute will bind input to the $scope.patient object's fields.
After filling all fields with valid values button will be enabled and we can click it.
ng-click will bind $scope.addPatient() function to the button.
Now we should change our backend to respond with JSON, instead of HTML, so we have to use res.json() instead of res.render(). Open routes/Patient.js and change POST / handler to the following code:
router
// ...
.post('/', function (req, res) {
console.log('Request body:', req.body);
// create model and fill fields from request body
let newPatient = new models.Patient(req.body);
// try to save patient
newPatient.save(function (err) {
// if there is error, send it and stop handler with return
if (err) {
return res.json({message: 'Error occured: ' + err.message});
}
// all right, show success message
res.json({message: 'Patient successfully created.'});
});
});Now restart the app and try to create some new patients.
File public/src/js/app.js:
angular.module('MyApp', ['ngResource', 'ui.router'])
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('patientList', {
url: '/patient/list', // URL to show in the address bar
templateUrl: '/patients.html', // partial view to load into <ui-view/>
controller: 'PatientsCtrl' // controller to use in that partial view
})
.state('patientCreate', {
url: '/patient/create',
templateUrl: '/create.html',
controller: 'PatientCtrl'
});
// by default go to this address (patientList state)
$urlRouterProvider.otherwise('/patient/list');
})
.factory('Patient', function ($resource) {
return $resource(
'/patient/:id', // URL to patient backend API
{id: '@_id'}, // obtain id from _id field of patient object
{
update: {
method: 'PUT' // for .update() method use PUT request
}
}
);
})
.controller('PatientCtrl', function ($scope, Patient, $state) {
$scope.patient = new Patient(); // create empty patient object
$scope.addPatient = function () {
// send ajax request to create new patient
$scope.patient.$save(function (resp) {
console.log('Save response:', resp);
// after save go to the patients list
$state.go('patientList');
});
}
})
.controller('PatientsCtrl', function ($scope, Patient) {
$scope.patients = Patient.query(); // get list of patients
});File public/views/create.njk:
<h3>New Patient</h3>
<form name="formPatient" novalidate>
<label>
First Name: <input type="text" name="firstName" ng-model="patient.firstName" required>
</label>
<label>
Last Name: <input type="text" name="lastName" ng-model="patient.lastName" required>
</label>
<button ng-click="addPatient()" ng-disabled="formPatient.$invalid">Create New Patient</button>
</form>File routes/Patient.js:
'use strict';
var router = require('express').Router();
var models = require('../models');
router
.get('/', function (req, res) {
models.Patient.find(function (err, patients) {
if (err) {
return res.json({message: 'Error occured: ' + err.message});
}
console.log('List of patients:', patients);
res.json(patients);
});
})
.post('/', function (req, res) {
console.log('Request body:', req.body);
// create model and fill fields from request body
let newPatient = new models.Patient(req.body);
// try to save patient
newPatient.save(function (err) {
// if there is error, send it and stop handler with return
if (err) {
return res.json({message: 'Error occured: ' + err.message});
}
// all right, show success message
res.json({message: 'Patient successfully created.'});
});
});
module.exports = router;Coming soon...
Coming soon...
Coming soon...
Coming soon...
Coming soon...
Coming soon...