shelf_bind 0.4.1 copy "shelf_bind: ^0.4.1" to clipboard
shelf_bind: ^0.4.1 copied to clipboard

outdatedDart 1 only

A binding handler for shelf

Binding Handler for Dart Shelf #

Build Status

Introduction #

Provides Shelf middleware that lets you use ordinary Dart functions as Shelf Handlers.

Shelf Bind frees you to:

  • use your own functions without worrying about the Shelf boilerplate
  • focus on writing the business logic with your own classes and let Shelf Bind deal with fitting it in to Shelf

Shelf Bind favours convention over configuration so that you can write the minimal code necessary but still be able to override defaults as needed.

Using #

The bind function creates a Shelf Handler from a normal dart function.

var handler = bind(() => "Hello World");

This creates a Shelf Handler equivalent of

var handler = (Request request) => new Response.ok("Hello World");

If the function returns a Future this will be mapped to a Future<Response>

bind(() => new Future.value("Hello World"))

Now you can set up a Shelf IO server to bring your much needed greeting the world (awthanks)

io.serve(bind(() => "Hello World"), 'localhost', 8080);

Response #

Response Body #

By default the return value of your function is encoded as JSON by calling JSON.encode.

So for example you can return a map

bind(() => { "greeting" : "Hello World" })

This will work for anything that can be encoded as JSON including any of your custom classes

class SayHello {
  String greeting;

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

bind(() => new SayHello()..greeting = "Hello World")

Response Status #

You can override the default status code as described in the section on Annotations.

Shelf Response #

If you want full control over the response you can simply return a Shelf Reponse directly

bind(() => new Response.ok("Hello World"))

Error Response #

Shelf Bind doesn't do any specific formatting for errors. Instead it leaves it to upstream middleware to handle, such as shelf_exception_response.

This allows all your error handling to be kept in one location.

bind(() => throw new BadRequestException())

Sprinkling in some shelf_exception_response middleware

var handler = const Pipeline()
    .addMiddleware(exceptionResponse())
    .addHandler(bind(() => throw new BadRequestException()));

we get a handler that will return a 400 response.

Path Parameters #

Any parameters you add to your function will match to path parameters of the same name.

bind((String name) => "Hello $name")

Shelf Bind supports binding to any path parameters including:

  • path segments like /greeting/fred
  • query parameters like /greeting?name=fred

It accesses the path parameters using Shelf Path which means it will work with any middleware (such as Shelf Route) that uses Shelf Path to store path parameters in the Request context property.

This also means it is not tied to any particular format for representing paths. For example it doesn't matter if the paths are defined like /greeting/:name or /greeting/{name} or /person{?name} or whatever.

Simple Types #

You can also bind to simple types like int

bind((String name, int age) => "Hello $name of age $age"))

Custom Objects #

You can bind path variables to properties of your classes too.

class Person {
  String name;
}

bind((Person person) => "Hello ${person.name}")

If you prefer immutable classes then you can bind to a constructor

class Person {
  final String name;

  Person.build({this.name});
}

The constructor must use named arguments for all the properties and the names must match the request path parameter names.

By default the constructor must be called build. This will be overridable with annotations in the future.

Shelf Request #

If you want the Request object passed in you can have that too ;)

bind((String name, Request request) => "Hello $name ${request.method}")

Tweaking with Annotations #

Response Headers #

You can override the default status (200) that is set on a successful return of the handler method using the ResponseHeaders annotation. You can also have the location header set to the incoming request url. The main use for this is when implementing resource creating via an HTTP PUT.

@ResponseHeaders.created()
String _create(String name) => "Hello $name";

final handler = bind(_create);

You can set the status to anything you like

@ResponseHeaders(successStatus: 204)
String _whatever(String name) => "Hello $name";

Binding to Request Body #

Use the RequestBody annotation to bind a handler parameter to the body of the request instead of path parameters. Note, only one handler parameter can be mapped to the body.

Currently JSON encoded bodies are supported.

bind(@RequestBody() Person person) => "Hello ${person.name}")

which will map from a request body like

{"name":"fred"}

More to come #

The bind function provides named parameters that allow full control over the bindings. Over time these will all be supported via new annotations.

Using With Shelf Route #

One of the main uses of Shelf Bind is with a router like Shelf Route.

As bind returns a Handler you can simply pass that handler into the Shelf Route's Router methods

var myRouter = router()
  ..get('/', bind(() => "Hello World"));

Couldn't be much easier. However, having to wrap all your handler's in the bind adds a bit of noise. To avoid that we can install a HandlerAdapter into the router first. Shelf Bind provides one out of the box.

var myRouter = router(handlerAdapter: handlerAdapter)
  ..get('/', () => "Hello World");

Example #

The following is shows all the example handlers from above using Shelf Route as the router

import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_route/shelf_route.dart' as route;
import 'package:shelf_bind/shelf_bind.dart';
import 'package:shelf_exception_response/exception_response.dart';
import 'dart:async';

void main() {
  var router = route.router(handlerAdapter: handlerAdapter)
      ..get('/', () => "Hello World")
      ..get('/later', () => new Future.value("Hello World"))
      ..get('/map', () => {"greeting" : "Hello World"})
      ..get('/object', () => new SayHello()..greeting = "Hello World")
      ..get('/ohnoes', () => throw new BadRequestException())
      ..get('/response', () => new shelf.Response.ok("Hello World"))
      ..get('/greeting/{name}', (String name) => "Hello $name")
      ..get('/greeting2/{name}{?age}',
          (String name, int age) => "Hello $name of age $age")
      ..get('/greeting3/{name}', (Person person) => "Hello ${person.name}")
      ..get('/greeting5/{name}',
          (String name, shelf.Request request) => "Hello $name ${request.method}");

  var handler = const shelf.Pipeline()
      .addMiddleware(shelf.logRequests())
      .addMiddleware(exceptionResponse())
      .addHandler(router.handler);

  route.printRoutes(router);

  io.serve(handler, 'localhost', 8080).then((server) {
    print('Serving at http://${server.address.host}:${server.port}');
  });
}

class SayHello {
  String greeting;

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

class Person {
  final String name;

  Person.build({this.name});

  Person.fromJson(Map json) : this.name = json['name'];

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


See more detailed example in the project at example/binding_example.dart

More Information #

See the wiki for more details on all the options

TODO #

See open issues.

Contributing #

Contributions are welcome. Please:

  1. fork the repo and implement your changes with good unit test coverage of your changes
  2. create a pull request and include enough detail in the descriptio
0
likes
0
pub points
20%
popularity

Publisher

unverified uploader

A binding handler for shelf

Homepage

License

unknown (LICENSE)

Dependencies

shelf, shelf_path, shelf_route

More

Packages that depend on shelf_bind