KO
|
EN
gitlite — search
Search
#typescript
#ai-agents
#ai
#dsh-plugin
#deepseek-harness
#open-source
#cli
#claude-code
#codex
#developer-tools
#react
#windows
shelf_plus
★ 78
Open GitHub ↗
No description available.
Download README (.md)
Explore Similar Repositories
vscode-graphql
:
Apollo GraphQL VS Code extension
fastapi-asyncapi
:
OpenAPI is not enough for you? 😡 Let's try AsyncAPI! :angel:
gameserver-ingress-controller
:
Automatic Ingress configuration for Game Servers managed by Agones
ui5-typescript-helloworld
:
Showcase of a TypeScript setup for developing UI5 applications.
Captcha-Tools
:
All-in-one Python (And now Go!) module to help solve captchas with Capmonster, 2captcha, Anticaptcha, and Capsolver API's!
// repository documentation
Was this content helpful?
★ 0
(0 ratings)
Select Rating:
★
★
★
★
★
Submit Feedback
Recent Feedback
×
Download README
Do you want to download the
README.md
file for
shelf_plus
?
Download (.md)
<!-- This file uses generated code. Visit https://pub.dev/packages/readme_helper for usage information. --> # 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 doc_files/quickstart.dart --> ```dart import 'package:shelf_plus/shelf_plus.dart'; void main() => shelfRun(init); Handler init() { var app = Router().plus; app.get('/', () => 'Hello World!'); return app.call; } ``` <!-- // 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**. <!-- #toc --> ## Table of Contents [**Router Plus**](#router-plus) - [Routes API](#routes-api) - [Middleware](#middleware) - [ResponseHandler](#responsehandler) - [Cascading multiple routers](#cascading-multiple-routers) [**Middleware collection**](#middleware-collection) - [setContentType](#setcontenttype) - [typeByExtension](#typebyextension) - [download](#download) [**Request body handling**](#request-body-handling) - [Object deserialization](#object-deserialization) - [Custom accessors for model classes](#custom-accessors-for-model-classes) - [Custom accessors for third party body parser](#custom-accessors-for-third-party-body-parser) [**Shelf Run**](#shelf-run) - [Custom configuration](#custom-configuration) - [Multithreading](#multithreading) [**Examples**](#examples) - [Enable CORS](#enable-cors) - [Rest Service](#rest-service) - [WebSocket chat server](#websocket-chat-server) <!-- // end of #toc --> ## 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; 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)); ``` <!-- // 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). 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 doc_files/router_plus_upgrade.dart --> ```dart var app = Router().plus; ``` <!-- // end of #code --> ### 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. 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 doc_files/routes_api_signature.dart --> ```dart 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')}'; }); ``` <!-- // end of #code --> ### 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; 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); ``` <!-- // 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 --> ### 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 | 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](https://pub.dev/packages/shelf_static)) | | `WebSocketSession` (shelf_plus) | Create a websocket connection (using [shelf_web_socket](https://pub.dev/packages/shelf_web_socket)) | | `Handler` (shelf) | Processing Shelf-based Middleware or Handler | _Example:_ <!-- #code doc_files/response_handler_example.dart --> ```dart 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}; } ``` <!-- // end of #code --> **WebSocket Subprotocol Support:** The `WebSocketSession` also provides access to the optional `subprotocol` property, which contains the subprotocol negotiated between client and server (if any): <!-- #code doc_files/web_socket_subprotocol.dart --> ```dart app.get( '/ws', () => WebSocketSession( protocols: ['chat', 'superchat'], onOpen: (ws) { if (ws.subprotocol != null) { print('Negotiated subprotocol: ${ws.subprotocol}'); } }, ), ); ``` <!-- // 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) => maybeCat is Cat ? maybeCat.interact() : null; // register custom ResponseHandler as middleware 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 --> ### 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'; 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]); } ``` <!-- // end of #code --> ```dart 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]); } ``` ## Middleware collection This package comes with additional [Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html) to simplify common tasks. ### 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 --> <!-- #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 --> <!-- #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()); app.get('/invoice/<id>', (Request request, String id) { File document = pdfService.generateInvoice(id); return download(filename: 'invoice_$id.pdf') >> document; }); ``` <!-- // end of #code --> ```dart 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; }); ``` ## 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 doc_files/request_body_intro.dart --> ```dart 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}'; }); ``` <!-- // end of #code --> <!-- #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; Person({required this.name}); // created with tools like json_serializable package static Person fromJson(Map<String, dynamic> json) { return Person(name: json['name']); } } ``` <!-- // end of #code --> <!-- #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 --> <!-- #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 { return ThirdPartyLib().process(request.read()); } } ``` <!-- // end of #code --> ```dart extension OtherFormatBodyParserAccessor on RequestBodyAccessor { Future<OtherBodyFormat> get asOtherFormat async { return ThirdPartyLib().process(request.read()); } } ``` ## 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'; void main() => shelfRun(init); 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. 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 ``` ### 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](https://api.dart.dev/stable/2.16.0/dart-io/HttpServer/bind.html#:~:text=The%20optional%20argument-,shared,-specifies%20whether%20additional) option for multithreading | 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 --> ### Multithreading Dart supports multithreading using isolates. This might increases the performance by utilizing more cores. You can enable the [shared](https://api.dart.dev/stable/2.16.0/dart-io/HttpServer/bind.html#:~:text=The%20optional%20argument-,shared,-specifies%20whether%20additional) by setting the `defaultShared` parameter or the `SHELF_SHARED` environment variable to `true`. **Example of an application using multiple isolates** <!-- #code doc_files/isolates.dart --> ```dart 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; } ``` <!-- // end of #code --> You can test this application and compare different count of isolates: ```bash xargs -I % -P 8 curl "http://localhost:8080" < <(printf '%s\n' {1..400}) ``` <!-- #space 2 --> <!-- // end of #space --> ## Examples ### Enable CORS [CORS](https://developer.mozilla.org/docs/Web/HTTP/CORS) can be enabled by using the [shelf_cors_headers](https://pub.dev/packages/shelf_cors_headers) package: <!-- #code example/example_cors/bin/example_cors.dart --> ```dart 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.call; } ``` <!-- // end of #code --> ### 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 --> ```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.call; } ``` <!-- // 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'; 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); } ``` <!-- // end of #code --> ### WebSocket chat server Implementation of a WebSocket-based chat application. ([Full sources](example/example_websocket_chat/)) **example_websocket_chat.dart** <!-- #code example/example_websocket_chat/bin/example_websocket_chat.dart --> ```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.call; } ``` <!-- // end of #code -->