Partager via


Custom APIs in Azure Mobile Services

Today Scott Guthrie announced a couple of new features in Azure Mobile Services: custom APIs and Git source control. The Git source control is a preview feature, and I’ll talk about it in a future post, but custom APIs are ready to be used. Scott’s post already talked a little about this feature, but I want to go in a little more depth over this post.

Creating APIs

As mentioned in the original post, creating APIs via the new API tab:

01-CreateAPI

APIs can be used for many different scenarios, but for simplicity sake, I’ll go back to the calculator which I’ve used often in this blog. My calculator will respond to two different kinds of requests: GET and POST, and I want anyone to be able to use my new service, so I’ll set the permission for those two verbs to ‘Everyone’.

02-NewApiAndPermissions

Since my implementation will only expose the GET and POST verbs, the permissions for the other verbs don’t really matter, but as a matter of security in depth I prefer to leave all the verbs which I don’t plan on exposing with the permission set to ‘Administrator’, so that if I expose the other operations in my script by mistake they won’t be exposed to the general public.

Once you click the ‘ok’ button to create the API, you can get click into it and see that it already contains a script file which responds to the POST verb.

Implementing APIs

Custom APIs in Azure Mobile Services are implemented as node.js modules (but, as Scott mentioned in his post, support for .NET implementations is coming in the future), and the requests they respond to are defined as exports to the API module. As can be seen in the default implementation of the API, it already responds to the POST verb, with a simple ‘Hello world’ response.

  1. exports.post = function (request, response) {
  2.     // Use "request.service" to access features of your mobile service, e.g.:
  3.     // var tables = request.service.tables;
  4.     // var push = request.service.push;
  5.  
  6.     response.send(200, "Hello World");
  7. };

The request object contains all the information you need to know about the incoming HTTP request. The object is actually the same object you’d get if you were using the express node module (it’s the module used by the Azure Mobile Service runtime), with some additional objects: the ‘service’ object, which lets you access the Azure Mobile Services-specific objects which were on the global scope in table scripts, such as ‘tables’ and ‘push’; and the ‘user’ object, which is equivalent to the user object passed to the table scripts. For example, this API responds to the POST verb saying whether the user is authenticated or not:

  1. exports.post = function (request, response) {
  2.     var result = {
  3.         isAnonymous: request.user.level === 'anonymous'
  4.     };
  5.     response.send(200, result)
  6. };

Besides those two properties, the request object has all of the HTTP-specific properties (inherited from the express request object) listed on the express documentation for its request object.

So let’s implement the GET handler. To do that, we need to export a get function. For now, let’s start with a simple implementation with all the parameters being passed via the query string.

  1. exports.get = function (req, res) {
  2.     var x = parseInt(req.query.x, 10);
  3.     var y = parseInt(req.query.y, 10);
  4.     var operation = req.query.op;
  5.     var result;
  6.     if (operation === 'add') {
  7.         result = x + y;
  8.     } elseif (operation == 'sub') {
  9.         result = x - y;
  10.     } else {
  11.         res.send(400, { error: 'Operation "' + operation + '" not supported' });
  12.     }
  13.  
  14.     res.send(200, { result: result });
  15. };

So with this export, we can now make a call to https://YOUR_SERVICE.azure-mobile.net/api/calculator?op=add&x=8&y=13 and we’ll get the result. For the POST operation, we can do something similar, receiving the parameters in the request body:

  1. exports.post = function (request, response) {
  2.     var x = request.body.x;
  3.     var y = request.body.y;
  4.     var operation = request.body.operation;
  5.     var result;
  6.     if (operation === 'add') {
  7.         result = x + y;
  8.     } elseif (operation == 'sub') {
  9.         result = x - y;
  10.     } else {
  11.         response.send(400, { error: 'Operation "' + operation + '" not supported' });
  12.     }
  13.  
  14.     response.send(200, { result: result });
  15. };

Or even better, using a common function for both:

  1. exports.post = function (request, response) {
  2.     var x = request.body.x;
  3.     var y = request.body.y;
  4.     var operation = request.body.operation;
  5.     calculateAndRespond(x, y, operation, response);
  6. };
  7.  
  8. exports.get = function (req, res) {
  9.     var x = parseInt(req.query.x, 10);
  10.     var y = parseInt(req.query.y, 10);
  11.     var operation = req.query.op;
  12.     calculateAndRespond(x, y, operation, res);
  13. };
  14.  
  15. function calculateAndRespond(x, y, op, res) {
  16.     var result;
  17.     if (op === 'add') {
  18.         result = x + y;
  19.     } elseif (op == 'sub') {
  20.         result = x - y;
  21.     } else {
  22.         res.send(400, { error: 'Operation "' + op + '" not supported' });
  23.     }
  24.  
  25.     res.send(200, { result: result });
  26. }

