/angularjs/

Generic CRUD in AngularJS and ExpressJS

2014-08-24 03:11:54

GitHub

This time we will play with:

In this post, you will learn:

  • how to create AngularJS generic CRUD directive
  • how to create Generic REST API for entities
  • and, of course... MongoDB

How it looks like ?

http://localhost:8080/api/article

{
    config: {
        headers: [
            "_id",
            "Name",
            "Description"
        ]
    },
    rows: [
        {
            _id: "53f937a7bf96d76c2a000007",
            desc: "Ipsum1",
            name: "Lorem1"
        },
        {
            _id: "53f93c89fd03421d2d000001",
            desc: "Ipsum2",
            name: "Lorem2"
        }
    ]
}

http://localhost:8080/api/article/id/53f937a7bf96d76c2a000007

{
    config: {
        fields: [
            {
                name: "_id",
                type: "hidden",
                label: "_id"
            },
            {
                name: "name",
                type: "text",
                label: "Name"
            },
            {
                name: "desc",
                type: "text",
                label: "Description"
            }
        ]
    },
    document: {
        _id: "53f937a7bf96d76c2a000007",
        desc: "Ipsum1",
        name: "Lorem1",
        __v: 0
    }
}

First you have to add html element to page. (Frontend site)


<crud collection-name='"article"'></crud>

<crud collection-name='"post"'></crud>

Next you have to create file describes the entity. (Backend site)

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

module.exports.model = mongoose.model('Article', new Schema({
    name: String,
    desc: String
}));

module.exports.form = [
    {
        "name": "_id",
        "type": "hidden",
        "label": "_id"
    },
    {
        "name": "name",
        "type": "text",
        "label": "Name"
    },
    {
        "name": "desc",
        "type": "text",
        "label": "Description"
    }
];

module.exports.list = ["_id", "Name", "Description"];

And this is all!

angular.module('crudApp', [])

    .factory('crudService', function ($http, $q) {

        var URL = {
            _items: function (collectionName) {
                return '/api/{collectionName}'.replace("{collectionName}", collectionName);
            },
            _item: function (collectionName, _id) {
                return '/api/{collectionName}/id/{_id}'.replace("{collectionName}", collectionName).replace("{_id}", _id);
            },
            _form: function (collectionName, _id) {
                return '/api/{collectionName}/form'.replace("{collectionName}", collectionName);
            }
        };

        var getItems = function (collectionName) {

            var deferred = $q.defer();

            $http({method: 'GET', url: URL._items(collectionName)}).success(function (data) {
                deferred.resolve(data);
            });

            return deferred.promise;
        };

        var getItem = function (collectionName, _id) {

            var deferred = $q.defer();

            $http({method: 'GET', url: URL._item(collectionName, _id)}).success(function (data) {
                deferred.resolve({
                    fields: data.config.fields,
                    document: data.document
                });
            });

            return deferred.promise;
        };

        var getForm = function (collectionName) {

            var deferred = $q.defer();

            $http({method: 'GET', url: URL._form(collectionName)}).success(function (data) {
                deferred.resolve({
                    fields: data.config.fields,
                    document: data.document
                });
            });

            return deferred.promise;
        };

        var saveItem = function (collectionName, document, callback) {
            $http.post(URL._items(collectionName), document).success(function (data) {
                callback(data);
            });
        };

        var updateItem = function (collectionName, document, callback) {
            $http.put(URL._item(collectionName, document._id), document).success(function (data) {
                callback(data);
            });
        };

        var removeItem = function (collectionName, _id, callback) {
            $http.delete(URL._item(collectionName, _id)).success(function (data) {
                callback(data);
            });
        };

        return{
            getItems: getItems,
            getItem: getItem,
            saveItem: saveItem,
            updateItem: updateItem,
            removeItem: removeItem,
            getForm: getForm
        }
    })

    .directive('crud', function (crudService) {
        return {
            restrict: 'E',
            templateUrl: 'app/tpl/crud.html',
            scope: {
                collectionName: '@'
            },
            controller: function ($scope, $timeout) {

                var setMessage = function(message){
                    $scope.message = message;
                    $timeout(function() {
                        $scope.message = null;
                    }, 1000);
                };

                $scope.actionCreate = function () {
                    $scope.viewMode = 'create';

                    crudService.getForm($scope.collectionName).then(function (details) {
                        $scope.details = details;
                    });
                };

                $scope.actionList = function () {
                    $scope.viewMode = 'list';

                    crudService.getItems($scope.collectionName).then(function (list) {
                        $scope.list = list;
                    });
                };

                $scope.actionDetails = function (id) {

                    $scope.viewMode = 'details';

                    crudService.getItem($scope.collectionName, id).then(function (details) {
                        $scope.details = details;
                    });
                };

                $scope.actionSave = function () {
                    crudService.saveItem($scope.collectionName, $scope.details.document, function (data) {
                        setMessage(data.message);
                        $scope.actionList();
                    });
                };

                $scope.actionUpdate = function () {
                    crudService.updateItem($scope.collectionName, $scope.details.document, function (data) {
                        setMessage(data.message);
                        $scope.actionList();
                    });
                };

                $scope.actionRemove = function (_id) {
                    crudService.removeItem($scope.collectionName, _id, function (data) {
                        setMessage(data.message);
                        $scope.actionList();
                    });
                };

                $scope.actionList();
            }
        }
    })
