6. Planning
GET → /book Access to a list of books
GET → /book/:id Access to a single book
POST → /book Create a new book
PATCH → /book/:id Update a given book
DELETE → /book/:id Delete a given book
We can extends this using authentication header Authorization
7. We have a simple API
'use strict' ;
angular.module( 'myApp').service( 'bookService' , function($http) {
return {
"list": function() {
return $http.get( "http://localhost:8085/book" );
},
... // more methods
};
});
So we create a service
Or we can create a provider to configure the URLs etc
8. With a service we have a box
with different methods
bookService
Get all books: list()
Get a single book: get(:bookId)
Create a book: create(:bookModel)
...
9. The Book model is stable across our application
{title: "the book title" , isbn: "12345678" , available : true}
Or we resolve the book dependency with the book link (thanks to API uniquiness)
{
name: "my favourites" ,
books: [ 1,2,3]
}
So:
$q.all(
[1,2,3].map(bookService.get)
).then(function(books) {
//books is my favourites list with details
});
10. Where the GET
'use strict' ;
angular.module( 'myApp').service( 'bookService' , function($http) {
return {
"get": function(id) {
return $http.get( "http://localhost:8085/book/" +id);
},
"list": function() {
return $http.get( "http://localhost:8085/book" );
},
... // more methods
};
});
12. A controller can request data
angular.module( 'myApp')
.controller( 'MainCtrl' , function ($scope, bookService) {
$scope.books = [];
bookService.list().success(function(books) {
$scope.books = books;
});
});
Thanks to the controller we can pass our books to the view
The scope the communication channel for the data
13. Q: how can i verify the scope interface?
R: via testing (unit testing)?
14. Add a test case (1/2)
describe( "MainCtrl" , function() {
beforeEach(module( 'myApp'));
beforeEach(inject(function ($controller, $rootScope, $httpBackend) {
scope = $rootScope.$new();
httpBackend = $httpBackend;
MainCtrl = $controller( 'MainCtrl' , {
$scope: scope,
// other mocks
});
}));
// continue...
});
15. Add a test case (2/2)
it('should attach a list of books to the scope' , function () {
// when someone require the "/book" endpoint reply with a valid response
httpBackend.whenGET( "http://localhost:8085/book" ).respond([
{ id: 1, title: "This is a book" , isbn: "9835623" , available: true },
{ id: 2, title: "Another super book" , isbn: "9835624" , available: true },
{ id: 3, title: "A rare book to read" , isbn: "9835625" , available: false }
]);
// force the HTTP data resolution
httpBackend.flush();
// my expectation are that we books in the controller scope.
expect(scope.books.length).toBe( 3);
expect(scope.books.map((item) => item.title)).toEqual([
"This is a book" ,
"Another super book" ,
"A rare book to read"
]);
});
$httpBackend act as a spy for HTTP requests
22. Components testing (2/3)
it("should expose our books" , function() {
$rootScope.books = [
{ id: 1, title: "This is a book" , isbn: "9835623" , available: true }
{ id: 2, title: "Another book" , isbn: "9835624" , available: true }
{ id: 2, title: "A secret book" , isbn: "9835625" , available: false }
];
var element = $compile( '<bookslist books="books"></bookslist>' )($rootScope);
$rootScope.$digest();
var html = element.html();
expect(html).toContain( "This is a book" );
expect(html).toContain( "Another book" );
expect(html).not.toContain( "A secret book" );
});
23. Components testing (3/3)
Single book directive testing
it("should expose a single book" , function() {
$rootScope.book = {
id: 1,
title: "This is a single book" ,
isbn: "9835623" ,
available: true
};
var element = $compile( '<book book="book"></book>' )($rootScope);
$rootScope.$digest();
var html = element.html();
expect(html).toContain( "This is a single book" );
});
24. Add a book creation
We need the create book service method
We need another dummy web component
We use the controller to save the book model
31. Previous Controller (2/2)
it('should create a new book', function () {
// something uses the save method
var book = {title: "This is a book", isbn: "9835623", available: true };
scope.save(book);
// then the backend should works as expected
httpBackend.whenGET("http://localhost:8085/book" ).respond([]);
httpBackend.whenPOST("http://localhost:8085/book" ).respond(
Object.assign({}, book, {id: 1})
);
httpBackend.flush();
expect(scope.book).toEqual({});
expect(scope.books.length).toBe(1);
expect(scope.books[0].id).toBe(1);
expect(scope.books[0].title).toEqual("This is a book");
expect(scope.books[0].isbn).toEqual("9835623");
expect(scope.books[0].available).toBe(true);
});
35. ServerLess environments
Those envs allows us to pay just for every single requests
No requests? No payments! [more or less]
No server maintenance (platform as a service)
36. Amazon Web Services
AWS API Gateway [✔]
AWS Lambda [✔]
AWS DynamoDB [✔]
AWS SNS [ ]
AWS SQS [ ]
AWS CloudWatch [ ]
so many services... [ ... ]
37. API Gateway
Thanks to API Gateway you can create/publish/maintain/monitor
and secure APIs at any scale
Essentially: with API Gateway you declare your APIs interfaces and delegate to another component (like Lambda,
EC2, etc) the functionality
38.
39. Lambda is a compute service that can run the code on your behalf
using AWS infrastructure
40.
41. Amazon DynamoDB is a fully managed NoSQL database service that
provides fast and predictable performance with seamless scalability.
47. I prefer to use Claudia.js
https://github.com/claudiajs/claudia
48. Claudia expose a very simple interface
var ApiBuilder = require('claudiaapibuilder' ),
api = new ApiBuilder();
api.get('/hello', function (request, response) {
return "Hello";
});
Similar to express or hapi
49. You can use return codes and more
features...
api.post('/account' , function (request) {
return api.ApiResponse ({
name: "Walter",
surname: "Dal Mut"
}, 201);
});
50. Claudia.js prepare the whole ApiGateway and Lambda
configuration (with CORS) automatically for you
claudia create
name book module
region euwest 1
apimodule book
config claudiabooks.json
It creates a new module book-module and store in claudia-books.json the Claudia.js con guration
53. Thanks to Joi we can add data validation to our API
var joi = require(' Joi');
var result = joi.object().keys({
id: joi.number().required(),
title: joi. string().min(1).required()
}).validate(request.body);
if (result.error) {
return new api. ApiResponse (result.error, 406);
}
// continue
Claudia.js manage JSON data automatically
54. Claudia.js manage A+ Promises as a
return element
api.post( "/sayok" , function(request) {
var d = q.promise();
setTimeout( function() {
d.resolve( "OK");
}, 2000);
return d.promise;
});
56. For Node.js DynamoDB offers a
"Document client"
docClient = new AWS.DynamoDB.DocumentClient({region: "euwest1" });
docClient.put({
Item: {name: "Walter", surname: "Dal Mut" },
TableName: tableName
}).promise();
docClient.get( ...); // primary key
docClient.query( ...); // on a secondary index and/or primary key
docClient.scan( ...); // search without index (table scan)
All methods have a A+ Promise implementation integrated
57. Put all together
api.post( "/book", function(request) {
var d = q.promise();
var result = joi.object().keys({
title: joi. string().min(1).required(),
isbn: joi. string().min(1).required(),
available: joi.boolean().required()
}).validate(request.body);
if (result.error) {
return new api. ApiResponse (result.error, 406);
}
var id = uuid.v4();
var item = Object.assign({}, {id: id}, request.body);
docClient.put({ Item: item, TableName : tableName}).promise().then(function() {
d.resolve(item);
});;
return d.promise;
});
Of course you can split all responsabilities to di erent components
58. Thank you for listening
If you are interested in workshops and/or training sessions feel free
to contact info@corley.it
Check out also our page for training at: corsi.corley.it
Check out our corporate website: corley.it