But that’s not exactly the URL schema which I want for my calculator. I actually want my operation to be part of the request URL, with the requests being sent to service.azure-mobile.net/api/calculator/operation. When I first tried this, my idea was to use the ‘path’ request property to get the operation from there, as in the code below:

 

  1. exports.get = function (req, res) {
  2.     var x = parseInt(req.query.x, 10);
  3.     var y = parseInt(req.query.y, 10);
  4.     var path = req.path;
  5.     var operation = path.substring('/api/calculator/'.length);
  6.     calculateAndRespond(x, y, operation, res);
  7. };

However, when I sent a GET request to myservice.azure-mobile.net/api/calculator/add?x=6&y=9, I got a 404 response back. First lesson I learned about the “simple” export mechanism: the handlers only respond to requests to the exact path /api/<apiName>, not to its “sub-paths”.

Routing

To support a scenario where we want requests to go not only to /api/<apiName>, but to other paths under this as well, we need to use the routing feature from express. To use the routing feature, we need a different export, called ‘register’. That function will be passed the application (app) object from express, and from there you can register the routes as functions taking request and response objects.

  1. exports.register = function (api) {
  2.     api.get('*', getImplementation);
  3. };
  4.  
  5. function getImplementation(req, res) {
  6.     var x = parseInt(req.query.x, 10);
  7.     var y = parseInt(req.query.y, 10);
  8.     var path = req.path;
  9.     var operation = path.substring('/api/calculator/'.length);
  10.     calculateAndRespond(x, y, operation, res);
  11. }

Notice that inside the ‘register’ export you aren’t limited to registering routes; you can also change application settings as well.

But now that we’re already using routes, we can also take advantage of express’ route parameters feature, in which our route can define some parameters (captures over the URL) which we can access directly in the implementation, without needing to do any URI manipulation ourselves. With that, we can rewrite our calculator with that. And by adding a few more operations, we have our final implementation of our calculator.

  1. exports.register = function (api) {
  2.     api.get('/:op', getImplementation);
  3.     api.post('/:op', postImplementation)
  4. };
  5.  
  6. function getImplementation(req, res) {
  7.     var x = parseInt(req.query.x, 10);
  8.     var y = parseInt(req.query.y, 10);
  9.     var operation = req.params.op;
  10.     calculateAndRespond(x, y, operation, res);
  11. }
  12.  
  13. function postImplementation(req, res) {
  14.     var x = req.body.x;
  15.     var y = req.body.y;
  16.     var operation = req.params.op;
  17.     calculateAndRespond(x, y, operation, res);
  18. }
  19.  
  20. function calculateAndRespond(x, y, op, res) {
  21.     var operations = {
  22.         add: function (x, y) { return x + y; },
  23.         sub: function (x, y) { return x - y; },
  24.         mul: function (x, y) { return x * y; },
  25.         div: function (x, y) { return x / y; }
  26.     };
  27.     if (operations[op]) {
  28.         res.send(200, { result: operations[op](x, y) });
  29.     } else {
  30.         res.send(400, { error: 'Operation "' + op + '" not supported' });
  31.     }
  32. }

Now all is good – the ‘calculator’ API listens to the GET and POST verbs, and all code here is self-contained.

Sharing code

But now we need to implement the same calculator logic in another API. This is a feature that is frequently asked for table scripts, and it will be implemented shortly. For APIs, however, it’s already working, so let’s show how this can be done (if you’re using the Git source control you can implement it in another way, but I’ll leave it for a future post; here I’ll describe how to do it via the portal only).

As I mentioned before, a custom API is implemented as a node.js module. It’s common practice in node to group related functionality in modules, and export the functionality that the module wants to expose to external users. In the custom API modules, we can export operations corresponding to HTTP verbs (or the special ‘register’ function) which are then picked up (via ‘require’) by the Azure Mobile Services runtime to hook it up to the service. But nothing prevents other APIs in the same service from ‘require’-ing other API modules and using them. So in some projects where I want to have some shared code, I just create a new custom API (called ‘shared’, for convention, but any name would do), and, since I don’t intend on exposing it directly to the external world, I use the “administrator” permission for all the HTTP verbs:

03-sharedApi

Now, on that API, I can add our logic as one of the exports – as this is the only code in the script for the ‘shared’ API. For testing purposes, I’ll make the subtraction operation return an incorrect result, to make sure that we’re hitting it correctly.

  1. exports.operations = {
  2.     add: function (x, y) { return x + y; },
  3.     sub: function (x, y) { return 1000 + x - y; },
  4.     mul: function (x, y) { return x * y; },
  5.     div: function (x, y) { return x / y; }
  6. };

And we can change the implementation of our calculator API to use our shared code:

  1. function calculateAndRespond(x, y, op, res) {
  2.     var operations = require('./shared').operations;
  3.     if (operations[op]) {
  4.         res.send(200, { result: operations[op](x, y) });
  5.     } else {
  6.         res.send(400, { error: 'Operation "' + op + '" not supported' });
  7.     }
  8. }

