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:
Method | Path | Behavior |
---|---|---|
POST | /employees | Creates a new employee |
GET | /employees | Gets all employees |
GET | /employees/:id | Gets one employee by ID |
PUT | /employees/:id | Updates one employee by ID |
DELETE | /employees/:id | Deletes 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));
});