ANGULARJS
BACKEND
STORIES
How to build a backend-ready app
WITHOUT BACKEND
Wearing multiple hats at Narada Robotics startup, from coding to whatever else…
Main interests - mobile development
IN LOVE WITH ANGULARJS
Enrique Oriol
CTO @ Narada Robotics
blog.enriqueoriol.com @enrique.oriol
SCENARIO
I want you to develop my frontend
I’ll provide you with an API REST
SCENARIO
I want you to develop my frontend
I’ll provide you with an API REST
BACKEND STORIES 5
URLS TO LOCAL JSONS
Of course we can use urls to local JSON files, but…
We cannot use POST, PUT...
We cannot use response codes
We cannot access headers
So we cannot really prepare client side API connections
@enrique.oriol
WHAT CAN WE DO?
MOCKING THE SERVER
What we will cover:
URL DEFINITIONS01
CREATING JSON FIXTURES02
USING $HTTPBACKEND03
USING $RESOURCE04
DYNAMIC RESPONSES05
BACKEND PERSISTENCE06
BACKEND STORIES 8
CAUTION
This is NOT intended to be used in PRODUCTION
@enrique.oriol
BACKEND STORIES 9
Why not Node.js mock?
It is just an alternative to any other mock server
It runs at the same JS VM, so it does not need a public
endpoint somewhere
Think about sending the results to a client in the case of a
hybrid app
@enrique.oriol
BACKEND STORIES 10
You can download code here:
https://github.com/kaikcreator/UserRanking
And play with the results here:
http://kaikcreator.github.io/UserRanking/
EXAMPLE APP
We will use User Ranking app as
example
@enrique.oriol
QUICK OVERVIEW USER RANKING
Require Following Integration With API
NON AUTHENTICATED
CREATE USER - POST user endpoint
USERS RANKING - GET users endpoint
AUTHENTICATED
UPDATE USER - PUT user endpoint
URL DEFINITION
Let’s organize our code
BACKEND STORIES 13
URL DEFINITION
app/services/url/URLModule.js
angular.module('userRanking.urlsModule', [])
.constant('urlConst', (function(){
var protocol = 'http://';
var domain = 'www.userRanking.com';
var base = '/api';
var version = '/v1';
var placeholders = {
userId: '[userId]'
}
return {
user: protocol + domain + base + version + '/user/' + placeholders.userId,
users: protocol + domain + base + version + '/users',
placeholders: placeholders
};
})())
@enrique.oriol
BACKEND STORIES 14
URL DEFINITION
app/services/url/URLModule.js
.factory('urls', ['urlConst', function(urlConst){
var urls = angular.copy(urlConst);
var replacePlaceholders = function(url, placeholders){
for(var key in placeholders){
url = url.replace(urlConst.placeholders[key], placeholders[key]);
}
return url;
};
//generate dinamic urls here
urls.user = function(id){
return replacePlaceholders(urlConst.user, {userId: id});
};
return urls;
}]);
@enrique.oriol
BACKEND STORIES 15
URL DEFINITION
This Approach
EASY TO EXTEND
WITH MORE
ENDPOINTS
ISOLATES URL
DETAILS FROM
OTHER SERVICES
@enrique.oriol
CREATING JSON
FIXTURES
We should agree in the models
BACKEND STORIES 17
CREATING JSON FIXTURES
How a user will look like
{
id: userId,
name: username,
image: urlToUserImage,
rating: userRating
}
@enrique.oriol
BACKEND STORIES 18
CREATING JSON FIXTURES
app/fixtures/users.json
[
{
"id": "1",
"name": "Peter",
"image": "user1.png",
"points": 150
},
.
.
.
{
"id": "6",
"name": "Travis",
"image": "user6.png",
"points": 100
}
]
@enrique.oriol
E2E
$HttpBackend
As a mechanism not to test, but to mock our server
BACKEND STORIES 20
e2e $httpBackend
https://docs.angularjs.org/api/ngMockE2E/service/$httpBackend
1. download
Installation
$ bower install angular-mocks
2. include source
<!-- mock server -->
<script src="bower_components/angular-mocks/angular-mocks.js"></script>
3. include module
angular.module('app', ['ngMockE2E']);
@enrique.oriol
BACKEND STORIES 21
e2e $httpBackend
https://docs.angularjs.org/api/ngMockE2E/service/$httpBackend
When URL: return object with responds & passthrough methods
Basics
when( method, url, [data], [headers], [keys] );
Responds
Passthrough
$httpBackend.when(‘GET’, url).respond({} / [])
$httpBackend.when(‘GET’, url).respond(function(method, url, data, headers, params){})
$httpBackend.when(‘GET’, url).passThrough()
@enrique.oriol
BACKEND STORIES 22
e2e $httpBackend
app/services/mock/MockServerModule.js
angular.module('userRanking.mockServerModule', ['ngMockE2E', 'userRanking.urlsModule'])
.run(['$httpBackend', 'urls', function($httpBackend, urls){
/*mocked data*/
$httpBackend.whenGET(urls.users)
.respond([
{ "id":"5", "name":"Alex", "image":"user5.png", "points": 210},
{ "id":"6", "name":"Travis", "image":"user6.png", "points": 100}
]);
}])
@enrique.oriol
$resource
As a mechanism to load local JSON files
BACKEND STORIES 24
$resource
https://docs.angularjs.org/api/ngResource/service/$resource
1. download
Installation
$ bower install angular-resource
2. include source
<!-- mock server -->
<script src="bower_components/angular-resource/angular-resource.js"></script>
3. include module
angular.module('app', ['ngResource']);
@enrique.oriol
BACKEND STORIES 25
$resource: return object with different methods, that populates async
Usage
$resource(url, [paramDefaults], [actions], options)
Returned object methods
$resource
https://docs.angularjs.org/api/ngResource/service/$resource
{ 'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'}
};
@enrique.oriol
BACKEND STORIES 26
$resource
app/services/mock/MockServerModule.js
angular.module('userRanking.mockServerModule', ['ngMockE2E','ngResource', 'userRanking.urlsModule'])
.constant('fixturePaths', {
"users": "fixtures/users.json"
})
.run(['$httpBackend', 'urls', 'fixturePaths', '$resource', function($httpBackend, urls, fixturePaths, $resource){
/* allow local calls */
$httpBackend.whenGET(/fixtures/.*/).passThrough();
/*mocked data*/
$httpBackend.whenGET(urls.users).respond( $resource(fixturePaths.users).query() );
}])
@enrique.oriol
BACKEND STORIES 27
If you perform a $http call just after app boostrap, it’s possible that $resource is
not already populated, returning an empty value.
In that case you should perform some additional trick, always trying to perform
all the stuff in MockServerModule, so it’s easier to “shut down” mock server
when real server comes to life.
You can see a decorator approach in the example code available on github.
REMEMBER that $resource
is populated ASYNC
$resource
@enrique.oriol
Dynamic
responses
The power behind mocking server with E2E
$httpBackend
BACKEND STORIES 29
Dynamic responses
Using function inside $httpBackend.respond
<!-- mocked data -->
$httpBackend.whenGET(urls.users).respond( $resource(fixturePaths.users).query() );
/*mocked data*/
var usersResource = $resource(fixturePaths.users).query();
$httpBackend.whenGET(urls.users).respond(function(method, url, data, headers){
return [200, usersResource, {}];
});
Changing this:
Into this:
Gives us access to url, data and headers, so our response can be dynamic
@enrique.oriol
BACKEND STORIES 30
Dynamic responses
We can perform data validation, and return custom headers
.run(['$httpBackend', 'urls', 'fixturePaths', '$resource', function($httpBackend, urls, fixturePaths, $resource){
/*...previous stuff...*/
/* regex allowing any user id in user url*/
var anyUserUrl = new RegExp(urls.user('.*'));
/*mock POST rx of a new user, and return auth token*/
$httpBackend.whenPOST(anyUserUrl).respond(function(method, url, data, headers){
data = angular.fromJson(data);
if(data.name === undefined || !angular.isString(data.name) || data.name.length === 0){ return [400, null, null]; }
else{
var user = {
"id": "7",
"name": data.name,
"points": 0
};
return [200, user, {Authorization: "1234567890asdfghjklzxcvbnm"}];
}
});
}])
@enrique.oriol
BACKEND STORIES 31
Dynamic responses
We lack persistence
.run(['$httpBackend', 'urls', 'fixturePaths', '$resource', function($httpBackend, urls, fixturePaths, $resource){
/*...previous stuff...*/
/*mock POST rx of a new user, and return auth token*/
$httpBackend.whenPUT(anyUserUrl).respond(
function(method, url, data, headers){
data = angular.fromJson(data);
if(headers['Authorization'] !== "Token 1234567890asdfghjklzxcvbnm"){
return [401, null, null];
}
else if(data.name === undefined && data.points === undefined && data.image ===undefined){
return [400, null, null];
}
else{
/*here we need to return updated data, so our app can reflect changes*/
var user = {...};
return [200, user, {}];
}
});
}])
@enrique.oriol
Backend persistence
Completing the experience with localStorage
BACKEND STORIES 33
Write in LS
$window.localStorage['myKey'] = angular.toJson(myData);
Read from LS
var data = angular.fromJson($window.localStorage['myKey']);
Usage
Backend persistence
Local Storage
@enrique.oriol
BACKEND STORIES 34
.constant('lsMock', {
"users": "ls-cached-users"
})
.factory('mockedUsers', ['$window', '$resource', 'fixturePaths', 'lsMock',
function($window, $resource, fixturePaths, lsMock){
var service = {};
service.list = angular.fromJson($window.localStorage[lsMock.users]);
if(!service.list || service.list.length === 0){
service.list = $resource(fixturePaths.users).query();
}
service.save = function(){
$window.localStorage[lsMock.users] = angular.toJson(service.list);
};
service.add = function(user){
service.list.push(user);
service.save();
};
Backend persistence
Define a service to handle with cached users
service.update = function(user){
for(var i=0; i<service.list.length; i++){
if(service.list[i].id == user.id){
service.list[i].points = user.points;
service.save();
return;
}
}
};
service.getById = function(userId){
for(var i=0; i<service.list.length; i++){
if(service.list[i].id == userId){
return service.list[i];
}
}
return null;
};
return service;
}])
@enrique.oriol
BACKEND STORIES 35
.run(['$httpBackend', '$resource', 'urls', 'mockedUsers', function($httpBackend, $resource, urls, mockedUsers){
/* local calls */
$httpBackend.whenGET(/fixtures/.*/).passThrough();
/* regex allowing any user id in user url*/
var anyUserUrl = new RegExp(urls.user('.*'));
/*mocked users list*/
$httpBackend.whenGET(urls.users).respond(function(method, url, data, headers){
return [200, mockedUsers.list];
});
Backend persistence
Using our service in mock server - whenGET
@enrique.oriol
BACKEND STORIES 36
/* mock update user*/
$httpBackend.whenPUT(anyUserUrl).respond(
function(method, url, data, headers){
data = angular.fromJson(data);
var userId = url.replace(urls.user(''), '');
if(!data.id) {data.id = userId;}
if(headers['Authorization'] !== "Token 1234567890asdfghjklzxcvbnm"){
return [401, null, null];
}
else if(data.name === undefined && data.points === undefined && data.image ===undefined){
return [400, null, null];
}
else{
mockedUsers.update(data);
data = mockedUsers.getById(userId);
return [200, data, {}];
}
});
Backend persistence
Using our service in mock server - - whenPUT
@enrique.oriol
BACKEND STORIES 37
/* mock new user*/
$httpBackend.whenPOST(anyUserUrl).respond(
function(method, url, data, headers){
data = angular.fromJson(data);
if(data.name === undefined || !angular.isString(data.name) || data.name.length === 0){
return [400, null, null];
}
else{
var user = {
"id": ''+ mockedUsers.list.length,
"name": data.name,
"points": 0
};
mockedUsers.add(user);
return [200, user, {Authorization: "1234567890asdfghjklzxcvbnm"}];
}
});
}])
Backend persistence
Using our service in mock server - - whenPOST
@enrique.oriol
You’re ready
Time to play with code
http://kaikcreator.github.io/UserRanking/
THANK YOU!
http://lnkdin.me/enriqueoriol
blog.enriqueoriol.com
@enrique.oriol

