Building RESTful Web APIs with Dart, Aqueduct, and PostgreSQL — Part 2: Routing
Tags: api, webdev, dart, sql
Published: 2018-05-18 16:14:21

---
title: Building RESTful Web APIs with Dart, Aqueduct, and PostgreSQL — Part 2: Routing
published: true
description: Learn how to build your own RESTful Web APIs with Dart, Aqueduct, and PostgreSQL
tags: api, webdev, dart, sql
series: Building RESTful Web APIs with Dart, Aqueduct, and PostgreSQL
---

![Featured image for Building RESTful Web APIs with Dart, Aqueduct and PostgreSQL](https://thepracticaldev.s3.amazonaws.com/i/dv19znif0x9wpvdcdt13.jpg)

---

_**PLEASE NOTE: As of Dart 2 the API for Aqueduct has changed, leading to breaking changes. This article was based on Aqueduct 2.5.0 for Dart v1.**_

_**I have updated this as a new video series: [http://bit.ly/aqueduct-tutorial](http://bit.ly/aqueduct-tutorial)**_

---

In Part 1 we had a brief overview of Aqueduct, its features and learnt how to set up the example project using its CLI tool.

This article is part of a series, covering these topics:

* [Part 1: Setting up and running the example](https://dev.to/graphicbeacon/building-restful-web-apis-with-dart-aqueduct-and-postgresql-55k)
* **Part 2: Implementing routing with CRUD operations** (we’re here)
* [Part 3: Connecting Web APIs to PostgreSQL database](https://dev.to/graphicbeacon/building-restful-web-apis-with-dart-aqueduct-and-postgresqlpart-3-postgres-9c7)
* [Part 4: Writing automated tests](https://dev.to/graphicbeacon/building-restful-web-apis-with-dart-aqueduct-and-postgresqlpart-4-testing-3974)
* [**\*Bonus content\*** DB Migration and Model Relationships](https://dev.to/graphicbeacon/building-restful-web-apis-with-dart-aqueduct-and-postgresqlbonus-content-551g) 😄

In this part, we’ll be implementing a custom route with CRUD(_create, read, update, delete_) capabilities.

Before jumping into this, we need to understand the concept of Routers and HTTPControllers. This will inform the way we proceed. At the end of this part, we’ll have our endpoint in place, with the ability to manipulate our data source through the CRUD actions we define.

---

## What is a Router?

Routers are responsible for capturing the request path and determining the logic that runs based on it. The request path is defined by registering a route when calling the `route` method on a `Router` object that is supplied to us. The registration occurs when the `setupRouter` method in our **_FaveReadsSink_** subclass is called:

```dart
// lib/fave_reads_sink.dart
@override
void setupRouter(Router router) {...}
```
The `setupRouter` method provides a Router object as a parameter, which we then use to define each of our routes:

```dart
@override
void setupRouter(Router router) {
  router.route('/router-1').listen(...);
  router.route('/router-2').listen(...);
  router.route('/router-3').listen(...); // and so on
}
```
Calling the route method accepts a string containing the path name, followed by a `listen` method that then allows us to define the logic to run when a request is made to this path. The route can contain _path variables_, which are placeholder tokens that represent whatever value is in that segment of the path:

```dart
router.route('/items/:itemID');
```
The example above declares a path variable `itemID`, which matches “**/items/0**”, “**/items/1**”, “**/items/foo**” and so on. The value of `itemID` will be “**0**”, “**1**” and “**foo**” respectively.

Path variables can also be optional, so that allows us to set them like so:

```dart
router.route('/items/[:itemID]');
```
This means that route-matching will also include “**/items**” as a path name.

_The documentation on Routers is quite comprehensive and I’d recommend [checking that out](https://aqueduct.io/docs/http/routing)._

## So, how do I apply this?

With this knowledge let’s open up `lib/fave_reads_sink.dart` from the project and amend `setupRouter` implementation as follows:

```dart
@override
void setupRouter((Router router) async {
  router.route('/books[/:index]').listen((Request incomingRequest) async {
    return new Response.ok('Showing all books.');
  });
  router.route('/').listen((Request incomingRequest) async {
    return new Response.ok('<h1>Welcome to FaveReads</h1>')
      ..contentType = ContentType.HTML;
  });
});
```
The root path(/) now returns HTML content. We also have a “**/books**” route defined that accepts an optional path variable named `index`. This will be the endpoint for our CRUD operations.

Invoking the route method returns a `RouteController` which exposes the `listen` method for us to define our logic to run in. There are two other methods we can use, namely `pipe` and `generate`. The latter allows us to create a new HTTPController object that gives better handling of our request (_more on that later_).

The `listen` method accepts a closure containing a `Request` object that represents an incoming request. We can then pull the information we need from that, perform transformations, and return a response.

The current logic for “**/books**” return the same response regardless of the request action. Let’s modify this to return a different response for each of our actions:

```dart
router.route('/books[/:index]').listen((Request incomingRequest) async {
  String reqMethod = incomingRequest.innerRequest.method;
  String index = incomingRequest.path.variables["index"];
  
  if (reqMethod == 'GET') {
    if(index != null) {
      return new Response.ok('Showing book by index: $index');
    }
    return new Response.ok('Showing all books.');
  } else if (reqMethod == 'POST') {
    return new Response.ok('Added a book.');
  } else if (reqMethod == 'PUT') {
    return new Response.ok('Added a book.');
  } else if (reqMethod == 'DELETE') {
    return new Response.ok('Added a book.');
  }
  // If all else fails
  return new Response(405, null, 'Not sure what you\'re asking here');
});
```
This works as expected by the code quality gets pretty ugly quickly. It’s repetitive and makes an easy mess of things:

![Image of spaghetti meatballs via Giphy](https://thepracticaldev.s3.amazonaws.com/i/e99xrgpufaerqzi0sdep.gif)

This can be cleaned up using an `HTTPController`!

## What is an HTTPController?

HTTPControllers respond to HTTP requests by mapping them to a particular ‘handler method’ to generate a response. A request is sent to an HTTPController by a Router as long as its path is matched.

To create one we will create a `BooksController` that extends `HTTPController` in order to add the behaviour we want. Let’s do this now. Create `controller/books_controller.dart` in the `lib` directory with the below content:

```dart
import '../fave_reads.dart';

class BooksController extends HTTPController {
  // invoked for GET /books
  @httpGet // HTTPMethod meta data
  Future<Response> getAllBooks() async => new Response.ok('Showing all books');
  
  // invoked for GET /books/:index
  @httpGet // HTTPMethod meta data
  Future<Response> getBook(@HTTPPath("index") int idx) async => new Response.ok('Showing single book');
  
  // invoked for POST /books
  @httpPost // HTTPMethod meta data
  Future<Response> addBook() async => new Response.ok('Added a book');
  
  // invoked for PUT /books
  @httpPut // HTTPMethod meta data
  Future<Response> updateBook() async => new Response.ok('Updated a book');
  
  // invoked for DELETE /books
  @httpDelete  // HTTPMethod meta data
  Future<Response> deleteBook() async => new Response.ok('Deleted a book');
}
```
Here’s a summation of what’s happening:

1. The BooksController subclass consists of 5 handler methods, known as responder methods.
2. Each responder method is annotated with a constant reflecting the appropriate request method: `@httpGet`, `@httpPost`, `@httpPut`, `@httpDelete`. Other methods will use `HTTPMethod`, like `@HTTPMethod('PATCH')`.
3. Each responder method returns a Future of type `Response`. _Futures are to Dart what Promises are to JavaScript_.
4. A responder method can bind values from the request to its arguments. We see this with the `getBook()` responder method argument: `@HTTPPath("index") int idx`. It's path variable `index` is cast to an integer and assigned to a variable named `idx`.

If none of the responder methods match the request method(_e.g. PATCH_), a `405 Method Not Allowed` response is returned.

Let’s go to `lib/fave_reads_sink.dart` and use this controller:

```dart
import 'fave_reads.dart';
import 'controller/books_controller.dart'; // don't forget to import!
// ...
// ...
@override
void setupRouter((Router router) async {
  router
    .route('/books/[:index]')
    .generate(() => new BooksController()); // replaces `listen` method
// ...
```
and run our project by doing `aqueduct serve` or `dart bin/main.dart` in the terminal.

We can test our responses by using [Postman](https://www.getpostman.com/apps).

![Image of Postman app](https://thepracticaldev.s3.amazonaws.com/i/df8jwytrzh6lrr3agguz.png)

## Mocking our datasource

For our datasource, let’s create an array inside `controller/books_controller.dart`:

```dart
import '../fave_reads.dart';

List books = [
  {
    'title': 'Head First Design Patterns',
    'author': 'Eric Freeman',
    'year': 2004
  },
  {
    'title': 'Clean Code: A handbook of Agile Software Craftsmanship',
    'author': 'Robert C. Martin',
    'year': 2008
  },
  {
    'title': 'Code Complete: A Practical Handbook of Software Construction',
    'author': 'Steve McConnell',
    'year': 2004
  },
];

class BooksController extends HTTPController {...}
```
We will then update our responder methods inside `BooksController` to manipulate this dataset:

```dart
class BooksController extends HTTPController {
  @httpGet
  Future<Response> getAll() async => new Response.ok(books);

  @httpGet
  Future<Response> getSingle(@HTTPPath("index") int idx) async {
    if (idx < 0 || idx > books.length - 1) { // index out of range
      return new Response.notFound(body: 'Book does not exist');
    }
    return new Response.ok(books[idx]);
  }

  @httpPost
  Future<Response> addSingle() async {
    var book = request.body.asMap(); // `request` represents the current request. This is a property inside HTTPController base class
    books.add(book);
    return new Response.ok(book);
  }

  @httpPut
  Future<Response> replaceSingle(@HTTPPath("index") int idx) async {
    if (idx < 0 || idx > books.length - 1) { // index out of range
      return new Response.notFound(body: 'Book does not exist');
    }
    var body = request.body.asMap();
    for (var i = 0; i < books.length; i++) {
      if (i == idx) {
        books[i]["title"] = body["title"];
        books[i]["author"] = body["author"];
        books[i]["year"] = body["year"];
      }
    }
    return new Response.ok(body);
  }

  @httpDelete
  Future<Response> delete(@HTTPPath("index") int idx) async {
    if (idx < 0 || idx > books.length - 1) { // index out of range
      return new Response.notFound(body: 'Book does not exist');
    }
    books.removeAt(idx);
    return new Response.ok('Book successfully deleted.');
  }
}
```
_[Learn more about Dart's array/list methods](https://api.dartlang.org/stable/1.24.3/dart-core/List-class.html)_

Restart the server again and test this out with [Postman](https://www.getpostman.com/apps).

**Please note: This is running in an isolate, which means that any side-effects can only be seen in Postman’s(_or whatever tool_) session. Opening a separate session(_like the browser_) will not show these changes. This is because isolates by design do not share state. Not to worry though–this will be resolved when we implement the real database.**

## Refactoring the solution

I should have finished already, but that will create more work for us in Part 3. I don’t want that to happen, therefore please bear with me on this final stretch 😊

So remember when I said you could bind values from the request to the arguments of a responder method? Well, we can refactor our POST operation to convert its payload to a map through the `@HTTPBody()` metadata:

```dart
@httpPost
Future<Response> addSingle(@HTTPBody() Map book) async {
  books.add(book);
  return new Response.ok('Added new book.');
}
```
Here, an attempt is made to parse the request payload as a `Map` type. We could also specify a custom type, not just using the inbuilt ones, as long as the custom type extends an `HTTPSerializable` type. Let’s do this by introducing a Book model inside `lib/model/book.dart`:

```dart
import '../fave_reads.dart';

class Book extends HTTPSerializable {
  String title;
  String author;
  int year;

  Book({this.title, this.author, this.year});

  @override
  Map<String, dynamic> asMap() => {
    "title": title,
    "author": author,
    "year": year,
  };

  @override
  void readFromMap(Map requestBody) {
    title = requestBody["title"];
    author = requestBody["author"];
    year = requestBody["year"];
  }
}
```
Here’s what is happening in summary:

1. Our Book model implements `HTTPSerializable`, which is a utility used to parse information from an HTTP request
2. Defining `asMap` and `readFromMap(Map requestBody)` methods. The first will be used when a JSON response is being sent back to the client, while the latter retrieves the request body and extracts the data for populating our model’s properties.

Now we just need to use this model:

```dart
// lib/controller/book_controller.dart

import '../fave_reads.dart';
import 'model/book.dart';

List books = [
  new Book(
    title: 'Head First Design Patterns',
    author: 'Eric Freeman',
    year: 2004
  ),
  new Book(
    title: 'Clean Code: A handbook of Agile Software Craftsmanship', 
    author: 'Robert C. Martin', 
    year: 2008
  ),
  new Book(
    title: 'Code Complete: A Practical Handbook of Software Construction',
    author: 'Steve McConnell',
    year: 2004
  ),
];

class BooksController extends HTTPController {
  // ...
  // ...
  Future<Response> addSingle(@HTTPbody() Book book) async { // note the `Book` type being used
    books.add(book);
    return new Response.ok(book);
  }
  //...
  //...
}
```
Restart the server and test the results with Postman.

## Conclusion

We’ve made some significant progress by fleshing out the skeleton of our web APIs. I hope that this journey has been a fun challenge so far. I’d encourage you to go through the further reading materials below to grasp the concepts we’ve covered.

As always **I’m open to receiving feedback**. Let me know what you liked about this tutorial, what you disliked and what you would like to see in future. I’d really be grateful for that.

And this concludes Part 2 of the series. The source code is [available on github](https://github.com/graphicbeacon/favereads) and will be updated as we go through the series. Stay tuned for more.

---

## Further reading

* [Routing and path variables](https://aqueduct.io/docs/http/routing/)
* [Handling Requests: HTTPController](https://aqueduct.io/docs/http/http_controller/)

---

{% link https://dev.to/graphicbeacon/building-restful-web-apis-with-dart-aqueduct-and-postgresqlpart-3-postgres-9c7 %}

---

_Originally posted on [Medium](https://itnext.io/building-restful-web-apis-with-dart-aqueduct-and-postgresql-part-2-routing-with-crud-operations-629fe58114fa)_