Building a web service with Node.js in Visual Studio Part 7: service, repository and “next”

Introduction

In the previous post we looked at how to create controllers and routes in our Node.js project. We saw the controllers are normal JS classes that don’t need to follow any naming convention. We still give them names like “customersController” to indicate their purpose. We also saw what “module.exports” does and how it can be used to add functionality to a JS object. Lastly we looked at the role of “index.js” to reference a whole folder using the “require” function.

We ended up with a customers controller that directly sends back a JSON object. In a layered project, however, we should separate the roles as much as possible into controllers, services and repositories. The repository is responsible for data store operations such as insertions and queries. Services are the glue between controllers and repositories as controller normally do not contact the data store directly.

In a .NET project we’d hide the services and repositories behind abstractions like interfaces. In JS we don’t have interfaces so we’ll go for a simplified version. Still, the goal is to separate the roles and not let the controllers drive data access.

However, before that we need to look at asynchronous code execution in Node.js.

The “next” parameter

Have you checked out OWIN and Katana in .NET 4.5? If not, then you as a .NET developer should. It will help you understand the role of the “next” parameter in Node.js. There’s a course on OWIN on this blog starting here. Skim through it to get the idea. Take special note of the app builder object and the role of the “next” parameter in the following function:

appBuilder.Use(async (env, next)

In short “next” refers to the next action in the execution chain. The “next” parameter usually represents a function which can accept a number of parameters. Often it accepts an error parameter and some other objects that are returned from the callback function.

Note that “next” is only a parameter name. It could be called donaldduck or mickeymouse but apparently it’s quite often called “next” by default in Node.js projects to indicate its role to the caller.

The service

In this section we’ll first want to move the following dummy data extraction code from the controller to a service:

res.send({ name: "Great Customer with id " + customerId, orders: "none yet" });

Once that’s working we’ll move it further down to a repository. The controller we’ll interact with the service only.

Insert a new folder called “services” into the project and into that folder two JS files: one called index.js and another called customerService.js. Remember index.js from the previous post? It is a default file in a folder so that callers can refer to the folder through the require function without having exact knowledge of the folder’s contents beforehand.

We’ll start with customerService.js which at first won’t call any repository, we’ll do that in the next section. We must first understand how to chain the bits and pieces together. customerService.js is very simple:

module.exports.getAllCustomers = function () {
    return { name: "Great Customer", orders: "none yet" };  
};

module.exports.getCustomerById = function (customerId) {
    return { name: "Great Customer with id " + customerId, orders: "none yet" }
};

This should be fairly straightforward by now: we expose two methods, one for getting all customers and another for getting a single customer by id.

index.js is somewhat more exciting:

var customerService = require('./customerService');

module.exports.getAllCustomers = function (next) {
    next(null, customerService.getAllCustomers());
};

module.exports.getCustomerById = function (id, next) {
    next(null, customerService.getCustomerById(id));
};

We first make a reference to customerService. We then build the two methods that in turn will call the service functions in at first sight a funny way. We declare that the getAllCustomers function accepts a parameter called next. By the way we call “next” we imply the signature of “next” that must be passed into the function: a first parameter which we simply set to “null” here and the result of the customerService.getAllCustomers() operation. The first parameter will be an error parameter which is simply set to null for right now, we’ll keep it as a placeholder.

So in fact we’ll be soon passing in a function callback into the getAllCustomers function. By “next” we indicate to the caller that this might need to be executed asynchronously: data retrieval usually means consulting a database or a web service which involves some overhead. While that operation is ongoing the idle threads can perform something else.

Note, however, that in the above case we simply call “next” in a synchronous manner first to keep the example simple. This test method doesn’t involve any database operation yet but this implementation will change in future posts.

The getCustomerById is similar but we also pass in a customer ID, not only the next function to be called.

So how are these used from the controller? Consider the following code in customersController.js:

var customerService = require('../services');

module.exports.start = function (app) {
    app.get("/customers", function (req, res) {
        res.set('Content-Type', 'application/json');
        customerService.getAllCustomers(function (err, customers) {
            if (err) {

            }
            else {
                res.send(customers);
            }
        });
    });
    
    app.get("/customers/:id", function (req, res) {
        var customerId = req.params.id;
        res.set('Content-Type', 'application/json');
        customerService.getCustomerById(customerId, function (err, customer) {
            if (err) {
            }
            else {
                res.send(customer);
            }
        });
        
    });
};

We first reference the services folder. Notice the two dots as we need to leave the controllers folder first. We still set the routes as before but respond in a different manner which at first can seem confusing. We call getAllCustomers in the /customers endpoint and pass in a function for the “next” parameter. We know that the function needs to follow a certain signature: an error an a result placeholder. In the body of the implementation of “next” we check if there was any error – to be implemented later – otherwise we send back the result from the operation, i.e. “customers”. getCustomerById is the same but we also pass in the customer ID besides the “next” function implementation.

Run the application, navigate to /customers and /customers/123 and both should work as before.

The repositories

This step might be overkill at this stage but let’s see how a repository can be implemented. Add a new folder called “repositories” and a file called customerRepository into it:

module.exports.getAll = function () {
    return { name: "Great Customer", orders: "none yet" };
};

module.exports.getById = function (customerId) {
    return { name: "Great Customer with id " + customerId, orders: "none yet" }
};

This is the same as customerService above, only the exposed method names are different. customerService.js can be updated to:

var customerRepository = require('../repositories/customerRepository');

module.exports.getAllCustomers = function () {
    return customerRepository.getAll();  
};

module.exports.getCustomerById = function (customerId) {
    return customerRepository.getById(customerId);
};

There’s no need to change any other file.

Run the application and check if the above routes still work as before, they should.

In the next post we’ll connect to our customers database in MongoDb.

View all posts related to Node here.

Advertisements

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

ultimatemindsettoday

A great WordPress.com site

iReadable { }

.NET Tips & Tricks

Robin Sedlaczek's Blog

Developer on Microsoft Technologies

HarsH ReaLiTy

A Good Blog is Hard to Find

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

the software architecture

thoughts, ideas, diagrams,enterprise code, design pattern , solution designs

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

Anything around ASP.NET MVC,WEB API, WCF, Entity Framework & AngularJS

Cyber Matters

Bite-size insight on Cyber Security for the not too technical.

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: