controller 0.1.12 copy "controller: ^0.1.12" to clipboard
controller: ^0.1.12 copied to clipboard

Write HTTP servers with metadata annotated classes with support for validation and security.

Controller #

Controller is a library that facilitates writing HTTP request handlers with Shelf.

Features #

  • Request mapping
  • Validation
  • Security

Getting started #

Add the controller libraries to pubspec.yaml:

dependencies:
  controller: ^0.1.10+1
  json_annotation: ^4.8.0

dev_dependencies:
  build_runner: ^2.3.3
  controller_generator: ^0.1.8
  json_serializable: ^6.6.1

Usage #

Controller #

  • Add the @controller metadata to the class where you want to handle requests.
  • Annotate handler methods with HTTP verbs (@Get, @Put, @Delete, ...).
  • Use @body to deserialize a JSON body to an object.
  • Prefix path parameters with a colon.
import 'package:controller/controller.dart';
import 'package:shelf/shelf.dart';

part 'todo_controller.g.dart';

@controller
class TodoController {
  @Post('/todos')
  Future<Response> addTodo(@body Todo todo) {
    // implementation goes here
  }

  @Get('/todos/:id')
  Future<Response> getTodo(String id) {
    // implementation goes here
  }
}

Request body #

  • Annotate classes mapped to the request body with @validatable.
  • Annotate fields with validator metadata.
  • Add a fromJson() factory method.
import 'package:controller/controller.dart';
import 'package:json_annotation/json_annotation.dart';

part 'todo.g.dart';

@validatable
@JsonSerializable(createToJson: false)
class Todo {
  @notEmpty
  final String id;
  @notEmpty
  final String description;

  Todo({
    required this.id,
    required this.description,
  });

  factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
}

Validation #

  • Validation metadata can be added on request bodies and controller parameters
  • Out-of-the-box available validators include:
    • @Min(value) and @Max(value)
    • @Length(min, max)
    • @notEmpty
    • @Regex(pattern, description)
    • @Unique(existsPredicate)

Returning responses #

  • You can return Shelf Response objects.
  • When returning an object with a toJson method, a 200 OK response is returned with the JSON as body.
import 'package:controller/controller.dart';
import 'package:shelf/shelf.dart';
import 'package:json_annotation/json_annotation.dart';

part 'todo_controller.g.dart';

@controller
class TodoController {
  @Post('/todos')
  // Returning Future<void> will return an empty `200 OK` response.
  Future<void> addTodo(@body Todo todo) {
    // implementation goes here
  }

  @Get('/todos/:id')
  // Returning Future<A> will call `A.toJson()` and return a `200 OK` response
  // with the JSON in the body.
  Future<Todo> getTodo(String id) {
    // implementation goes here
  }
}

@JsonSerializable()
class Todo {
  @notEmpty
  final String id;
  @notEmpty
  final String description;

  Todo({
    required this.id,
    required this.description,
  });

  factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);

  Map<String, dynamic> toJson() => _$TodoToJson(this);
}

Securing requests #

  • Add a @Secured metadata to the endpoints you want to protect with security.
  • @Secured takes a parameter with a condition the request has to comply with.
  • You can either use off the shelf conditions or write your own.
import 'package:controller/controller.dart';
import 'package:shelf/shelf.dart';

part 'todo_controller.g.dart';

@controller
class TodoController {
  @Post('/todos')
  // Check whether the user has a claim "role" with value "todo-editor"
  @Secured(HasClaim('role', 'todo-editor'))
  Future<Response> addTodo(@body Todo todo) {
    // implementation goes here
  }

  @Get('/todos/:id')
  // Custom condition defined below
  @Secured(IsTodoOwner())
  Future<Response> getTodo(String id) {
    // implementation goes here
  }
}

class IsTodoOwner extends SecurityCondition {
  const IsTodoOwner();

  @override
  Future<bool> evaluate(Map<String, dynamic> claims, // the claims found in the token or user database
      Map<String, String> headers, // request headers and parameters
      ) async {
    final userId = claims['userId'];
    // When true, the user is allowed to access the endpoint
    return userId != null && headers['id'].startsWith(userId);
  }
}

Setting up the server #

Run dart run build_runner build to generate dispatcher builders and validators.

Create the request dispatcher with the generated dispatcher builders and wire it in Shelf:

import 'package:controller/controller.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart';
import 'todo/todo_controller.dart';

void main() async {
  final todoController = TodoController();

  // Only required when you have secured controllers
  final security = JwtSecurity(
    issuerUri: Uri.parse('https://issuer.uri.goes.here'),
    clientId: 'your-applications-client-id',
  );

  final dispatcher = createRequestDispatcher([
    // Only controllers with @Secured methods will take the security parameter
    TodoController$DispatcherBuilder(todoController, security),
  ]);
  final handler = Pipeline().addHandler(dispatcher);
  final server = await serve(handler, '0.0.0.0', 8080);
}
3
likes
140
points
208
downloads

Publisher

unverified uploader

Weekly Downloads

Write HTTP servers with metadata annotated classes with support for validation and security.

Repository (GitHub)

Documentation

API reference

License

MIT (license)

Dependencies

jose, logging, openid_client, shelf

More

Packages that depend on controller