How to build an AngularJS backend-ready app WITHOUT BACKEND

  • 1.
    ANGULARJS BACKEND STORIES How to builda backend-ready app WITHOUT BACKEND
  • 2.
    Wearing multiple hatsat Narada Robotics startup, from coding to whatever else… Main interests - mobile development IN LOVE WITH ANGULARJS Enrique Oriol CTO @ Narada Robotics blog.enriqueoriol.com @enrique.oriol
  • 3.
    SCENARIO I want youto develop my frontend I’ll provide you with an API REST
  • 4.
    SCENARIO I want youto develop my frontend I’ll provide you with an API REST
  • 5.
    BACKEND STORIES 5 URLSTO LOCAL JSONS Of course we can use urls to local JSON files, but… We cannot use POST, PUT... We cannot use response codes We cannot access headers So we cannot really prepare client side API connections @enrique.oriol
  • 6.
  • 7.
    MOCKING THE SERVER Whatwe will cover: URL DEFINITIONS01 CREATING JSON FIXTURES02 USING $HTTPBACKEND03 USING $RESOURCE04 DYNAMIC RESPONSES05 BACKEND PERSISTENCE06
  • 8.
    BACKEND STORIES 8 CAUTION Thisis NOT intended to be used in PRODUCTION @enrique.oriol
  • 9.
    BACKEND STORIES 9 Whynot Node.js mock? It is just an alternative to any other mock server It runs at the same JS VM, so it does not need a public endpoint somewhere Think about sending the results to a client in the case of a hybrid app @enrique.oriol
  • 10.
    BACKEND STORIES 10 Youcan download code here: https://github.com/kaikcreator/UserRanking And play with the results here: http://kaikcreator.github.io/UserRanking/ EXAMPLE APP We will use User Ranking app as example @enrique.oriol
  • 11.
    QUICK OVERVIEW USERRANKING Require Following Integration With API NON AUTHENTICATED CREATE USER - POST user endpoint USERS RANKING - GET users endpoint AUTHENTICATED UPDATE USER - PUT user endpoint
  • 12.
  • 13.
    BACKEND STORIES 13 URLDEFINITION app/services/url/URLModule.js angular.module('userRanking.urlsModule', []) .constant('urlConst', (function(){ var protocol = 'http://'; var domain = 'www.userRanking.com'; var base = '/api'; var version = '/v1'; var placeholders = { userId: '[userId]' } return { user: protocol + domain + base + version + '/user/' + placeholders.userId, users: protocol + domain + base + version + '/users', placeholders: placeholders }; })()) @enrique.oriol
  • 14.
    BACKEND STORIES 14 URLDEFINITION app/services/url/URLModule.js .factory('urls', ['urlConst', function(urlConst){ var urls = angular.copy(urlConst); var replacePlaceholders = function(url, placeholders){ for(var key in placeholders){ url = url.replace(urlConst.placeholders[key], placeholders[key]); } return url; }; //generate dinamic urls here urls.user = function(id){ return replacePlaceholders(urlConst.user, {userId: id}); }; return urls; }]); @enrique.oriol
  • 15.
    BACKEND STORIES 15 URLDEFINITION This Approach EASY TO EXTEND WITH MORE ENDPOINTS ISOLATES URL DETAILS FROM OTHER SERVICES @enrique.oriol
  • 16.
  • 17.
    BACKEND STORIES 17 CREATINGJSON FIXTURES How a user will look like { id: userId, name: username, image: urlToUserImage, rating: userRating } @enrique.oriol
  • 18.
    BACKEND STORIES 18 CREATINGJSON FIXTURES app/fixtures/users.json [ { "id": "1", "name": "Peter", "image": "user1.png", "points": 150 }, . . . { "id": "6", "name": "Travis", "image": "user6.png", "points": 100 } ] @enrique.oriol
  • 19.
    E2E $HttpBackend As a mechanismnot to test, but to mock our server
  • 20.
    BACKEND STORIES 20 e2e$httpBackend https://docs.angularjs.org/api/ngMockE2E/service/$httpBackend 1. download Installation $ bower install angular-mocks 2. include source <!-- mock server --> <script src="bower_components/angular-mocks/angular-mocks.js"></script> 3. include module angular.module('app', ['ngMockE2E']); @enrique.oriol
  • 21.
    BACKEND STORIES 21 e2e$httpBackend https://docs.angularjs.org/api/ngMockE2E/service/$httpBackend When URL: return object with responds & passthrough methods Basics when( method, url, [data], [headers], [keys] ); Responds Passthrough $httpBackend.when(‘GET’, url).respond({} / []) $httpBackend.when(‘GET’, url).respond(function(method, url, data, headers, params){}) $httpBackend.when(‘GET’, url).passThrough() @enrique.oriol
  • 22.
    BACKEND STORIES 22 e2e$httpBackend app/services/mock/MockServerModule.js angular.module('userRanking.mockServerModule', ['ngMockE2E', 'userRanking.urlsModule']) .run(['$httpBackend', 'urls', function($httpBackend, urls){ /*mocked data*/ $httpBackend.whenGET(urls.users) .respond([ { "id":"5", "name":"Alex", "image":"user5.png", "points": 210}, { "id":"6", "name":"Travis", "image":"user6.png", "points": 100} ]); }]) @enrique.oriol
  • 23.
    $resource As a mechanismto load local JSON files
  • 24.
    BACKEND STORIES 24 $resource https://docs.angularjs.org/api/ngResource/service/$resource 1.download Installation $ bower install angular-resource 2. include source <!-- mock server --> <script src="bower_components/angular-resource/angular-resource.js"></script> 3. include module angular.module('app', ['ngResource']); @enrique.oriol
  • 25.
    BACKEND STORIES 25 $resource:return object with different methods, that populates async Usage $resource(url, [paramDefaults], [actions], options) Returned object methods $resource https://docs.angularjs.org/api/ngResource/service/$resource { 'get': {method:'GET'}, 'save': {method:'POST'}, 'query': {method:'GET', isArray:true}, 'remove': {method:'DELETE'}, 'delete': {method:'DELETE'} }; @enrique.oriol
  • 26.
    BACKEND STORIES 26 $resource app/services/mock/MockServerModule.js angular.module('userRanking.mockServerModule',['ngMockE2E','ngResource', 'userRanking.urlsModule']) .constant('fixturePaths', { "users": "fixtures/users.json" }) .run(['$httpBackend', 'urls', 'fixturePaths', '$resource', function($httpBackend, urls, fixturePaths, $resource){ /* allow local calls */ $httpBackend.whenGET(/fixtures/.*/).passThrough(); /*mocked data*/ $httpBackend.whenGET(urls.users).respond( $resource(fixturePaths.users).query() ); }]) @enrique.oriol
  • 27.
    BACKEND STORIES 27 Ifyou perform a $http call just after app boostrap, it’s possible that $resource is not already populated, returning an empty value. In that case you should perform some additional trick, always trying to perform all the stuff in MockServerModule, so it’s easier to “shut down” mock server when real server comes to life. You can see a decorator approach in the example code available on github. REMEMBER that $resource is populated ASYNC $resource @enrique.oriol
  • 28.
    Dynamic responses The power behindmocking server with E2E $httpBackend
  • 29.
    BACKEND STORIES 29 Dynamicresponses Using function inside $httpBackend.respond <!-- mocked data --> $httpBackend.whenGET(urls.users).respond( $resource(fixturePaths.users).query() ); /*mocked data*/ var usersResource = $resource(fixturePaths.users).query(); $httpBackend.whenGET(urls.users).respond(function(method, url, data, headers){ return [200, usersResource, {}]; }); Changing this: Into this: Gives us access to url, data and headers, so our response can be dynamic @enrique.oriol
  • 30.
    BACKEND STORIES 30 Dynamicresponses We can perform data validation, and return custom headers .run(['$httpBackend', 'urls', 'fixturePaths', '$resource', function($httpBackend, urls, fixturePaths, $resource){ /*...previous stuff...*/ /* regex allowing any user id in user url*/ var anyUserUrl = new RegExp(urls.user('.*')); /*mock POST rx of a new user, and return auth token*/ $httpBackend.whenPOST(anyUserUrl).respond(function(method, url, data, headers){ data = angular.fromJson(data); if(data.name === undefined || !angular.isString(data.name) || data.name.length === 0){ return [400, null, null]; } else{ var user = { "id": "7", "name": data.name, "points": 0 }; return [200, user, {Authorization: "1234567890asdfghjklzxcvbnm"}]; } }); }]) @enrique.oriol
  • 31.
    BACKEND STORIES 31 Dynamicresponses We lack persistence .run(['$httpBackend', 'urls', 'fixturePaths', '$resource', function($httpBackend, urls, fixturePaths, $resource){ /*...previous stuff...*/ /*mock POST rx of a new user, and return auth token*/ $httpBackend.whenPUT(anyUserUrl).respond( function(method, url, data, headers){ data = angular.fromJson(data); if(headers['Authorization'] !== "Token 1234567890asdfghjklzxcvbnm"){ return [401, null, null]; } else if(data.name === undefined && data.points === undefined && data.image ===undefined){ return [400, null, null]; } else{ /*here we need to return updated data, so our app can reflect changes*/ var user = {...}; return [200, user, {}]; } }); }]) @enrique.oriol
  • 32.
    Backend persistence Completing theexperience with localStorage
  • 33.
    BACKEND STORIES 33 Writein LS $window.localStorage['myKey'] = angular.toJson(myData); Read from LS var data = angular.fromJson($window.localStorage['myKey']); Usage Backend persistence Local Storage @enrique.oriol
  • 34.
    BACKEND STORIES 34 .constant('lsMock',{ "users": "ls-cached-users" }) .factory('mockedUsers', ['$window', '$resource', 'fixturePaths', 'lsMock', function($window, $resource, fixturePaths, lsMock){ var service = {}; service.list = angular.fromJson($window.localStorage[lsMock.users]); if(!service.list || service.list.length === 0){ service.list = $resource(fixturePaths.users).query(); } service.save = function(){ $window.localStorage[lsMock.users] = angular.toJson(service.list); }; service.add = function(user){ service.list.push(user); service.save(); }; Backend persistence Define a service to handle with cached users service.update = function(user){ for(var i=0; i<service.list.length; i++){ if(service.list[i].id == user.id){ service.list[i].points = user.points; service.save(); return; } } }; service.getById = function(userId){ for(var i=0; i<service.list.length; i++){ if(service.list[i].id == userId){ return service.list[i]; } } return null; }; return service; }]) @enrique.oriol
  • 35.
    BACKEND STORIES 35 .run(['$httpBackend','$resource', 'urls', 'mockedUsers', function($httpBackend, $resource, urls, mockedUsers){ /* local calls */ $httpBackend.whenGET(/fixtures/.*/).passThrough(); /* regex allowing any user id in user url*/ var anyUserUrl = new RegExp(urls.user('.*')); /*mocked users list*/ $httpBackend.whenGET(urls.users).respond(function(method, url, data, headers){ return [200, mockedUsers.list]; }); Backend persistence Using our service in mock server - whenGET @enrique.oriol
  • 36.
    BACKEND STORIES 36 /*mock update user*/ $httpBackend.whenPUT(anyUserUrl).respond( function(method, url, data, headers){ data = angular.fromJson(data); var userId = url.replace(urls.user(''), ''); if(!data.id) {data.id = userId;} if(headers['Authorization'] !== "Token 1234567890asdfghjklzxcvbnm"){ return [401, null, null]; } else if(data.name === undefined && data.points === undefined && data.image ===undefined){ return [400, null, null]; } else{ mockedUsers.update(data); data = mockedUsers.getById(userId); return [200, data, {}]; } }); Backend persistence Using our service in mock server - - whenPUT @enrique.oriol
  • 37.
    BACKEND STORIES 37 /*mock new user*/ $httpBackend.whenPOST(anyUserUrl).respond( function(method, url, data, headers){ data = angular.fromJson(data); if(data.name === undefined || !angular.isString(data.name) || data.name.length === 0){ return [400, null, null]; } else{ var user = { "id": ''+ mockedUsers.list.length, "name": data.name, "points": 0 }; mockedUsers.add(user); return [200, user, {Authorization: "1234567890asdfghjklzxcvbnm"}]; } }); }]) Backend persistence Using our service in mock server - - whenPOST @enrique.oriol
  • 38.
    You’re ready Time toplay with code http://kaikcreator.github.io/UserRanking/
  • 39.