Click here to Skip to main content
13,863,239 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

7.4K views
56 downloads
1 bookmarked
Posted 13 Sep 2016
Licenced CPOL

WCF design pattern for Nodejs web api

, 13 Sep 2016
Rate this:
Please Sign up or sign in to vote.
applying WCF design pattern into nodejs web api

Introduction

When you start with nodejs, the first issues you will face the dynamic way of how the code get written in Javascript. It's almost impossible to find one standard or pattern to write decent web API backend with nodejs. This article is trying to propose a theory about taking the WCF design pattern into nodejs, before reading this artical you can full backed to play with at https://documenter.getpostman.com/view/258093/example/2Jwb6h 

Background

WCF is a great design pattern; it keeps everything in place if we ignore the configuration hell. You got the data contract which allows exposing the needed data to the consumer, operation contract to present service methods, endpoints it's a good way telling how to consume the service So how we can implement WCF design pattern in nodejs.

Using the code

On order to get WCF design pattern works with nodejs, everything will start from file structure here's my suggestion for it, notes the following structure is hight level 

-App
--dataContracts
--IApp
--logs
--operationsContract
--web.config
--package.json

In the next section, I'm trying to explain what each folder is doing and why we needed.   

Structure explain

App

The root folder of your application it holds everything your application need, stuff like node_models, YAML files or any extra information the application need set it in the root folder, notes that name of the App folder it can be whatever your application name is. 

dataContracts

In this folder, you declare all model that need to expose to the consumer. Each model will have a folder; each folder will have an index.js file that exports the model, for example, this index.js file is exposing a mongoose schema.

var mongoose = require('mongoose');

var schema = new mongoose.Schema({
   name: String, 
   isActive: {
       type: Boolean,
       default: true
   }, 
   isDeleted: {
       type: Boolean, 
       default: false
   }, 
   createDate: {
       type: Date, 
       default: new Date
   }, 
   latitude: String, 
   longitude: String
});

module.exports = mongoose.model('categories', schema);

Will see later how to register this type in mongoose driver before you run the application, Right now the file structure for this folder will look like this: 

-IApp
--dataContracts
---applications
----index.js
---categories
----index.js
---cities
----index.js
---friendships
----index.js
---otp
----index.js
---products
----index.js
---users
----index.js
--logs
--operationsContract
--web.config
--package.json

operations contract

Now after declaring our data contracts, it's time to write the operations contract. This part it may seem, a stranger for the first time but my way of work just separates thing's so for each method I create a single file with the name of this method, the index file then will export each method. For example, if I have method create for the model categories, then I will create a new folder named categories inside the operations Contract folder. Now inside that folder, I will create a file called create.js and write my function in it with exported as execute, here's the code: 

var mongoose = require('mongoose');
var categoryCollection = mongoose.model('categories');
var responseDictionary = require('response-dictionary');
var verifyingCategory = require('./verifying');

exports.execute = function(httpRequest, httpResponse) {
    try {
        verifyingCategory.isValidForCreate(httpRequest.body, function(categoryCreationError) {
            if (!categoryCreationError) {
                var category = new categoryCollection(httpRequest.body);
                category.save(function(savingcategoryError) {
                    if (!savingcategoryError) {
                        return responseDictionary.getAsHttpResponse(1008, category, null, httpResponse);
                    }
                    else {
                        return responseDictionary.getAsHttpResponse(1500, null, savingcategoryError, httpResponse);
                    }
                });
            }
            else {
                return httpResponse.status(categoryCreationError.httpStatus).send(categoryCreationError);
            }
        });
    }
    catch (exception) {
        return responseDictionary.getAsHttpResponse(1500, null, exception, httpResponse);
    }
};

Your function can be whatever you want but notice that I'm passing the HTTP request so I can take full control of request that coming from the consumer. Now to expose this function we use the index.js inside each folder in the operations contract. here's a full index.js file: 

var category = function() {};

category.prototype.create = function execute(httpRequest, httpResponse) {
  require('./create').execute(httpRequest, httpResponse);
};

category.prototype.update = function execute(httpRequest, httpResponse) {
  require('./update').execute(httpRequest, httpResponse);
};

category.prototype.delete = function execute(httpRequest, httpResponse) {
  require('./delete').execute(httpRequest, httpResponse);
};

category.prototype.get = function execute(httpRequest, httpResponse) {
  require('./get').execute(httpRequest, httpResponse);
};

category.prototype.getById = function execute(httpRequest, httpResponse) {
  require('./getById').execute(httpRequest, httpResponse);
};


module.exports = category;

let's take a look how's the file Structure look like so far: 

-IApp
--dataContracts
---applications
----index.js
---categories
----index.js
---cities
----index.js
---friendships
----index.js
---otp
----index.js
---products
----index.js
---users
----index.js
--logs
--operationsContract
---applications
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
---categories
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
---cities
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
---friendships
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
---otp
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
---products
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
---users
----create.js
----update.js
----delete.js
----get.js
----getById.js
----index.js
--web.config
--package.json

Web.Config

Now that operations and data contract are set and ready it's time to connect them together. First, let's take a look at the web.config folder. It has one main file named index.js; let's take a look: 

