NodeJS and CQRS Based REST APIs Using ExpressJS SubApps

Before going on this post, i highly recommend to read first my last post about CQRS , this will give you how i am thinking about CQRS and the added value to build CQRS based REST APIs.

Implementation Steps

  1. Create Authorization Module
  2. Building two SubApps (1st for Commands and other for Queries)
  3. Handle the logging and Authorization
  4. Create App (Entry point) to consume the SubApps

If you are new in NodeJS sub apps – its is an ExpressJS way to extract part of your API functionality in separate JS Node and consume (use) it when ever you need.

lets start with the code …

1. Create Authorization Module

//  ./authorization-handler.js
function functionAuth(req,res,next)
{
     var auth = true; // Write Down Your Auth Logic Here
     if(!auth)
     {
          console.log('Non Authorized To Function.');
          res.status(403).send('Not Authorized');
     }
     else
          console.log('Authorized.');
}

function modelAuth(req,res)
{
     var auth = true; // Write Down Your Auth Logic Here
     if(!auth)
     {
          console.log('Non Authorized To Model.');
          throw ('Invalid Token');
     }
     else
          console.log('Authorized.');
     }

exports.modelAuth = modelAuth;
exports.functionAuth = functionAuth;

2. Building Sub App for Commands

//  ./commands.js (POST, PUT, DELETE)
const express= require('express');
const authhandler = require('./authorization-handler');
var app = new express();

app.on('mount', function (parent) {
  console.log('Commands API Mounted');
});

app.all('*',function(req,res,next){
  try{
    authhandler.modelAuth();
    next();
  }
  catch(ex)
  {
    res.status(403).send(ex)
  }
})
app.post('/get',authhandler.functionAuth,function(req,res,next)
{
  res.send('get command executed');
})

module.exports = app;

3. Building Sub App for Queries

//  ./queries.js
//Do the same as the commands for GET functions

4. Building the Entry Point

//  ./app.js
const express = require('express');
const authhandler = require('./authorization-handler');
const commands = require('./commands');
const queries = require('./queries');
var app = new express();

app.use('/commands',commands);
app.use('/queries',queries);

app.listen(7777,function(req,res){
  console.log('7777 Server Is Working ...');
});

And we have done 🙂

Advertisements

Introducing CQRS (Command Query Responsibility Segregation)

Dears,

If you are a Domain Driven or Micro-services based developer, you might be interested to build your coming API(s) in CQRS models instead of the current traditional CRUD approach.

First, based on Martin Fowler article about CQRS, Greg Young is the founder of this pattern, but honestly is don’t believe CQRS is a pattern at all, it is more simpler than that, the pattern is something solving a specific problem, but CQRS is not a solution for anything, it is a way to separate and elevate your code to be more expandable and easy to handle specially with complex domains.

What is CQRS

Its easy to figure out the target from the name (Command Query Responsibility Segregation) where you separate between the API Commands and Queries.

  • Commands (Changes the state of the object) [Post, Put, Delete] (e.g. Writes or Update on the DataSource)
  • Query (Never Changes the state of the object) [Get] (e.g. Selecting from DataSource)

and the first thing comes to your mind, yes its really important for code security !

I remember a project written in NodeJS where i elevated the CQRS models to be with different security token check.

1. Simply How It works ?

Define your domain (keep the business in mind)

Domain is always maps to business value, an independent and totally agnostic in terms of behavior, and we might talk about this later in separate post, but first remember that business separation is all you should care about.

Create two models (Command & Query)

Its kind of extra domain separation but not a sub-domain, as Commands and Queries give the domain its full behavior.

CQRS and Token Authorization

I am talking about API authorization where you just allow authorized requests to access specific API, and this could be on the Model Route or on the Function Route.

NodeJS (Model Level Authorization)

const express= require('express');
const authorizationHandler = require('/authorization-handler');
var app = new express();

app.all('*',function(req,res,next){
  try{
   authorizationHandler.authorizeModel();
   next();
  }
  catch(ex)
  {
    res.status(403).send(ex);
  }
});

app.get("/get/:id",function(req,res){
 res.send(/*What Ever ...*/);
});

app.post("/post",function(req,res){
 res.send(/*What Ever ...*/);
});

C# (Model Level Authorization)

[RoutePrefix("api/employee/{token}")]
[Authorize]
public class EmployeeController : ApiController
{
    [Route("get/{id}")]
    public async Task Get(int id, string token)
    {
          return Ok(/*What Ever*/);
    }
    [Route("post")]
    public async Task Post([FromBody] Employee employee, string token)
    {
          return Ok(/*What Ever*/);
    }
}

NodeJS (Function Level Authorization)

const express= require('express');
var app = new express();

app.all('*',function(req,res,next){
 /*No Authorization here ...*/
 next();
});

app.get("/get/:id",authorizationHandler.authorizeFunction,function(req,res,next){
 res.send(/*What Ever ...*/);
});

app.post("/post",authorizationHandler.authorizeFunction,function(req,res,next){
 res.send(/*What Ever ...*/);
});

C# (Function Level Authorization)

[RoutePrefix("api/employee/{token}")]
public class EmployeeController : ApiController
{
    [Route("get/{id}")]
    [Authorize]
    public async Task Get(int id, string token)
    {
          return Ok(/*What Ever*/);
    }
    [Route("post")]
    [Authorize]
    public async Task Post([FromBody] Employee employee, string token)
    {
          return Ok(/*What Ever*/);
    }
}

In CQRS, it will be a little different where you have to authorize on the Model Level to make sure the request accessing the Commands model is authorized to change the object state, and the authorization is less sensitive here. For this case in specific you can use my implementation for a simple NodeJS two levels authorization, where the primary Authorization will be on the model level (Mandatory) and second will be on the function level (Optional).

CQRS and Event Sourcing working together

If you are not following, Event Sourcing is a design approach to publish events instead of executing DataSource operations and a publisher will execute these events in order (if dependent) to the data source.

I wish you started to figure out how CQRS is very useful for Event Sourcing !

We will talk about this in future post in details because it deserves.

Thanks for now, try it by your self and make it a life style 😉

Regards,

Sameh Selem

Two Levels Authorization Using NodeJS

Lets first figure out why we might need the two level authorizations specially while building production REST APIs.

1- Implementing CQRS (you can red more about CQRS in last two posts 1 & 2 )
2- Having non-user (Session) token authorization and a user token authorization
3-  You could also use it for logging or any kind of aspect oriented pre-processing functionality.

 

Building the Authorization Module

//Level 1
function level1_auth(req,res)
{
  var auth = true;
  if(!auth)
  {
    console.log('Non Authorized Level1.');
    throw ('Invalid Token');
  }
  else
    console.log('Authorized Level1.');
}

//Level 1
function level2_auth(req,res,next)
{
  var auth = false;
  if(!auth)
  {
    console.log('Non Authorized Level2.');
    res.status(403).send('Not Authorized');
  }
  else
    console.log('Authorized Level2.');
}

exports.level1_auth = level1_auth;
exports.level2_auth = level2_auth;

API

const express= require('express');
const authhandler = require('./authorization-handler');
var app = new express();

//Level 1
app.all('*',function(req,res,next){
  try{
    authhandler.level1_auth();
    next();
  }
  catch(ex)
  {
    res.status(403).send(ex)
  }
})

//Level 2
app.get('/get',authhandler.level2_auth,function(req,res,next)
{
  res.send('get command executed');
})

Thanks,