build rest api clients for angularjs
TRANSCRIPT
Build REST API client like a BOSSBuild RESTful API clients for AngularJS, the proper way
Who are you?
@AlmogBaku nice to meet ya`
1. Entrepreneur
2. Co-Founder & CTO @ Rimoto
3. Developer for 10 years
4. GitHub addicted.
5. Blog about entrepreneurship and development:
www.AlmogBaku.com
What are we going to talk about?
● What tha’ heck is REST?
● Why $resource is sucks?
● $http
● Say hi to Restangular
● Do it like a boss
Disclaimer
You wanna know more? Google it!
What tha’ heck is REST?
Representational State Transfer
Watttt??
What is API?
API is a layer that connects two applications.
Application Programing Interface
What is API?
API is actually a common language, that
both of the parties knows.
REST
REST is a Client-Server API
REST
REST is just the regular way the internet works!
GET http://google.com/
REST
REST is just the regular way the internet works!
GET http://google.com/RESPONSE 200 OK
REST
REST is about resources, not about functions.
Book store api:
1. /api/authors/2. /api/authors/:authorId/3. /api/authors/:authorId/books/4. /api/authors/:authorId/books/:bookId5. /api/authors/:authorId/books/:bookId/reviews6. /api/authors/:authorId/books/:bookId/reviews/:reviewId
REST
REST is about resources, not about functions.
Book store api:
1. /api/authors/2. /api/authors/:authorId/3. /api/authors/:authorId/books/4. /api/authors/:authorId/books/:bookId5. /api/authors/:authorId/books/:bookId/reviews6. /api/authors/:authorId/books/:bookId/reviews/:reviewId
REST
GET /api/authors/
[ { "id": 7, "name": "J.R.R. Tolkien", "birthday": "1-3-1892" }, { "id": 183, "name": "Douglas Adams", "birthday": "3-11-1952" }]
REST
REST is about resources, not about functions.
Book store api:
1. /api/authors/2. /api/authors/:authorId/3. /api/authors/:authorId/books/4. /api/authors/:authorId/books/:bookId5. /api/authors/:authorId/books/:bookId/reviews6. /api/authors/:authorId/books/:bookId/reviews/:reviewId
REST
GET /api/authors/187/
{ "id": 183, "name": "J.R.R. Tolkien", "full_name": "John Ronald Reuel Tolkien", "birthday": "1-3-1892", "genre": "SciFi"}
REST
The same URIs can do many different actions...
We can request web pages in one of the following methods:
1. GET - request information about resource
2. POST - create new resource
3. PUT - update resource
4. DELETE - delete resource
5. HEAD - get information only with headers (eg. if resource exists)
6. OPTIONS - list of available methods to the resource (like --help)
REST
Errors are simple http errors
200 - OK
404 - Not found
401 - Unauthorized
500 - Server Error
Etc.
REST
REST is Stateless
- You can’t use cookies
- You need to pass your identification in every request
GET /users/me?access_token=ftjhi89uh5982hbrvt92vgt9qvhg2r0219
REST API+
REST with AngularJS
It’s pretty simple actually.. just use $resource
var Author = $resource('/api/v1/author/:authorId', {authorId:'@id'});Author.get({authorId:183}, function(author) { author.name = 'J.R.R. Tolkien'; author.$save();});
The thing is…It just sucks
The thing is…It just sucks
1. It can be very complex...
var Author = $resource('https://api.rimoto.net/api/v1/author/:authorId', {authorId:'@id'});Author.get({authorId:183}, function(author) { author.name = 'J.R.R. Tolkien'; author.$save();});
var Book = $resource('https://.../api/v1/author/:authorId/books/:bookId', {authorId: 183, bookId:'@id'});Book.get({bookrId:2}, function(book) { book.name = 'Lord of the rings'; book.$save();});
The thing is…It just sucks
2. Doesn’t allow you to parse the API3. No way to set base url application wide4. You can’t handle errors application wide5. You can’t handle headers application wide6. You can’t handle anything application wide
Actually- $resource is okay for anything other than serious apps.
What can we do?
We can use $http, in order to add necessary headers:
We can also use the $http interceptor, in order to handle errors...
var req = { method: 'POST', url: 'http://example.com', headers: { 'Content-Type': 'application/json' }, data: { test: 'test' }};$http(req).success(function(){...}).error(function(){...});
What can we do?
We can also use the $http interceptor, in order to handle errors...
$provide.factory('myHttpInterceptor', function($q, dependency1) { return { 'requestError': function(rejection) { // do something on error if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); }, 'response': function(response) { //parsing here.... return response; }, 'responseError': function(rejection) { // do something on error if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); } };});$httpProvider.interceptors.push('myHttpInterceptor');
What can we do?
We can also use the $http interceptor, in order to handle errors...
$provide.factory('myHttpInterceptor', function($q, dependency1) { return { 'requestError': function(rejection) { // do something on error if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); }, 'response': function(response) { //parsing here.... return response; }, 'responseError': function(rejection) { // do something on error if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); } };});$httpProvider.interceptors.push('myHttpInterceptor');
What can we do?
We can also use the $http interceptor, in order to handle errors...
$provide.factory('myHttpInterceptor', function($q, dependency1) { return { 'requestError': function(rejection) { // do something on error if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); }, 'response': function(response) { //parsing here.... return response; }, 'responseError': function(rejection) { // do something on error if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); } };});$httpProvider.interceptors.push('myHttpInterceptor');
What can we do?
We can also use the $http interceptor, in order to handle errors...
$provide.factory('myHttpInterceptor', function($q, dependency1) { return { 'requestError': function(rejection) { // do something on error if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); }, 'response': function(response) { //parsing here.... return response; }, 'responseError': function(rejection) { // do something on error if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); } };});$httpProvider.interceptors.push('myHttpInterceptor');
Say hi to
Restangular
Restangular
1. Restangular solves ‘em all!
2. Published on 2013
3. Very stable and popular angular-module
4. Active community
5. Allows you to configure your REST API in application level
6. Restangular 2 will support AngularJS 2! (WIP)
7. Very easy to use
Configurations
Define initial settings:
Defining base url:
Defining Content-Type headers:
RestangularProvider.setBaseUrl('http://api.rimoto.net/api/v1/');
RestangularConfigurer.setDefaultHeaders({'Content-Type': 'application/json'});
Say hi to Restangular
Let’s create a new author:
var Authors = Restangular.all('authors');Authors.post({ name: "J.R.R. Tolkien"});
Say hi to Restangular
Lets’ create a new author:
var Authors = Restangular.all('authors');Authors.post({ name: "J.R.R. Tolkien"});
POSThttp://api.rimoto.net/api/v1/authors/
Say hi to Restangular
Lets’ create a new author:
var Authors = Restangular.all('authors');Authors.post({ name: "J.R.R. Tolkien"});
Say hi to Restangular
Get the author:
Restangular.one('authors', 183).get() .then(function(author) { $scope.author = author; });
Say hi to Restangular
Get the author:
Restangular.one('authors', 183).get() .then(function(author) { $scope.author = author; });
GEThttp://api.rimoto.net/api/v1/authors/183
Say hi to Restangular
Get all his books:
$scope.author.getList('books') .then(function(books) { var firstBook = books[0]; firstBook.name="The Hobbit"; firstBook.put(); });
Pretty simple.. huh?
Model parsing
RestangularConfigurer.ExtendModel("books", function(book) { book.published = new Date(book.published); return book;});
We can “transform” data from our API to javascript!
Model parsing
<div class="vote-box"> <button ng-click="voteUp($review)" class="up"><i class="up-arrow"></i></button> <div>{{votes|number}}</div> <button ng-click="voteDown($review)" class="down"><i class="down-arrow"></i></button></div>
Model parsing
<div class="vote-box"> <button ng-click="voteUp($review)" class="up"><i class="up-arrow"></i></button> <div>{{votes|number}}</div> <button ng-click="voteDown($review)" class="down"><i class="down-arrow"></i></button></div>
Vote done on the controller
Model function
But it’s an API function!
Model parsing
How can we solve that?
Model parsing
How can we solve that?
1. Create a transformer2. Add the function to the model!3. Fin.
Model parsing
How can we solve that?
1. Create a transformer2. Add the model this function!3. Fin.
RestangularConfigurer.ExtendModel("review", function(review) { review.voteUp = function() { return review.getAll("votes").post({ vote_up: true }); }; return review;});
Model parsing
We can do use this tool for many cases:
1. Ordering lists2. Special parsing3. Add functions4. Etc.
What about errors?
RestangularConfigurer.setErrorInterceptor(function(response) { if([401, 403, 405].indexOf(response.status)!=-1) { logout(); return false; }});
Build REST client like a BOSS
PRO tips
Bundle your whole API as “SDK”, and extend the Restangular to be your own API service:
module .factory('API', ['Restangular', function(Restangular) { angular.extend(this, Restangular.withConfig(function(RestangularConfigurer) { RestangularConfigurer.setBaseUrl("https://api.rimoto.net"); RestangularConfigurer.setRequestSuffix('/'); RestangularConfigurer.setDefaultHeaders({ Accept: 'application/json;v=1' }); }));
return this; }]);
PRO tips
1. You can set the `id` parameter (eg. for mongodb):
RestangularProvider.setRestangularFields({ id: '_id'});
PRO tips
2. Handle the Access-Tokens on application level
Example for Restangular with ngAuth:
/** Login changed **/$rootScope.$on("Auth.status", function(event, response) { if(response.access_token) { Restangular.setDefaultRequestParams({ access_token: response.access_token }); } else { Restangular.setDefaultRequestParams({ access_token: null }); }});
PRO tips
3. Decouple Restangular to models
4. You can define Restangular to use HATEOAS
5. Keep your API service generic, and handle specific use-cases separately.
module.factory('Users', function(Restangular) { return Restangular.service('users');});
RestangularProvider.setRestangularFields({ selfLink: 'self.link'});
Check it out
github.com/mgonto/restangular
Be creative,and create your own API
Questions?
Thanks.