Dart on the Server: Aqueduct 3 for Dart 2

aqueduct 3

Aqueduct 3.0 for Dart 2.0 is now available on pub.dartlang.org. This major version update introduces many powerful, new features and makes moderate changes to some existing APIs. The release of Aqueduct 3.0 coincides with the release of Dart 2.0, a major change to the Dart language. Additionally, the framework and command line tool are now fully supported on Windows OS.

Aqueduct is a mature HTTP web server framework written in Dart. With over a hundred documentation guides and a full API reference, developers can be extremely productive when building applications with Aqueduct. Aqueduct performs similarly to frameworks like Java’s Spring, Elixir’s Phoenix and JS Express. The framework is composed of well-tested libraries, including request routing; a statically-typed, fluent ORM; a customizable OAuth2 provider; and OpenAPI 3.0 integration.

Much thanks to Google’s Dart team, who has been incredibly supportive and helpful in working with our team to ensure a timely, successful release of Aqueduct 3.0. Along with their efforts on Flutter and AngularDart, we believe that Dart will become a go-to tool for software developers everywhere. Special thanks to Kevin Moore, Dan Grove, Vyacheslav Egorov, Kevin Millikin and the Dart VM team for their support.

New Features for Aqueduct 3.0

The 3.0 feature set is a major step for server-side development with Dart. Additions like ORM database transactions and OpenAPI 3 integration provide a compelling developer experience.

The full changelog can be found here, and the following sections will expand on the important features in 3.0.

Database Transactions

The Aqueduct ORM now supports database transactions. Database transactions allow you to group queries together such that they all must succeed for any to succeed. In Aqueduct, queries in a transaction are enclosed in an anonymous function:

await context.transaction((transaction) async {
  final newObject = await Query.insertObject(transaction, someObject);
  final query = new Query(transaction)
    ..where((s) => s.id).equalTo(1)
    ..values.relationship.id = newObject.id;
  await query.update();
});

Documentation on transactions is available here.

OpenAPI 3 Document Generation

OpenAPI 3 is an open-standard document format that describes an HTTP API. An OpenAPI document enables tools such as documentation pages, client code generation, and mock server implementations.

Aqueduct 3.0 now includes a solution for generating OpenAPI 3.0 documents from your source code. The majority of an application’s OpenAPI document can be generated with no effort from the programmer through reflection and static analysis. Documents are generated by running the aqueduct document command from your project’s directory. An API documentation server can be started with the aqueduct document serve command in your project’s directory.

Try out the aqueduct document client command – it generates a SwaggerUI HTML page that configures and execute requests specific to your application.

Documentation on generating OpenAPI documents is available here.

JSONB Columns

The Aqueduct ORM now supports PostgreSQL JSONB columns. JSONB columns store JSON data, similar to key-value databases like MongoDB. An example usage of a JSONB column looks like this:

final query = Query(context)
  ..values.timestamp = DateTime.now()
  ..values.contents = Document({
    "type": "push",
    "user": "bob",
    "tags": ["v1"]
  });
final event = await query.insert(); 

Documentation on using JSONB columns is available here.

OAuth2 Scope Annotations

OAuth2 scopes can now be applied as annotations to operation methods to more easily control access scopes. An example of this feature looks like this:

class NoteController extends ResourceController {
  @Scope(['notes.readonly'])
  @Operation.get()
  Future getNotes() async => ...;

  @Scope(['notes'])
  @Operation.post()
  Future createNote(@Bind.body() Note note) async => ...;
}

Documentation on using scope annotations is available here.

Test Agents and aqueduct_test

The Aqueduct testing library has moved to its own package, aqueduct_test and improved significantly. Two new concepts have been introduced: agents and harnesses.

An agent is an HTTP client with customizable defaults (e.g., authorization headers) and methods for executing requests. An example usage looks like this:

test("After POST to /thing, GET /thing/:id returns created thing", () async {
  final postResponse = await agent.post("/thing", body: {"key": "value"});
  expectResponse(postResponse, 200);

  final thingId = postResponse.body.as<Map>()["id"];
  final getResponse = await agent.get("/thing/$thingId");
  expectResponse(getResponse, 200, body: {
    "id": thingId,
    "key": "value"
  });
});

Harnesses are extensible application launchers that configure your application to run under test. Mixins provide common behavior, such as conveniences for provisioning a database and managing authorization during testing. Harnesses are most often subclassed once per application, and a new project created from a template includes a harness subclass.

Documentation on using agents and harnesses is available here.

Changes to Common Components

Some features of Aqueduct had to change for Dart 2.0, and others were improved based on developer feedback. The following sections outline changes to the most common framework components. More information on these changes and migrating existing applications are available later in this document.

RequestSink is now ApplicationChannel

ApplicationChannel replaces RequestSink, and the initialization process has been simplified and improved. ApplicationChannel subclasses now implement two methods – one to initialize services, and another to create and link controllers. A sample implementation looks like this:

class AppChannel extends ApplicationChannel {
  Service service;

  @override
  Future prepare() async {
    final config = new AppConfig.fromFile(options.configurationPath);
    service = Service(config.url);
  }

  @override
  Controller get entryPoint {
    final router = new Router();

    router
      .route("/users/[:id]")
      .link(() => new UserController(database));

    return router;
  }
}

The methods pipe, generate and listen have been replaced by link and linkFunction. The link method always takes a closure that returns a Controller. (Whether a new controller is created for each request or the same controller is reused is determined by whether the Controller implements Recyclable<T>. See the documentation here.)

HTTPController is now ResourceController

HTTPController has been renamed to ResourceController, and the annotations for operation methods have been improved. A sample implementation looks like this:

class CityController extends ResourceController {
  @Operation.get()
  Future getAllCities() async {
    return new Response.ok(["Atlanta", "Madison", "Mountain View"]);
  }

  @Operation.get('name')
  Future getCityByName(@Bind.path('name') String name) async {
    return new Response.ok(fetchCityWithName(name));
  }
}

The concepts remain the same, except for one distinction: an operation method is chosen only by its @Operation annotation. The HTTP method and path variables must exactly match the information provided by the @Operation annotation. (Binding a path variable no longer determines which method is selected. See the documentation here.)

Query.where

Applying search criteria to a Query has been changed to use the property selector syntax. This syntax is identical to the syntax used when establishing joining, paging or sorting criteria. Matchers such as whereEqualTo have been replaced by typed, instance methods on the object returned by the property selector. It looks like this:

final query = Query(context)
  ..where((u) => u.name).equalTo("Bob")
  ..where((u) => u.email).isNotNull();

See the documentation here.

2.5 -> 3.0 Migration

Migration is straightforward and can often be done in a few hours or days, depending on the size of the project. Many changes are accounting for renamed APIs, but there are some changes (like those in the above section) that may require more effort. A migration guide is available here.

Install/Update Instructions

To upgrade the ‘aqueduct’ command line tool, run the following command:

pub global activate aqueduct

Projects should update their ‘pubspec.yaml’ file with the following entry:

aqueduct: ^3.0.0

The 3.0.0 CLI may not be used on projects of previous major versions, and an error will be thrown if it is attempted.

We hope you enjoy these changes, please join us in our slack channel and check out the documentation. Also, sign up for the Aqueduct newsletter to get updates on Aqueduct.

Say hello to your new mobile product team.

  • * By filling out this form, I accept stable|kernel’s Privacy Policy.
  • This field is for validation purposes and should be left unchanged.


0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

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