Skip to content

Instantly share code, notes, and snippets.

@umidjons
Last active June 17, 2024 09:33
Show Gist options
  • Save umidjons/57e0f672106feab544f246cf705f3e4f to your computer and use it in GitHub Desktop.
Save umidjons/57e0f672106feab544f246cf705f3e4f to your computer and use it in GitHub Desktop.
MEAN application example: step by step guide

Creating MEAN application: step by step guide

Table of Content

Project structure

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

Installing prerequisites

Initialize Node project and install necessary modules:

npm init
npm i express nunjucks mongoose body-parser -S

Creating server

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!.

Render pages with Nunjucks

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!.

Middleware to render *.html requests with Nunjucks

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.

Content of index.js

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...');
});

Accept data from users

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.

Content of index.js

'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...');
});

Work with MongoDB via Mongoose

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 Patient model

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');

Creating POST /patient route

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>

Creating GET /patient route

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.

Adjusting home page to list available operations

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>

Complete index.js content

'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...');
    });
});

Checking new features

  • 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

Routers

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.

Creating Patient 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;

Using Patient 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.

Complete 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
    );

    // 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...');
    });
});

Integrating AngularJS

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>

Content of 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
    );

    // 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...');
    });
});

Check UI Router

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 URL http://localhost:3000/#/patient/list, because our UI Router loads it by default.
  • Try to navigate by Patients and New 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.

Integrating $resource

After integrating $resource we will have following functions:

  • Patient.query() - to retrieve list of patients
  • Patient.get() - to retrieve one patient by id
  • Patient.$save() - to create new patient
  • Patient.$update() - to update existing patient by id
  • Patient.$delete() - to delete existing patient by id

Retrieving list of patients

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.

Content of changed files

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>

Create new patient

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.

Content of changed files

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;

Updating existing patients

Coming soon...

Deleting patient

Coming soon...

Advanced validation in models

Coming soon...

Toaster integration

Coming soon...

Searching patients

Coming soon...

User Authorization

Coming soon...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment