syntaxhighlighter

Tuesday, August 28, 2012

REST - express-examples/sqlite

I've developed a very simple RESTful app, only considering CRUD operations over an user entity.

Naming convention

I followed some good recommendations mentioned in this article, shared by jjeronimo.

In general, the API is:

  • getUsers. Retrieves all users (SELECT * FROM user).
  • curl -X GET http://localhost:3000/rest/users
    
  • getUsersById. Retrieves an user by its id (SELECT * FROM user WHERE id = ?).
  • curl -X GET http://localhost:3000/rest/users/1
    
  • addUser. Adds an user (INSERT INTO user(name) VALUES(?)).
  • curl -X PUT -H "Content-type:application/x-www-form-urlencoded" -d "name=rodolfo" http://localhost:3000/rest/users
    
  • updateUser. Updates an user (UPDATE user SET name = ? WHERE id = ?).
  • curl -X POST -H "Content-type:application/x-www-form-urlencoded" -d "id=1&name=juan" http://localhost:3000/rest/users
    
  • removeUser. Removes an user by its id (DELETE FROM user WHERE id = ?).
  • curl -X DELETE -H "Content-type:application/x-www-form-urlencoded" -d "id=1" http://localhost:3000/rest/users
    

As you can see, the URLs follow the pattern: rest/users. Some comments about this:

  • URLs start with the word "rest" for easily identifying them as part of the REST API. Another part indicating the API version can be added, like: rest/v1/users (util for backwards compatibility issues).
  • A REST API should be easy to discover, just adding and removing parameters. For example: if you want all users can use /users, but if you want an specific user can use /users/1. Maybe a better option, for more complex cases, could be: /users/id/1, because this way results "evident" something like: /users/name/rodolfo/status/0. However, I prefer to specify getXxxById following the entity name (written in plural) by its id.

Facade-Service

I think REST APIs should be coded using Facades. The idea is to decouple business logic into some classes, and treat REST, SOAP, and whatever interfaces aside.

Following an example extracted from /lib/rest/user.js:

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

var userService = require('../service/user');

this.addUser = function(req, res) {
  var params = req.body;

  common.call(
    res, 
    params.name, 
    function(name) { 
      var valid = true;

      if (name == null) 
        valid = false; 
  
      return valid;
    },
    userService.newInstance().addUser
  );
};

// ...

As you can see in the code above, inside the REST addUser method there are only operations related with parameters filling and validations. For this specific implementation, I coded a generic method for calling services (common.call). But you could just take parameters, validate them, call the corresponding service method, and return the expected output; sort of controller in a MCV implementation.

Call method for integrating Service - REST Facade

I wrote a method call (lib/rest/common.js) for integrating Service methods and REST facades. Following the code main highlights:

// ...

this.call = function(res, params, validate, execute) {
  if (validate(params)) {
    if (params != null) {
      execute(params, function(result) { // TODO: Add extra parameter for error msg
        if (result) 
          ok(res); 
        else 
          nok(res, HTTP_CODE_FORBIDDEN); 

        res.end(JSON.stringify(result)); 
      });
    } else {
      execute(function(result) { // TODO: Add extra parameter for error msg
        if (result) 
          ok(res); 
        else 
          nok(res, HTTP_CODE_FORBIDDEN); 

        res.end(JSON.stringify(result)); 
      });
    }
  } else {
    nok(res, HTTP_CODE_PRECONDITION_FAILED); 
    res.end(); 
  }
};

// ...

The call method executes validate method using params, if true, calls execute method, if not, returns HTTP code 412 (HTTP_CODE_PRECONDITION_FAILED).

  • res: HttpResponse object
  • params: Can be value or object (JSON)
  • validate: Function used for validation, if returns true everything is OK (HTTP_CODE_OK=200), if not, something went wrong and returns HTTP_CODE_PRECONDITION_FAILED(412)
  • execute: Function executed if validate=true. Parameters (params) can be value (e.g. String) or Object (JSON). Callback should receive one parameter (result).

An example about how to use the common.call method can be found above (this.addUser).

No comments:

Post a Comment