Now, send a GET request to YOUR_SERVICE.azure-mobile.net/api/calculator/sub?x=100&y=10, and you’ll get the (incorrect by design) result of 1090.

Just a final note: there is currently a bug in which sometimes updates to shared code aren’t picked up immediately by the runtime (it should be fixed over the next week or two). If this happens, you can try restarting the service (I find the easiest way to do that is to go to the “configure” tab and toggle the “dynamic schema” feature, then toggle it back). As I said, this should be fixed over the next couple of weeks.

Wrapping up

As usual, please try the new feature and let us know what you think. Over the next few days I’ll have another post how to invoke the custom APIs on the different client SDKs. The support for that is on the latest version of the SDKs from the azure downloads page (except the Android SDK, which should be updated over the next few days).

Comments

  • Anonymous
    June 16, 2013
    Great post, very helpful! There's a typo in the second header: "Impementing APIs". ;-)
  • Anonymous
    June 16, 2013
    Thanks, fixed that typo! :)
  • Anonymous
    June 17, 2013
    Carlos, do you know when the HTML/JS client library will be updated to support these APIs?  Is there a new version already that can be referenced from our HTML apps?  Thanks for the great information!
  • Anonymous
    June 17, 2013
    Matt, the HTML/JS should be updated sometime this week. I'll post an update to this post (or to the next post specifically about the client SDKs) when this happens.Thanks!
  • Anonymous
    June 18, 2013
    Hey Carlos, this is looking better every day.When can we expect the updated sdks to show up on github?The xamarin sdks are starting to really lag behind.
  • Anonymous
    June 23, 2013
    Hi Carlos,Great update! Thank you so much for these new features.About the sharing code part...I'm not sure I got this right: except in the custom api, where can we use the code in ../shared ?I'm trying to 'require' my shared code in my schedule scripts and my table scripts without success.Is this part of the next update or am I doing something wrong?Thanks!
  • Anonymous
    June 24, 2013
    @Matt, the HTML/JS SDK should be updated now.@Ignacio, I don't know when the Xamarin SDKs will be updated, but I should post something when this happens.@Maxence, there's currently a bug for using npm modules within table / scheduler / feedback scripts, this should be fixed over the next couple of weeks.
  • Anonymous
    June 27, 2013
    Thank you Carlos, this is exactly what I was looking for.Just one problem, I have problems accessing values in req.body (I use POST routing).I can see that req.body is containing JSON but when I try to access a variable, I get an undefined value in the console.Do you have any idea ?Thanks
  • Anonymous
    August 02, 2013
    Can we use external nodejs modules, such as crypto?
  • Anonymous
    October 22, 2013
    Carlos, How can I do to use external nodejs modules, such as cheerio?
  • Anonymous
    October 22, 2013
    @Cedric, try 'console.log' the value of req.body, and (typeof req.body). If it's a string, then you need to parse it (using JSON.parse) to make it an actual object. If the request content-type is 'application/json', it should be parsed for you, though, please let us know if this is not the case.
  • Anonymous
    October 22, 2013
    @Gonzalo / @Neto, yes, you can use external node.js modules - anything that NPM supports. You can do that by enabling source control, and from there you can 'npm install' any module you want. The tutorial at www.windowsazure.com/.../store-scripts-in-source-control shows how this can be done.
  • Anonymous
    November 04, 2013
    you mentioned that a bug with using such shared code in scheduler and table scripts will be fixed in the next couple of weeks, any update on that 'cause it still seems to be an issue - or I do something wrong...
  • Anonymous
    November 05, 2013
    The comment has been removed
  • Anonymous
    November 05, 2013
    thx for the reply. Meanwhile I figured out what I was doing wrong too.Cloning my mobile service locally with git showed that the custom api is stored in another directory and that was the problem. I had misunderstood a howto I found about that.Thx anyway!
  • Anonymous
    March 01, 2014
    Hi! This is awesome, and I've been using it for a while.I would like to set different permissions for different registered routes though (i.e User for "GET /" and Admin for "GET /adminstuff").That's not possible, so I thought I'd create a little middleware that checks request.user.level. But (as is clear from the source) it's not possible to pass an array as you would normally do in Express. Is there any good way to do it?Thanks! (awesome post; wouldn't have known about register otherwise)
  • Anonymous
    August 07, 2014
    Good day, I implemented exports.get = function(request,response) of a custom api on a mobile service of azure. I download 5 thousands records from the database and then i prepare the json for the output. The problem is that the time of downloading of all records is too long, for that my script goes into timeout. I was thinking if there is a way to increase the timeout of the response.
  • Anonymous
    August 08, 2014
    @roaned, that's not a good practice in node.js world. Remember that node has only a single thread, and if you spend a lot of time preparing the JSON output, that thread will be blocked and no other requests will be able to reach your service. You should consider splitting your function to return a subset of your records, and some sort of token that can be passed to the same API so that you can get the next subset of the records in your complete response.
  • Anonymous
    August 09, 2014
    @CarlosFigueira ok thanks, then I make this solution