shelf_plus

Last updated:

0 purchases

shelf_plus Image
shelf_plus Images
Add to Cart

Description:

shelf plus

Shelf Plus #
Shelf Plus is a quality of life addon for your server-side development within
the Shelf platform. It's a great base to start off your apps fast, while
maintaining full compatibility with the Shelf ecosystem.

import 'package:shelf_plus/shelf_plus.dart';

void main() => shelfRun(init);

Handler init() {
var app = Router().plus;

app.get('/', () => 'Hello World!');

return app.call;
}
copied to clipboard

It comes with a lot of awesome features, like zero-configuration initializer, build-in hot-reload
and a super powerful and intuitive router upgrade. Continue reading and get to know why
you can't ever code without Shelf Plus.

Table of Contents #
Router Plus

Routes API
Middleware
ResponseHandler
Cascading multiple routers

Middleware collection

setContentType
typeByExtension
download

Request body handling

Object deserialization
Custom accessors for model classes
Custom accessors for third party body parser

Shelf Run

Custom configuration
Multithreading

Examples

Enable CORS
Rest Service
WebSocket chat server


Router Plus #
Router Plus is a high-level abstraction layer sitting directly on shelf_router.
It shares the same routing logic
but allows you to handle responses in a very simple way.

var app = Router().plus;

app.use(middleware());

app.get('/text', () => 'I am a text');

app.get(
'/html/<name>', (Request request, String name) => '<h1>Hello $name</h1>',
use: typeByExtension('html'));

app.get('/file', () => File('path/to/file.zip'));

app.get('/person', () => Person(name: 'John', age: 42));
copied to clipboard

The core mechanic is called ResponseHandler which continuously refines a data structure,
until it resolves in a Shelf Response.
This extensible system comes with support for text, json, binaries, files, json serialization and Shelf Handler.
You can access the Router Plus by calling the .plus getter on a regular Shelf Router.

var app = Router().plus;
copied to clipboard

Routes API #
The API mimics the Shelf Router
methods. You basically use an HTTP verb, define a route to match and specify a handler,
that generates the response.

app.get('/path/to/match', () => 'a response');
copied to clipboard

You can return any type, as long the ResponseHandler mechanism has a capable
resolver to handle that type.
If you need the Shelf Request
object, specify it as the first parameter. Any other parameter will match the
route parameters, if defined.

app.get('/minimalistic', () => 'response');

app.get('/with/request', (Request request) => 'response');

app.get('/clients/<id>', (Request request, String id) => 'response: $id');

app.get('/customer/<id>', (Request request) {
// alternative access to route parameters
return 'response: ${request.routeParameter('id')}';
});
copied to clipboard

Middleware #
Router Plus provides several options to place your middleware (Shelf Middleware).

var app = Router().plus;

app.use(middlewareA); // apply to all routes

// apply to a single route
app.get('/request1', () => 'response', use: middlewareB);

// combine middleware with + operator
app.get('/request2', () => 'response', use: middlewareB + middlewareC);
copied to clipboard

You can also apply middleware dynamically inside a route handler, using the >> operator.

app.get('/request/<value>', (Request request, String value) {
return middleware(value) >> 'response';
});
copied to clipboard

ResponseHandler #
ResponseHandler process the return value of a route handler, until it matches a
Shelf Response.
Build-in ResponseHandler



Return type
Use case




String
Respond with a text (text/plain)


Uint8List, Stream<List<int>>
Respond with binary (application/octet-stream)


Map<String, dynamic>, List<dynamic>>
Respond with JSON (application/json)


Any Type having a toJson() method
Serialization support for classes


File (dart:io)
Respond with file contents (using shelf_static)


WebSocketSession (shelf_plus)
Create a websocket connection (using shelf_web_socket)


Handler (shelf)
Processing Shelf-based Middleware or Handler



Example:

import 'dart:io';

import 'package:shelf_plus/shelf_plus.dart';

void main() => shelfRun(init);

Handler init() {
var app = Router().plus;

app.get('/text', () => 'a text');

app.get('/binary', () => File('data.zip').openRead());

app.get('/json', () => {'name': 'John', 'age': 42});

app.get('/class', () => Person('Theo'));

app.get('/list-of-classes', () => [Person('Theo'), Person('Bob')]);

app.get('/iterables', () => [1, 10, 100].where((n) => n > 9));

app.get('/handler', () => typeByExtension('html') >> '<h1>Hello</h1>');

app.get('/file', () => File('thesis.pdf'));

app.get(
'/websocket',
() => WebSocketSession(
onOpen: (ws) => ws.send('Hello!'),
onMessage: (ws, data) => ws.send('You sent me: $data'),
onClose: (ws) => ws.send('Bye!'),
));

return app.call;
}

class Person {
final String name;

Person(this.name);

// can be generated by tools (i.e. json_serializable package)
Map<String, dynamic> toJson() => {'name': name};
}
copied to clipboard

Custom ResponseHandler
You can add your own ResponseHandler by using a Shelf Middleware
created with the .middleware getter on a ResponseHandler function.

// define custom ResponseHandler
ResponseHandler catResponseHandler = (Request request, dynamic maybeCat) =>
maybeCat is Cat ? maybeCat.interact() : null;

// register custom ResponseHandler as middleware
app.use(catResponseHandler.middleware);

app.get('/cat', () => Cat());
copied to clipboard


class Cat {
String interact() => 'Purrrrr!';
}
copied to clipboard

Cascading multiple routers #
Router Plus is compatible to a Shelf Handler.
So, you can also use it in a Shelf Cascade.
This package provides a cascade() function, to quickly set up a cascade.

import 'package:shelf_plus/shelf_plus.dart';

void main() => shelfRun(init);

Handler init() {
var app1 = Router().plus;
var app2 = Router().plus;

app1.get('/maybe', () => Response.notFound('no idea'));

app2.get('/maybe', () => 'got it!');

return cascade([app1.call, app2.call]);
}
copied to clipboard

import 'package:shelf_plus/shelf_plus.dart';

void main() => shelfRun(init);

Handler init() {
var app1 = Router().plus;
var app2 = Router().plus;

app1.get('/maybe', () => Response.notFound('no idea'));

app2.get('/maybe', () => 'got it!');

return cascade([app1, app2]);
}
copied to clipboard
Middleware collection #
This package comes with additional Shelf Middleware
to simplify common tasks.
setContentType #
Sets the content-type header of a Response to the specified mime-type.

app.get('/one', () => setContentType('application/json') >> '1');

app.get('/two', () => '2', use: setContentType('application/json'));
copied to clipboard


typeByExtension #
Sets the content-type header of a Response to the mime-type of the
specified file extension.

app.get('/', () => '<h1>Hi!</h1>', use: typeByExtension('html'));
copied to clipboard


download #
Sets the content-disposition header of a Response, so browsers will download the
server response instead of displaying it. Optionally you can define a specific file name.

app.get('/wallpaper/download', () => File('image.jpg'), use: download());

app.get('/invoice/<id>', (Request request, String id) {
File document = pdfService.generateInvoice(id);
return download(filename: 'invoice_$id.pdf') >> document;
});
copied to clipboard

app.get('/wallpaper/download', () => File('image.jpg'), use: download());

app.get('/invoice/<id>', (Request request, String id) {
File document = pdfService.generateInvoice(id);
return download(filename: 'invoice_$id.pdf') >> document;
});
copied to clipboard
Request body handling #
Shelf Plus provides an extensible mechanism to process the HTTP body of a request.
You can access it by calling the .body getter on a Shelf Request.
It comes with build-in support for text, JSON and binary.

app.post('/text', (Request request) async {
var text = await request.body.asString;
return 'You send me: $text';
});

app.post('/json', (Request request) async {
var person = Person.fromJson(await request.body.asJson);
return 'You send me: ${person.name}';
});
copied to clipboard


Object deserialization #
A recommended way to deserialize a json-encoded object is to provide a
reviver function, that can be generated by code generators.

var person = await request.body.as(Person.fromJson);
copied to clipboard


class Person {
final String name;

Person({required this.name});

// created with tools like json_serializable package
static Person fromJson(Map<String, dynamic> json) {
return Person(name: json['name']);
}
}
copied to clipboard


Custom accessors for model classes #
You can add own accessors for model classes by creating an
extension on RequestBodyAccessor.

extension PersonAccessor on RequestBodyAccessor {
Future<Person> get asPerson async => Person.fromJson(await asJson);
}
copied to clipboard


app.post('/person', (Request request) async {
var person = await request.body.asPerson;
return 'You send me: ${person.name}';
});
copied to clipboard


Custom accessors for third party body parser #
You can plug-in any other body parser by creating an
extension on RequestBodyAccessor.

extension OtherFormatBodyParserAccessor on RequestBodyAccessor {
Future<OtherBodyFormat> get asOtherFormat async {
return ThirdPartyLib().process(request.read());
}
}
copied to clipboard

extension OtherFormatBodyParserAccessor on RequestBodyAccessor {
Future<OtherBodyFormat> get asOtherFormat async {
return ThirdPartyLib().process(request.read());
}
}
copied to clipboard
Shelf Run #
Shelf Run is zero-configuration web-server initializer with hot-reload support.

import 'package:shelf_plus/shelf_plus.dart';

void main() => shelfRun(init);

Handler init() {
return (Request request) => Response.ok('Hello!');
}
copied to clipboard

It's important to use a dedicated init function, returning a Shelf Handler,
for hot-reload to work properly.
To enable hot-reload you need either run your app with the IDE's debug profile, or
enable vm-service from the command line:
dart run --enable-vm-service my_app.dart
copied to clipboard
Custom configuration #
Shelf Run uses a default configuration, that can be modified via environment variables:



Environment variable
Default value
Description




SHELF_PORT
8080
Port to bind the shelf application to


SHELF_ADDRESS
localhost
Address to bind the shelf application to


SHELF_HOTRELOAD
true
Enables hot-reload


SHELF_SHARED
false
Enables shared option for multithreading



You can override the default values with optional parameters in the shelfRun() function.

void main() => shelfRun(init, defaultBindPort: 3000);
copied to clipboard

Multithreading #
Dart supports multithreading using isolates. This might increases the performance by utilizing more cores.
You can enable the shared by setting the defaultShared parameter or the SHELF_SHARED environment variable to true.
Example of an application using multiple isolates

import 'dart:isolate';
import 'package:shelf_plus/shelf_plus.dart';

void main() {
const numberOfIsolates = 8;

for (var i = 0; i < numberOfIsolates - 1; i++) {
Isolate.spawn(spawnServer, null, debugName: i.toString()); // isolate 0..7
}
spawnServer(null); // use main isolate as the 8th isolate
}

void spawnServer(_) => shelfRun(init, defaultShared: true);

Handler init() {
var app = Router().plus;

app.get('/', () async {
await Future.delayed(Duration(milliseconds: 500)); // simulate load
return 'Hello from isolate: ${Isolate.current.debugName}';
});

return app.call;
}
copied to clipboard

You can test this application and compare different count of isolates:
xargs -I % -P 8 curl "http://localhost:8080" < <(printf '%s\n' {1..400})
copied to clipboard




Examples #
Enable CORS #
CORS can be enabled by using the shelf_cors_headers package:

import 'package:shelf_cors_headers/shelf_cors_headers.dart';
import 'package:shelf_plus/shelf_plus.dart';

void main() => shelfRun(init);

Handler init() {
final router = Router().plus;

router.get('/', () => {'data': 'This API is CORS enabled.'});

return corsHeaders() >> router;
}
copied to clipboard

Rest Service #
Implementation of a CRUD, rest-like backend service. (Full sources)
example_rest.dart

import 'dart:io';

import 'package:shelf_plus/shelf_plus.dart';

import 'person.dart';

void main() => shelfRun(init);

final data = <Person>[
Person(firstName: 'John', lastName: 'Doe', age: 42),
Person(firstName: 'Jane', lastName: 'Doe', age: 43),
];

Handler init() {
var app = Router().plus;

/// Serve index page of frontend
app.get('/', () => File('frontend/page.html'));

/// List all persons
app.get('/person', () => data);

/// Get specific person by id
app.get('/person/<id>',
(Request request, String id) => data.where((person) => person.id == id));

/// Add a new person
app.post('/person', (Request request) async {
var newPerson = await request.body.as(Person.fromJson);
data.add(newPerson);
return {'ok': 'true', 'person': newPerson.toJson()};
});

/// Update an existing person by id
app.put('/person/<id>', (Request request, String id) async {
data.removeWhere((person) => person.id == id);
var person = await request.body.as(Person.fromJson);
person.id = id;
data.add(person);
return {'ok': 'true'};
});

/// Remove a specific person by id
app.delete('/person/<id>', (Request request, String id) {
data.removeWhere((person) => person.id == id);
return {'ok': 'true'};
});

return app;
}
copied to clipboard

person.dart

import 'package:json_annotation/json_annotation.dart';
import 'package:uuid/uuid.dart';

part 'person.g.dart';

/// run 'dart run build_runner build' to update model

@JsonSerializable()
class Person {
String? id;
final String firstName;
final String lastName;
final int age;

Person({
String? id,
required this.firstName,
required this.lastName,
required this.age,
}) {
this.id = id ?? Uuid().v4();
}

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

static Person fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
}
copied to clipboard

WebSocket chat server #
Implementation of a WebSocket-based chat application. (Full sources)
example_websocket_chat.dart

import 'dart:io';

import 'package:shelf_plus/shelf_plus.dart';

void main() => shelfRun(init);

Handler init() {
var app = Router().plus;

// HTML-based web client
app.get('/', () => File('public/html_client.html'));

// Track connected clients
var users = <WebSocketSession>[];

// Web socket route
app.get(
'/ws',
() => WebSocketSession(
onOpen: (ws) {
// Join chat
users.add(ws);
users
.where((user) => user != ws)
.forEach((user) => user.send('A new user joined the chat.'));
},
onClose: (ws) {
// Leave chat
users.remove(ws);
for (var user in users) {
user.send('A user has left.');
}
},
onMessage: (ws, dynamic data) {
// Deliver messages to all users
for (var user in users) {
user.send(data);
}
},
),
);

return app;
}
copied to clipboard

License:

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Customer Reviews

There are no reviews.