;

Server main file (server.js)

var express = require('express');
var app = express();
var bodyParser = require('body-parser');

require('./server/config/mongoose').initDb();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

app.use(express.static(__dirname + '/public'));

app.use('/api', require('./server/route').init());

app.listen(8080);
console.log('==&gt; goto: http://localhost:' + 8080);

entityManager.js

var model = {};
var form = {};
var list = {};

module.exports.getModel = function (collectionName) {
    if (model[collectionName] == undefined) {
        model[collectionName] = require('./entity/' + collectionName).model;
    }
    return model[collectionName];
};

module.exports.getList = function (collectionName) {
    if (list[collectionName] == undefined) {
        list[collectionName] = require('./entity/' + collectionName).list;
    }
    return list[collectionName];
};

module.exports.getForm = function (collectionName) {
    if (form[collectionName] == undefined) {
        form[collectionName] = require('./entity/' + collectionName).form;
    }
    return form[collectionName];
};

Generic route (route.js)

var express = require('express');

var Entity = require('./entityManager');

module.exports.init = function () {

    var routerApi = express.Router();

    // CREATE NEW ITEM
    routerApi.route('/:collectionName').post(function (req, res) {

        var collectionName = req.params.collectionName;

        var entity = new Entity.getModel(collectionName)();

        Entity.getForm(collectionName).forEach(function (item) {
            if(item.name == '_id') return;
            entity[item.name] = req.body[item.name];
        });

        entity.save(function (err) {
            if (err) {
                res.send(err);
            }
            res.json({ message: 'entity created!' });
        });

    });

    // GET ALL ITEMS
    routerApi.route('/:collectionName').get(function (req, res) {

        var collectionName = req.params.collectionName;

        Entity.getModel(collectionName).find().select('-__v').exec(function (err, article) {
            if (err) {
                res.send(err);
            }
            res.json({
                "config": {
                    "headers": Entity.getList(collectionName)
                },
                "rows": article
            });
        });
    });

    // GET ONE ITEM
    routerApi.route('/:collectionName/id/:id').get(function (req, res) {

        var collectionName = req.params.collectionName;

        Entity.getModel(collectionName).findById(req.params.id, function (err, entity) {
            if (err) {
                res.send(err);
            }
            res.json({
                "config": {
                    "fields": Entity.getForm(collectionName)
                },
                "document": entity
            });
        });
    });

    // GET FORM FOR ITEM
    routerApi.route('/:collectionName/form').get(function (req, res) {

        var collectionName = req.params.collectionName;

        res.json({
            "config": {
                "fields": Entity.getForm(collectionName)
            },
            "document": {}
        });
    });

    // UPDATE ONE ITEM
    routerApi.route('/:collectionName/id/:id').put(function (req, res) {

        var collectionName = req.params.collectionName;

        Entity.getModel(collectionName).findById(req.params.id, function (err, entity) {
            if (err) {
                res.send(err);
            }
            Entity.getForm(collectionName).forEach(function (item) {
                if(item.name == '_id') return;
                entity[item.name] = req.body[item.name];
            });

            entity.save(function (err) {
                if (err) {
                    res.send(err);
                }
                res.json({ message: 'entity updated!' });
            });

        });
    });

    //DELETE ONE ITEM
    routerApi.route('/:collectionName/id/:id').delete(function (req, res) {

        var collectionName = req.params.collectionName;

        Entity.getModel(collectionName).remove({
            _id: req.params.id
        }, function (err, article) {
            if (err) {
                res.send(err);
            }

            res.json({ message: 'Successfully deleted' });
        });
    });

    return routerApi;
};

Entity article.js & post.js

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

module.exports.model = mongoose.model('Article', new Schema({
    name: String,
    desc: String
}));

module.exports.form = [
    {
        "name": "_id",
        "type": "hidden",
        "label": "_id"
    },
    {
        "name": "name",
        "type": "text",
        "label": "Name"
    },
    {
        "name": "desc",
        "type": "text",
        "label": "Description"
    }
];

module.exports.list = ["_id", "Name", "Description"];
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

module.exports.model = mongoose.model('Post', new Schema({
    title: String
}));

module.exports.form = [
    {
        "name": "_id",
        "type": "hidden",
        "label": "_id"
    },
    {
        "name": "title",
        "type": "text",
        "label": "Title"
    }
];
module.exports.list = ["_id", "Title"];

GitHub