Explore the use of Aqueduct, the Dart server-side framework, to bind a REST Interface to Postgres, producing more efficient results.

In the Dart server-side framework Aqueduct, a ManagedObjectController<T> automatically maps the standard HTTP methods to a database query. That is, adding the following route:

router
  .route("/employees/[:id]")
  .generate(() => new ManagedObjectController<Employee>());

Gives us the following endpoints:

MethodPathBehavior
POST/employeesCreates a new employee
GET/employeesGets all employees
GET/employees/:idGets one employee by ID
PUT/employees/:idUpdates one employee by ID
DELETE/employees/:idDeletes one employee by ID

(See a working example here.)

The endpoint to get all employees has some options for modifying which objects are returned. These are passed as query parameters. Simple paging looks like this:

GET /employees?count=10&offset=10

Property-based paging looks like this:

// Gets the first 10 hired employees
GET /employees?count=10&pageBy=hireDate&pageAfter=null

// Get the next 10 hired employees after 7/27/17
GET /employees?count=10&pageBy=hireDate&pageAfter=2017-07-27T00:00:00Z

And the results can be sorted by one or more properties, too:

GET /employees?sortBy=lastName,asc
GET /employees?sortBy=lastName,asc&sortBy=firstName,desc

Sometimes, this simple REST-to-table binding is enough. Other times, you may need to add a bit more logic by subclassing ManagedObjectController<T>. For example, let’s say we want to also include an employee’s manager along with the employee, but only when we fetch a single employee:

class EmployeeController extends ManagedObjectController<Employee> {
  @override
  Future<Query<Employee>> willFindObjectWithQuery(Query<Employee> query) async {
    query.join(object: (employee) => employee.manager);

    return query;
  }
}

Or let’s say we’d like to filter by some property of an employee when fetching many:

class EmployeeController extends ManagedObjectController<Employee> {
  @override
  Future<Query<Employee>> willFindObjectsWithQuery(Query<Employee> query) async {
    var titleFilter = request.innerRequest.uri.queryParameters["title"];
    if (titleFilter != null) {
      query.where.title = whereEqualTo(titleFilter);
    }

    return query;
  }
}

You could do this for generically for any property with a little magic:

class EmployeeController extends ManagedObjectController<Employee> {
  @override
  Future<Query<Employee>> willFindObjectsWithQuery(Query<Employee> query) async {
    request.innerRequest.uri.queryParameters.forEach((property, value) {
      if (query.entity.attributes[property] != null) {
        query.where[property] = whereEqualTo(value);
      }
    });

    return query;
  }
}

If your needs outgrow ManagedObjectController<T>, then the ubiquitous HTTPController is the next step. This process of starting with higher-leveled features and gradually descending closer to the core is a key design principle of Aqueduct.

For even more fun, you can dynamically create a REST interface for every table in a database:

var dataModel = new ManagedDataModel.fromCurrentMirrorSystem();
dataModel.entities.forEach((e) {
  router
    .route("/${e.tableName}/[:id]")
    .generate(() => new ManagedObjectController.forEntity(e));
});

 

 

Joe Conway

Founder at Stable Kernel

Leave a Reply

Your email address will not be published. Required fields are marked *