shelf_rest 0.3.4 copy "shelf_rest: ^0.3.4" to clipboard
shelf_rest: ^0.3.4 copied to clipboard

outdatedDart 1 only

Shelf components that makes it easy to create uniform, hierarchical REST resources.

REST Handler for Dart Shelf #

Build Status Pub Version

Introduction #

Provides Shelf components that makes it easy to create uniform, hierarchical REST resources with minimal boilerplate.

shelf_rest is a drop in replacement of shelf_route. It supports all the functionality of shelf_route with many additions to reduce boilerplate.

###Routing Choices###

There are a number of choices for routing in the shelf world. Here is a simple guide to help you choose between a few of them.

  1. shelf_route. Good choice if:
    • you want a powerful router with a fluent api
    • you don't want to use mirrors or annotations
    • you prefer a bit more boilerplate over any magic that comes with mirrors
  2. shelf_rest. Good choice if:
    • you want all the features of shelf_route plus
    • you are happy to use annotations (supported by mirrors) to significantly reduce boilerplate
    • you like consistency in your REST APIs and like support to help with that
  3. mojito. Good choice if:
    • you want all the features of shelf_rest plus
    • you want a light framework that provides a fluent api on many other shelf components for things like:
      • authentication & authorisation;
        • serving static resources via the filesystem or via pub serve;
        • oauth;
        • logging and more

In short, if you want to build your own stack then shelf_route and shelf_rest will likely suit you better. If you want a more fully featured framework, whilst still being highly extensible, then mojito is the better option.

To get a good overview of the options you have, read the blog post Routing Options in Shelf.

Basic Usage #

Instead of importing shelf_route

import 'package:shelf_route/shelf_route.dart';

you import shelf_rest

import 'package:shelf_rest/shelf_rest.dart';

Note: don't import both at the same time.

If you wish, you can continue to use it exactly the same as shelf_route, such as.


import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_rest/shelf_rest.dart';

void main() {
  var myRouter = router()
    ..get('/accounts/{accountId}', (Request request) {
      var account =
          new Account.build(accountId: getPathParameter(request, 'accountId'));
      return new Response.ok(JSON.encode(account));
    });

  printRoutes(myRouter);
  
  io.serve(myRouter.handler, 'localhost', 8081);
}

class Account {
  final String accountId;

  Account.build({this.accountId});

  Account.fromJson(Map json) : this.accountId = json['accountId'];

  Map toJson() => {'accountId': accountId};
}


Using normal Dart functions as Handlers #

As shelf_rest automatically bundles shelf_bind you can now remove much of this boiler plate.

  var myRouter = router()
    ..get('accounts/{accountId}',
        (String accountId) => new Account.build(accountId: accountId));

Here the accountId path parameter was automatically extracted from the request and passed in as a variable to the handler function. Additionally, the returned account is automatically converted into JSON.

See the documentation for shelf_bind for more details on the features you an use with your handlers.

Grouping routes into classes #

You can group routes into classes and mount these at a given subpath using the addAll method.

class AccountResource {
  void createRoutes(Router r) {
    r..get('{accountId}', (String accountId) => new Account.build(accountId: accountId));
  }
}

void main() {
  var myRouter = router()..addAll(new AccountResource(), path: 'accounts');

  printRoutes(myRouter);

  io.serve(myRouter.handler, 'localhost', 8081);
}

As the createRoutes method in UserResource takes a single argument of type Router, this will automatically be called.

Using Route annotations #

Instead of implementing a method that takes a Router, like createRoutes above, you can use a Get annotation.

class AccountResource {
  @Get('{accountId}')
  Account find(String accountId) => new Account.build(accountId: accountId);
}

Annotations exist for all the methods on Router such as @Get, @Post, @Put, @Delete and@AddAll and these annotations support exactly the same arguments as the corresponding methods.

The @AddAll annotation is used to add nested routes (child resources). For example

class AccountResource {
  @AddAll(path: 'deposits')
  DepositResource deposits() => new DepositResource();
}

Note: @AddAll is currently only supported on methods. Support on getters likely in a future version

Using the RestResource annotation #

Most REST resources tend to include many of the standard CRUD operations.

To further reduce boilerplate and help enforce consistency, shelf_rest has special support for implementing these CRUD operations.

For example a RESTful resource for a bank account might have the following types of operations

Search Accounts

GET /accounts?name='Freddy'

Fetch a single Account

GET /accounts/1234

Create an Account

POST /accounts

Update an Account

PUT /accounts/1234

Delete an Account

DELETE /accounts/1234

This is the standard pattern in shelf_rest and can be implemented as follows


@RestResource('accountId')
class AccountResource {
  List<Account> search(String name) => .....;

  Account create(Account account) => .....;

  Account update(Account account) => .....;

  Account find(String accountId) => ...;

  void delete(String accountId) => ...;
}

The @RestResource('accountId') annotation is used to denote classes that support the standard CRUD operations and tells shelf_rest to use accountId as the path variable. The route for DELETE would look like

DELETE /accounts/{accountId}

shelf_rest follows a standard naming convention to minimise configuration. This also serves to promote consistency in how you name your methods.

You can however override the default naming using the ResourceMethod annotation

@ResourceMethod(operation: RestOperation.FIND)
Account fetchAccount(String accountId) => ...;

Hierarchical Resources #

It is common to create hierarchical REST resources.

For example, we might want to allow deposits to be made to our account as follows

PUT ->  /accounts/1234/deposits/999

You add child resources using the standard @AddAll annotation described above.

@RestResource('accountId')
class AccountResource {

  ....

  @AddAll(path: 'deposits')
  DepositResource deposits() => new DepositResource();
}

Where the DepositResource might look like

@RestResource('depositId')
class DepositResource {

  @ResourceMethod(method: 'PUT')
  Deposit create(Deposit deposit) => ...;
}

Note, that the default HTTP method for a create operation is POST. PUT is often used when we know the primary key of the resource when we invoke the create.

In shelf_rest we do that by overriding the HTTP method with the ResourceMethod annotation.

To see this in action we use the printRoutes function

printRoutes(router);

You can see that the following routes were created

GET    ->  /accounts{?name}                            => bound to search method
POST   ->  /accounts                                   => bound to create method
GET    ->  /accounts/{accountId}                       => bound to find method
PUT    ->  /accounts/{accountId}                       => bound to update method
DELETE ->  /accounts/{accountId}                       => bound to delete method
PUT    ->  /accounts/{accountId}/deposits/{depositId}  => bound to create method of DepositResource

Note that any arguments that are not existing path variables will be added to the query of the uri template. So

List<Account> search(String name) => .....;

produces

GET    ->  /accounts{?name}

Middleware #

You can add middleware that will be included in the route created for a resource method using the ResourceMethod annotation.

@ResourceMethod(middleware: logRequests)
Account find(String accountId) => ...;

Similarly you can add them to all the Route annotations like Get and AddAll. For example

  @AddAll(path: 'deposits', middleware: logRequests)
  DepositResource deposits() => new DepositResource();

Validation #

As shelf_bind is used to create Shelf handlers from the resource methods, validation of request parameters comes for free (courtesy of constrain).

See the shelf_bind and constrain doco for details.

By default, validation is turned off. You can turn validation on for specific resource methods

@ResourceMethod(validateParameters: true)
Account find(String accountId) => ...;

You can also turn it on at any level of the router hierarchy by passing creating a new handlerAdapter. For example you can turn it on for all routes as follows

var router = router('/accounts', new AccountResource(),
    handlerAdapter: handlerAdapter(validateParameters: true,
        validateReturn: true);

HATEOAS Support #

shelf_rest has support for returning responses with HATEOAS links. The models for manipulating these links are in the hateoas_models package and may also be used on the client.

To use, simply add an argument to your handler methods of type ResourceLinksFactory. For example

AccountResourceModel find(
  String accountId, ResourceLinksFactory linksFactory) =>
    new AccountResourceModel(
        new Account.build(accountId: accountId), linksFactory(accountId));

The AccountResourceModel here is just a simple class that includes both the Account and the HATEOAS resource links.

class AccountResourceModel extends ResourceModel<Account> {
  final Account account;

  AccountResourceModel(this.account, ResourceLinks links) : super(links);

  Map toJson() => super.toJson()..addAll({'account': account.toJson()});
}

A typical response for the find operation looks like

{
    "account": {
        "accountId": "123",
        "name": 'fred'
    },
    "links": [
        {
            "href": "123",
            "rel": "self"
        },
        {
            "href": "123",
            "rel": "update"
        },
        {
            "href": "123/deposits/{?deposit}",
            "rel": "deposits.create"
        }
    ]
}

Mix and Match #

All the different forms of specifying routes can be used together. A common approach is to use the @RestResource approach for the standard CRUD operations together with the @Get, @Post, @Put, @Delete annotations for operations that don't fit the standard model.

Using methods that take Router as their only argument (called RouteableFunctions) provides a more fluent alternative. Particularly useful with a framework like mojito that extends the Router with fluent apis for creating oauth routes for example.

Conventions #

shelf_rest uses the following conventions by default. Each can be overriden with annotations.

  • create ... POST

TODO: more doco

0
likes
0
pub points
13%
popularity

Publisher

unverified uploader

Shelf components that makes it easy to create uniform, hierarchical REST resources.

Homepage

License

unknown (LICENSE)

Dependencies

constrain, hateoas_models, http_exception, matcher, option, quiver, shelf, shelf_bind, shelf_path, shelf_route

More

Packages that depend on shelf_rest