new documentation system

This commit is contained in:
Felix Blaschke
2021-10-09 19:36:11 +02:00
parent e067cc9ecd
commit 787f18b572
27 changed files with 110 additions and 404 deletions

114
README.md
View File

@ -4,6 +4,7 @@
the Shelf platform. It's a great base to **start off** your apps fast, while
**maintaining full compatibility** with the **Shelf** ecosystem.
<!-- #code doc_files/quickstart.dart -->
```dart
import 'package:shelf_plus/shelf_plus.dart';
@ -17,15 +18,18 @@ Handler init() {
return app;
}
```
<!-- // end of #code -->
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**.
<!-- #space 1 -->
&nbsp;
## Table of contents
<!-- // end of #space -->
<!-- #toc -->
## Table of Contents
[**Router Plus**](#router-plus)
- [Routes API](#routes-api)
@ -47,16 +51,19 @@ you can't ever code without **Shelf Plus**.
[**Examples**](#examples)
- [Rest Service](#rest-service)
<!-- // end of #toc -->
<!-- #space 1 -->
&nbsp;
<!-- // end of #space -->
## Router Plus
Router Plus is a **high-level abstraction layer** sitting directly on [shelf_router](https://pub.dev/packages/shelf_router).
It **shares the same [routing logic](https://pub.dev/documentation/shelf_router/latest/shelf_router/Router-class.html)**
but allows you to handle responses in a very simple way.
<!-- #code doc_files/router_plus_intro.dart -->
```dart
var app = Router().plus;
@ -72,6 +79,7 @@ app.get('/file', () => File('path/to/file.zip'));
app.get('/person', () => Person(name: 'John', age: 42));
```
<!-- // end of #code -->
The core mechanic is called **ResponseHandler** which continuously refines a data structure,
until it resolves in a [Shelf Response](https://pub.dev/documentation/shelf/latest/shelf/Response-class.html).
@ -79,22 +87,27 @@ This extensible system comes with support for text, json, binaries, files, json
You can access the **Router Plus** by calling the **`.plus`** getter on a regular Shelf Router.
<!-- #code doc_files/router_plus_upgrade.dart -->
```dart
var app = Router().plus;
```
<!-- // end of #code -->
<!-- #space 1 -->
&nbsp;
<!-- // end of #space -->
### Routes API
The API mimics the [Shelf Router](https://pub.dev/documentation/shelf_router/latest/shelf_router/Router-class.html)
methods. You basically use an HTTP verb, define a route to match and specify a handler,
that generates the response.
<!-- #code doc_files/routes_api_verb.dart -->
```dart
app.get('/path/to/match', () => 'a response');
```
<!-- // end of #code -->
You can return any type, as long the **ResponseHandler** mechanism has a capable
resolver to handle that type.
@ -103,6 +116,7 @@ If you need the [Shelf Request](https://pub.dev/documentation/shelf/latest/shelf
object, specify it as the first parameter. Any other parameter will match the
route parameters, if defined.
<!-- #code doc_files/routes_api_signature.dart -->
```dart
app.get('/minimalistic', () => 'response');
@ -115,14 +129,17 @@ app.get('/customer/<id>', (Request request) {
return 'response: ${request.routeParameter('id')}';
});
```
<!-- // end of #code -->
<!-- #space 1 -->
&nbsp;
<!-- // end of #space -->
### Middleware
Router Plus provides several options to place your middleware ([Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html)).
<!-- #code doc_files/middleware_intro.dart -->
```dart
var app = Router().plus;
@ -134,18 +151,22 @@ app.get('/request1', () => 'response', use: middlewareB);
// combine middleware with + operator
app.get('/request2', () => 'response', use: middlewareB + middlewareC);
```
<!-- // end of #code -->
You can also apply middleware dynamically inside a route handler, using the `>>` operator.
<!-- #code doc_files/middleware_in_requesthandler.dart -->
```dart
app.get('/request/<value>', (Request request, String value) {
return middleware(value) >> 'response';
});
```
<!-- // end of #code -->
<!-- #space 1 -->
&nbsp;
<!-- // end of #space -->
### ResponseHandler
ResponseHandler process the **return value** of a route handler, until it matches a
@ -164,6 +185,7 @@ ResponseHandler process the **return value** of a route handler, until it matche
*Example:*
<!-- #code doc_files/response_handler_example.dart -->
```dart
import 'dart:io';
@ -202,12 +224,14 @@ class Person {
Map<String, dynamic> toJson() => {'name': name};
}
```
<!-- // end of #code -->
#### Custom ResponseHandler
You can add your own ResponseHandler by using a [Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html)
created with the `.middleware` getter on a ResponseHandler function.
<!-- #code doc_files/response_handler_custom.dart -->
```dart
// define custom ResponseHandler
ResponseHandler catResponseHandler = (Request request, dynamic maybeCat) =>
@ -218,21 +242,26 @@ app.use(catResponseHandler.middleware);
app.get('/cat', () => Cat());
```
<!-- // end of #code -->
<!-- #code doc_files/response_handler_custom_cat.dart -->
```dart
class Cat {
String interact() => 'Purrrrr!';
}
```
<!-- // end of #code -->
<!-- #space 1 -->
&nbsp;
<!-- // end of #space -->
### Cascading multiple routers
Router Plus is compatible to a [Shelf Handler](https://pub.dev/documentation/shelf/latest/shelf/Handler.html).
So, you can also use it in a [Shelf Cascade](https://pub.dev/documentation/shelf/latest/shelf/Pipeline-class.html).
This package provides a `cascade()` function, to quickly set up a cascade.
<!-- #code doc_files/cascade.dart -->
```dart
import 'package:shelf_plus/shelf_plus.dart';
@ -249,53 +278,56 @@ Handler init() {
return cascade([app1, app2]);
}
```
<!-- // end of #code -->
<!-- #space 2 -->
&nbsp;
&nbsp;
<!-- // end of #space -->
## Middleware collection
This package comes with additional [Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html)
to simplify common tasks.
<!-- #space 1 -->
&nbsp;
<!-- // end of #space -->
### setContentType
Sets the `content-type` header of a `Response` to the specified **mime-type**.
<!-- #code doc_files/mw_set_content_type.dart -->
```dart
app.get('/one', () => setContentType('application/json') >> '1');
app.get('/two', () => '2', use: setContentType('application/json'));
```
<!-- // end of #code -->
&nbsp;
<!-- #space1 -->
### typeByExtension
Sets the `content-type` header of a `Response` to the **mime-type** of the
specified **file extension**.
<!-- #code doc_files/mw_type_by_extension.dart -->
```dart
app.get('/', () => '<h1>Hi!</h1>', use: typeByExtension('html'));
```
<!-- // end of #code -->
&nbsp;
<!-- #space1 -->
### 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**.
<!-- #code doc_files/mw_download.dart -->
```dart
app.get('/wallpaper/download', () => File('image.jpg'), use: download());
@ -304,14 +336,15 @@ app.get('/invoice/<id>', (Request request, String id) {
return download(filename: 'invoice_$id.pdf') >> document;
});
```
<!-- // end of #code -->
<!-- #space 2 -->
&nbsp;
&nbsp;
<!-- // end of #space -->
## Request body handling
Shelf Plus provides an extensible mechanism to process the HTTP body of a request.
@ -319,6 +352,7 @@ You can access it by calling the `.body` getter on a [Shelf Request](https://pub
It comes with build-in support for text, JSON and binary.
<!-- #code doc_files/request_body_intro.dart -->
```dart
app.post('/text', (Request request) async {
var text = await request.body.asString;
@ -330,18 +364,20 @@ app.post('/json', (Request request) async {
return 'You send me: ${person.name}';
});
```
<!-- // end of #code -->
&nbsp;
<!-- #space1 -->
### Object deserialization
A recommended way to deserialize a json-encoded object is to provide a
**reviver function**, that can be generated by code generators.
<!-- #code doc_files/request_body_deserialize1.dart -->
```dart
var person = await request.body.as(Person.fromJson);
```
<!-- // end of #code -->
<!-- #code doc_files/request_body_deserialize2.dart -->
```dart
class Person {
final String name;
@ -354,36 +390,38 @@ class Person {
}
}
```
<!-- // end of #code -->
&nbsp;
<!-- #space1 -->
### Custom accessors for model classes
You can add own accessors for model classes by creating an
extension on `RequestBodyAccessor`.
<!-- #code doc_files/request_body_ext_model1.dart -->
```dart
extension PersonAccessor on RequestBodyAccessor {
Future<Person> get asPerson async => Person.fromJson(await asJson);
}
```
<!-- // end of #code -->
<!-- #code doc_files/request_body_ext_model2.dart -->
```dart
app.post('/person', (Request request) async {
var person = await request.body.asPerson;
return 'You send me: ${person.name}';
});
```
<!-- // end of #code -->
&nbsp;
<!-- #space1 -->
### Custom accessors for third party body parser
You can plug-in any other body parser by creating an
extension on `RequestBodyAccessor`.
<!-- #code doc_files/request_body_ext_third_party.dart -->
```dart
extension OtherFormatBodyParserAccessor on RequestBodyAccessor {
Future<OtherBodyFormat> get asOtherFormat async {
@ -391,19 +429,21 @@ extension OtherFormatBodyParserAccessor on RequestBodyAccessor {
}
}
```
<!-- // end of #code -->
<!-- #space 2 -->
&nbsp;
&nbsp;
<!-- // end of #space -->
## Shelf Run
Shelf Run is **zero-configuration** web-server initializer with **hot-reload** support.
<!-- #code doc_files/shelf_run_intro.dart -->
```dart
import 'package:shelf_plus/shelf_plus.dart';
@ -413,6 +453,7 @@ Handler init() {
return (Request request) => Response.ok('Hello!');
}
```
<!-- // end of #code -->
It's important to use a dedicated `init` function, returning a [Shelf Handler](https://pub.dev/documentation/shelf/latest/shelf/Handler.html),
for hot-reload to work properly.
@ -432,17 +473,14 @@ Shelf Run uses a default configuration, that can be modified via **environment v
You can override the default values with optional parameters in the `shelfRun()` function.
<!-- #code doc_files/shelf_run_override_default.dart -->
```dart
void main() => shelfRun(init, defaultBindPort: 3000);
```
<!-- // end of #code -->
&nbsp;
&nbsp;
<!-- #space2 -->
## Examples
### Rest Service
@ -452,6 +490,7 @@ Implementation of a CRUD, rest-like backend service. ([Full sources](/example/ex
**example_rest.dart**
<!-- #code example/example_rest/bin/example_rest.dart -->
```dart
import 'dart:io';
@ -504,8 +543,10 @@ Handler init() {
return app;
}
```
<!-- // end of #code -->
**person.dart**
<!-- #code example/example_rest/bin/person.dart -->
```dart
import 'package:json_annotation/json_annotation.dart';
import 'package:uuid/uuid.dart';
@ -534,4 +575,5 @@ class Person {
static Person fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
}
```
```
<!-- // end of #code -->

View File

@ -1,7 +1 @@
include: package:lints/recommended.yaml
analyzer:
exclude: [build/**]
#strong-mode:
#implicit-casts: false
#implicit-dynamic: false

View File

@ -7,10 +7,10 @@ late dynamic middleware;
Handler init() {
var app = Router().plus;
//@start
// #begin
app.get('/request/<value>', (Request request, String value) {
return middleware(value) >> 'response';
});
//@end
// #end
return app;
}

View File

@ -7,7 +7,7 @@ late Middleware middlewareB;
late Middleware middlewareC;
Handler init() {
//@start
// #begin
var app = Router().plus;
app.use(middlewareA); // apply to all routes
@ -17,6 +17,6 @@ Handler init() {
// combine middleware with + operator
app.get('/request2', () => 'response', use: middlewareB + middlewareC);
//@end
// #end
return app;
}

View File

@ -8,13 +8,13 @@ dynamic pdfService;
Handler init() {
var app = Router().plus;
//@start
// #begin
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;
});
//@end
// #end
return app;
}

View File

@ -4,10 +4,10 @@ void main() => shelfRun(init);
Handler init() {
var app = Router().plus;
//@start
// #begin
app.get('/one', () => setContentType('application/json') >> '1');
app.get('/two', () => '2', use: setContentType('application/json'));
//@end
// #end
return app;
}

View File

@ -4,8 +4,8 @@ void main() => shelfRun(init);
Handler init() {
var app = Router().plus;
//@start
// #begin
app.get('/', () => '<h1>Hi!</h1>', use: typeByExtension('html'));
//@end
// #end
return app;
}

View File

@ -8,9 +8,9 @@ Handler init() {
var app = Router().plus;
app.post('/json', (Request request) async {
//@start
// #begin
var person = await request.body.as(Person.fromJson);
//@end
// #end
return 'You send me: ${person.name}';
});

View File

@ -2,8 +2,8 @@ import 'package:shelf_plus/shelf_plus.dart';
import 'other_code.dart';
//@start
// #begin
extension PersonAccessor on RequestBodyAccessor {
Future<Person> get asPerson async => Person.fromJson(await asJson);
}
//@end
// #end

View File

@ -6,12 +6,12 @@ void main() => shelfRun(init);
Handler init() {
var app = Router().plus;
//@start
// #begin
app.post('/person', (Request request) async {
var person = await request.body.asPerson;
return 'You send me: ${person.name}';
});
//@end
// #end
return app;
}

View File

@ -6,10 +6,10 @@ class OtherBodyFormat {}
dynamic ThirdPartyLib;
//@start
// #begin
extension OtherFormatBodyParserAccessor on RequestBodyAccessor {
Future<OtherBodyFormat> get asOtherFormat async {
return ThirdPartyLib().process(request.read());
}
}
//@end
// #end

View File

@ -6,7 +6,7 @@ void main() => shelfRun(init);
Handler init() {
var app = Router().plus;
//@start
// #begin
app.post('/text', (Request request) async {
var text = await request.body.asString;
return 'You send me: $text';
@ -16,6 +16,6 @@ Handler init() {
var person = Person.fromJson(await request.body.asJson);
return 'You send me: ${person.name}';
});
//@end
// #end
return app;
}

View File

@ -6,7 +6,7 @@ void main() => shelfRun(init);
Handler init() {
var app = Router().plus;
//@start
// #begin
// define custom ResponseHandler
ResponseHandler catResponseHandler = (Request request, dynamic maybeCat) =>
maybeCat is Cat ? maybeCat.interact() : null;
@ -15,7 +15,7 @@ Handler init() {
app.use(catResponseHandler.middleware);
app.get('/cat', () => Cat());
//@end
// #end
return app;
}

View File

@ -9,7 +9,7 @@ void main() => shelfRun(init);
late dynamic middleware;
Handler init() {
//@start
// #begin
var app = Router().plus;
app.use(middleware());
@ -23,6 +23,6 @@ Handler init() {
app.get('/file', () => File('path/to/file.zip'));
app.get('/person', () => Person(name: 'John', age: 42));
//@end
// #end
return app;
}

View File

@ -3,8 +3,8 @@ import 'package:shelf_plus/shelf_plus.dart';
void main() => shelfRun(init);
Handler init() {
//@start
// #begin
var app = Router().plus;
//@end
// #end
return app;
}

View File

@ -4,7 +4,7 @@ void main() => shelfRun(init);
Handler init() {
var app = Router().plus;
//@start
// #begin
app.get('/minimalistic', () => 'response');
app.get('/with/request', (Request request) => 'response');
@ -15,6 +15,6 @@ Handler init() {
// alternative access to route parameters
return 'response: ${request.routeParameter('id')}';
});
//@end
// #end
return app;
}

View File

@ -4,8 +4,8 @@ void main() => shelfRun(init);
Handler init() {
var app = Router().plus;
//@start
// #begin
app.get('/path/to/match', () => 'a response');
//@end
// #end
return app;
}

View File

@ -1,8 +1,8 @@
import 'package:shelf_plus/shelf_plus.dart';
//@start
// #begin
void main() => shelfRun(init, defaultBindPort: 3000);
//@end
// #end
Handler init() {
return (Request request) => Response.ok('Hello!');

View File

@ -1,118 +0,0 @@
import 'dart:io';
void main() {
process(File('tool/templates/README.md'), File('README.md'));
}
void process(File file, File to) {
print('Processing $file...');
var lines = file.readAsLinesSync();
lines = gapMacro(file, lines);
lines = codeMacro(file, lines);
lines = indexMacro(file, lines);
to.writeAsStringSync(lines.join('\n'));
}
List<String> gapMacro(File docFile, List<String> lines) {
final result = <String>[];
for (var line in lines) {
if (line.trim().startsWith('@gap')) {
var size = int.parse(
line.substring(line.indexOf('@gap') + '@gap'.length).trim());
for (var i = 0; i < size; i++) {
result.add('');
result.add('&nbsp;');
result.add('');
}
} else {
result.add(line);
}
}
return result;
}
List<String> indexMacro(File docFile, List<String> lines) {
final result = <String>[];
for (var line in lines) {
if (line.trim().startsWith('@index')) {
result.add('## Table of contents');
lines
.map((line) => line.trim())
.where((line) => line.startsWith('##'))
.forEach((line) {
var depth = line.indexOf(' ');
var title = line.substring(depth + 1);
var link = '#' + title.toLowerCase().replaceAll(' ', '-');
print('$depth $title');
if (depth == 2) {
result.add('');
result.add('[**$title**]($link)');
} else if (depth == 3) {
result.add(' - [$title]($link)');
}
});
} else {
result.add(line);
}
}
return result;
}
List<String> codeMacro(File docFile, List<String> lines) {
final result = <String>[];
for (var line in lines) {
if (line.trim().startsWith('@code')) {
var path = line.substring(line.indexOf('@code') + '@code'.length).trim();
var file = File(path);
var extension =
file.path.substring(file.path.lastIndexOf('.') + '.'.length);
var code = file.readAsStringSync().trim().split('\n');
code =
code.where((line) => !line.trim().startsWith('// ignore:')).toList();
code = manualTrim(code);
result.add('```$extension');
result.addAll(code);
result.add('```');
} else {
result.add(line);
}
}
return result;
}
List<String> manualTrim(List<String> code) {
if (code.any((line) => line.contains('//@start'))) {
var startLine = code.firstWhere((line) => line.contains('//@start'));
var endLine = code.firstWhere((line) => line.contains('//@end'));
var indent = startLine.indexOf('//');
// Cut out lines
var result = code
.getRange(code.indexOf(startLine) + 1, code.indexOf(endLine))
.toList();
// Intend based on comment
result = result
.map((line) => line.length > indent ? line.substring(indent) : line)
.toList();
return result;
} else {
return code;
}
}

View File

@ -1,212 +0,0 @@
# 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.
@code tool/templates/code/quickstart.dart
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**.
@gap 1
@index
@gap 1
## Router Plus
Router Plus is a **high-level abstraction layer** sitting directly on [shelf_router](https://pub.dev/packages/shelf_router).
It **shares the same [routing logic](https://pub.dev/documentation/shelf_router/latest/shelf_router/Router-class.html)**
but allows you to handle responses in a very simple way.
@code tool/templates/code/router_plus_intro.dart
The core mechanic is called **ResponseHandler** which continuously refines a data structure,
until it resolves in a [Shelf Response](https://pub.dev/documentation/shelf/latest/shelf/Response-class.html).
This extensible system comes with support for text, json, binaries, files, json serialization and Shelf [Handler](https://pub.dev/documentation/shelf/latest/shelf/Handler.html).
You can access the **Router Plus** by calling the **`.plus`** getter on a regular Shelf Router.
@code tool/templates/code/router_plus_upgrade.dart
@gap 1
### Routes API
The API mimics the [Shelf Router](https://pub.dev/documentation/shelf_router/latest/shelf_router/Router-class.html)
methods. You basically use an HTTP verb, define a route to match and specify a handler,
that generates the response.
@code tool/templates/code/routes_api_verb.dart
You can return any type, as long the **ResponseHandler** mechanism has a capable
resolver to handle that type.
If you need the [Shelf Request](https://pub.dev/documentation/shelf/latest/shelf/Request-class.html)
object, specify it as the first parameter. Any other parameter will match the
route parameters, if defined.
@code tool/templates/code/routes_api_signature.dart
@gap 1
### Middleware
Router Plus provides several options to place your middleware ([Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html)).
@code tool/templates/code/middleware_intro.dart
You can also apply middleware dynamically inside a route handler, using the `>>` operator.
@code tool/templates/code/middleware_in_requesthandler.dart
@gap 1
### ResponseHandler
ResponseHandler process the **return value** of a route handler, until it matches a
[Shelf Response](https://pub.dev/documentation/shelf/latest/shelf/Response-class.html).
#### Build-in ResponseHandler
| Source | Result | Use case |
| ---------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `String` | Shelf `Response` | Respond with a text (text/plain) |
| `Uint8List`, `Stream<List<int>>` | Shelf `Response` | Respond with binary (application/octet-stream) |
| `Map<String, dynamic>`, `List<dynamic>>` | Shelf `Response` | Respond with JSON (application/json) |
| Any Type having a `toJson()` method | `Map<String, dynamic>`, `List<dynamic>>` *(expected)* | Provide serialization support for classes |
| Shelf `Handler` | Shelf `Response` | Processing Shelf-based Middleware or Handler |
| `File` (dart:io) | Shelf `Response` | Respond with file contents (using [shelf_static](https://pub.dev/packages/shelf_static)) |
*Example:*
@code tool/templates/code/response_handler_example.dart
#### Custom ResponseHandler
You can add your own ResponseHandler by using a [Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html)
created with the `.middleware` getter on a ResponseHandler function.
@code tool/templates/code/response_handler_custom.dart
@code tool/templates/code/response_handler_custom_cat.dart
@gap 1
### Cascading multiple routers
Router Plus is compatible to a [Shelf Handler](https://pub.dev/documentation/shelf/latest/shelf/Handler.html).
So, you can also use it in a [Shelf Cascade](https://pub.dev/documentation/shelf/latest/shelf/Pipeline-class.html).
This package provides a `cascade()` function, to quickly set up a cascade.
@code tool/templates/code/cascade.dart
@gap 2
## Middleware collection
This package comes with additional [Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html)
to simplify common tasks.
@gap 1
### setContentType
Sets the `content-type` header of a `Response` to the specified **mime-type**.
@code tool/templates/code/mw_set_content_type.dart
@gap1
### typeByExtension
Sets the `content-type` header of a `Response` to the **mime-type** of the
specified **file extension**.
@code tool/templates/code/mw_type_by_extension.dart
@gap1
### 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**.
@code tool/templates/code/mw_download.dart
@gap 2
## 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](https://pub.dev/documentation/shelf/latest/shelf/Request-class.html).
It comes with build-in support for text, JSON and binary.
@code tool/templates/code/request_body_intro.dart
@gap1
### Object deserialization
A recommended way to deserialize a json-encoded object is to provide a
**reviver function**, that can be generated by code generators.
@code tool/templates/code/request_body_deserialize1.dart
@code tool/templates/code/request_body_deserialize2.dart
@gap1
### Custom accessors for model classes
You can add own accessors for model classes by creating an
extension on `RequestBodyAccessor`.
@code tool/templates/code/request_body_ext_model1.dart
@code tool/templates/code/request_body_ext_model2.dart
@gap1
### Custom accessors for third party body parser
You can plug-in any other body parser by creating an
extension on `RequestBodyAccessor`.
@code tool/templates/code/request_body_ext_third_party.dart
@gap 2
## Shelf Run
Shelf Run is **zero-configuration** web-server initializer with **hot-reload** support.
@code tool/templates/code/shelf_run_intro.dart
It's important to use a dedicated `init` function, returning a [Shelf Handler](https://pub.dev/documentation/shelf/latest/shelf/Handler.html),
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```
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 | Enable hot-reload |
You can override the default values with optional parameters in the `shelfRun()` function.
@code tool/templates/code/shelf_run_override_default.dart
@gap2
## Examples
### Rest Service
Implementation of a CRUD, rest-like backend service. ([Full sources](/example/example_rest/))
**example_rest.dart**
@code example/example_rest/bin/example_rest.dart
**person.dart**
@code example/example_rest/bin/person.dart