shelf_rest 0.3.2

  • README.md
  • CHANGELOG.md
  • Installing
  • Versions
  • 57

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

Change Log

0.3.0

  • includes shelf_bind 0.9.0 which has many significant and some BREAKING changes. Consult the change logs for shelf_bind

0.2.0

  • completely rewritten on top of new (rewritten) shelf_route. Should be mostly transparent to users except for the following breaking changes:
    • shelf_rest now extends shelf_route.
      • The previous functions such as bindResource, routeableAdapter, restHandler and restRouter have been removed. Now you simply import shelf_rest instead of shelf_route and the handling for shelf_rest is built into the router returned via the router() function.
    • child resources are now added using the @AddAll annotation. The previous method using a map is no longer supported
  • several new features including:
    • annotations for basic route methods (@Get, @Post etc)
    • helpers to create HATEOAS links
    • see README for more details

0.1.5-pre.*

  • experimental HATEOAS support

0.1.4

  • Support query parameters on all methods. Thanks to pajn for the contribution

0.1.4-pre.*

  • experimental HATEOAS support

0.1.3

  • Allow validation of parameters to be configured at the bind function and annotation level

0.1.2+1

  • Doco improvements

0.1.2

  • Turned validation of parameters on by default

0.1.1

  • Add middleware support in the RestMethod annotation

0.1.0

  • First version

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  shelf_rest: "^0.3.2"

2. Install it

You can install packages from the command line:

with pub:


$ pub get

Alternatively, your editor might support pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:shelf_rest/shelf_rest.dart';
        
Version Uploaded Documentation Archive
0.3.5 Oct 21, 2016 Go to the documentation of shelf_rest 0.3.5 Download shelf_rest 0.3.5 archive
0.3.4 Apr 10, 2016 Go to the documentation of shelf_rest 0.3.4 Download shelf_rest 0.3.4 archive
0.3.3 Jan 6, 2016 Go to the documentation of shelf_rest 0.3.3 Download shelf_rest 0.3.3 archive
0.3.2 Aug 12, 2015 Go to the documentation of shelf_rest 0.3.2 Download shelf_rest 0.3.2 archive
0.3.1 Jul 14, 2015 Go to the documentation of shelf_rest 0.3.1 Download shelf_rest 0.3.1 archive
0.3.0 Jul 12, 2015 Go to the documentation of shelf_rest 0.3.0 Download shelf_rest 0.3.0 archive
0.2.3 Jul 4, 2015 Go to the documentation of shelf_rest 0.2.3 Download shelf_rest 0.2.3 archive
0.2.2 Jul 1, 2015 Go to the documentation of shelf_rest 0.2.2 Download shelf_rest 0.2.2 archive
0.2.1 Jun 26, 2015 Go to the documentation of shelf_rest 0.2.1 Download shelf_rest 0.2.1 archive
0.2.0 Jun 25, 2015 Go to the documentation of shelf_rest 0.2.0 Download shelf_rest 0.2.0 archive

All 29 versions...

Analysis

This feature is new.
We welcome feedback.

We analyzed this package, and provided a score, details, and suggestions below.

  • tool failures on Dec 7, 2017
  • Dart: 2.0.0-dev.8.0
  • pana: 0.7.3+1

Scores

Popularity:
Describes how popular the package is relative to other packages. [more]
81
Health:
Code health derived from static analysis. [more]
57
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
0
Overall score:
Weighted score of the above. [more]
57

Platforms

Detected platforms:

Error(s) prevent platform classification.

Suggestions

  • Fix lib/src/router_builder_impl.dart.

    Strong-mode analysis of lib/src/router_builder_impl.dart failed with the following error:

    line: 14 col: 40
    Type parameter bound types must be instantiated.

  • Fix lib/src/router_builder.dart.

    Strong-mode analysis of lib/src/router_builder.dart failed with the following error:

    line: 12 col: 33
    Type parameter bound types must be instantiated.

  • Fix further 3 Dart files.

    Similar analysis of the following files failed:

    • lib/src/resource_processor.dart
    • lib/src/adapters.dart
    • lib/src/util.dart

Dependencies

Package Constraint Resolved Available
Direct dependencies
constrain ^0.2.5 0.2.8
hateoas_models >=0.1.6 <0.1.7 0.1.6 0.2.0
http_exception ^0.1.0 0.1.0
matcher >=0.10.0 <0.13.0 0.12.1+4
option >=0.3.0 <2.0.0 1.2.0
quiver >=0.18.2 <0.22.0 0.20.0 0.26.2
shelf ^0.6.0 0.6.8 0.7.1
shelf_bind >=0.9.1 <0.9.2 0.9.1 0.9.4
shelf_path ^0.1.7 0.1.7 0.1.8
shelf_route >=0.13.5 <0.13.6 0.13.5 0.14.3
Transitive dependencies
async 1.13.3 2.0.1
charcode 1.1.1
collection 1.14.3
concepts 0.2.0
converters 0.0.3
either 0.1.8
http_parser 3.1.1
path 1.5.1
source_span 1.4.0
stack_trace 1.9.1
stream_channel 1.6.2
string_scanner 1.0.2
typed_data 1.1.5
uri 0.10.0 0.11.1
utf 0.9.0+3
Dev dependencies
mockito >=0.8.1 <0.11.0
test ^0.12.0