module.exports = {
    appName: 'families',
    contracts: ['applications', 'cities', 'categories', 'users', 'otp', 'friendships', 'products'],
    responses: responses.responses,
    isHttpsEnabled: false,
    enableLogging: true,
    port: 8901,
    connectionString: "mongodb://localhost/families",
    dataBaseName: "families",
    certificates: {
        key: __dirname + '/server/certificates/server.key',
        certificate: __dirname + '/server/certificates/server.crt'
    },
    publicDirectories: [{
        title: '/',
        path: __dirname + '/public'
    }],
    appSetting: {
        uploadingFolder: __dirname + '/public/files'
    }
};
The main setting here's everything before the appSetting section. in the app setting section, you can write whatever you will need in your application. 
 
The last folder will name IApp which present the IInterface in WCF. this folder has only one index.js file and it link the data and operations contract together applying body parse module to any express app and last thing loading the end points file: 
 
var express = require('express');
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var cors = require('cors');
var https = require('https');
var fs = require("fs");
var config = require('../web.config');
var app = express();

exports.startup = function() {
    mongoose.connect(config.connectionString);
    app.use(bodyParser.urlencoded({
        extended: true,
        limit: '50mb'
    }));

    app.use(bodyParser.json({
        limit: '50mb'
    }));

    app.use(cors());

    for (var contract = 0; contract < config.contracts.length; contract++) {
        require('../dataContracts/' + config.contracts[contract]);
    }
    
    require('../web.config/endPoints')(app);
    for (var directory = 0; directory < config.publicDirectories.length; directory++) {
        if (fs.existsSync(config.publicDirectories[directory].path) === false) {
            fs.mkdirSync(config.publicDirectories[directory].path);
        }

        app.use(config.publicDirectories[directory].title, express.static(config.publicDirectories[directory].path));
    }

    if (config.isHttpsEnabled === true) {
        https.createServer({
            key: fs.readFileSync(config.certificates.key),
            cert: fs.readFileSync(config.certificates.certificate)
        }, app).listen(config.port);
    }
    else {
        app.listen(config.port);
    }

    console.log(config.appName + ' running on: ' + config.port);
};

this.startup();
notice this line, in the code above: 
require('../web.config/endPoints')(app);
the endPoints.js is a file that takes an express object here's full code of this file: 
 
var authenticate = require('../lib/authentication');
var applications = require('../operationsContract/applications');
var categories = require('../operationsContract/categories');
var cities = require('../operationsContract/cities');
module.exports = function(app) {
    app.post('/api/applications/', new authenticate().masterKey, new applications().create);
    app.patch('/api/applications/:id', new authenticate().masterKey, new applications().update);
    app.delete('/api/applications/:id', new authenticate().masterKey, new applications().delete);
    app.get('/api/applications/', new authenticate().masterKey, new applications().get);
    app.get('/api/applications/:id', new authenticate().masterKey, new applications().getById);
    app.post('/api/categories/', new authenticate().masterKey, new categories().create);
    app.patch('/api/categories/:id', new authenticate().masterKey, new categories().update);
    app.delete('/api/categories/:id', new authenticate().masterKey, new categories().delete);
    app.get('/api/categories/', new authenticate().applicationKey, new categories().get);
    app.get('/api/categories/:id', new authenticate().applicationKey, new categories().getById);
    app.post('/api/cities/', new authenticate().masterKey, new cities().create);
    app.patch('/api/cities/:id', new authenticate().masterKey, new cities().update);
    app.delete('/api/cities/:id', new authenticate().masterKey, new cities().delete);
    app.get('/api/cities/', new authenticate().applicationKey, new cities().get);
    app.get('/api/cities/:id', new authenticate().applicationKey, new cities().getById);
};

Authentication

Any function or module don't require exposing will write it in folder named lib, and since I'm passing a full HTTP request object to it you can use any authentication type you want, basic HTTP, oauth or just a key in the header, for eample here's a method that verify the master key: 

var responseDictionary = require('response-dictionary');
var config = require('rekuire')('web.config');

exports.execute = function(httpRequest, httpResponse, next) {
  try {
    if (httpRequest.header("X-example-MasterKey")) {
      if (httpRequest.header("X-example-MasterKey") === config.masterKey) {
        next();
      }
      else {
        responseDictionary.getAsHttpResponse(2, null, null, httpResponse);
      }
    }
    else {
      responseDictionary.getAsHttpResponse(1, null, null, httpResponse);
    }
  }
  catch (exception) {
    responseDictionary.getAsHttpResponse(1500, null, exception, httpResponse);
  }
};

the last thing is running our backend using: 

forever start IExample/
 
 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

a.alghabban
Software Developer
Saudi Arabia Saudi Arabia
No Biography provided

You may also be interested in...

Comments and Discussions

 
SuggestionWrong article title Pin
Pawel Cioch14-Sep-16 13:28
memberPawel Cioch14-Sep-16 13:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01 | 2.8.190214.1 | Last Updated 13 Sep 2016
Article Copyright 2016 by a.alghabban
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid