- 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 myapp
Create folder for models & routes:
mkdir models
mkdir routes
Create 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/views
Initialize Node project and install necessary modules:
npm init
npm i express nunjucks mongoose body-parser -S
Create 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 line
Include 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 init
Create .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 -S
In 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
Patients
andNew Patient
links. 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 byid
Patient.$save()
- to create new patientPatient.$update()
- to update existing patient byid
Patient.$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...