dust_server 0.2.0
dust_server: ^0.2.0 copied to clipboard
Runtime support for Dust-generated Dart HTTP servers on shelf.
Examples #
Two sizes, and the small one is the point.
One-question examples are single files under 60 lines. Each answers exactly
one question and nothing else, so the answer is not buried in an application.
Named after the question you would type into a search box — handle_head_request,
not advanced_routing.
Complete applications come later, and there will be few of them. They show the pieces working together, which no single-concept file can — and that is the only job they have.
The contract #
Every example in this directory:
- Opens with a doc comment saying what it answers, and how to run it:
dart run example/cors.dart - Carries a
curlblock for the endpoints it serves, so the example can be checked from a terminal rather than only by reading it. - Splits
Router buildApp()out ofmain, so the shared suite in../test/example/can serve it on a loopback port without a process. - Answers one question. A second concept in the file belongs in a second file.
buildApp() takes no arguments — anything the example needs, it constructs. A
complete application takes its dependencies instead, so a test can substitute a
repository.
Routing and requests #
| Question | File |
|---|---|
| The smallest server that answers | hello_world.dart |
| Paths, methods, nesting, merging | routing.dart |
Reading {id} out of the path |
path_params.dart |
| Reading the query string, including repeated keys | query_params.dart |
Reading headers, and the Host the client claims |
headers_and_host.dart |
| Finding the client address behind a proxy | client_ip.dart |
| Reading and setting cookies | cookies.dart |
Answering HEAD from a GET handler |
handle_head_request.dart |
| One fallback for everything unmatched | global_404.dart |
| What happens when a handler throws | error_handling.dart |
| Serving two versions of the same API | versioning.dart |
Bodies and validation #
| Question | File |
|---|---|
| Decoding a JSON body into a model | json_body.dart |
| Decoding an HTML form post | form_body.dart |
| Handling a file upload | multipart_form.dart |
| Accepting an upload larger than memory | multipart_stream.dart |
| Refusing a body that is too big | body_limits.dart |
| Verifying a signed webhook | webhook_signatures.dart |
| Answering 422 with the per-field errors | validation_422.dart |
| Changing what a rejection looks like on the wire | customize_rejection.dart |
Choosing a decoder by content-type |
parse_body_by_content_type.dart |
Extraction #
| Question | File |
|---|---|
| Passing a database or client to a handler | state.dart |
| Writing an extractor of your own | custom_extractor.dart |
Making a missing value None instead of a 400 |
optional_extraction.dart |
| Handing the failure to the handler instead of short-circuiting | fallible_extraction.dart |
| Requiring a bearer token | bearer_auth.dart |
| Accepting an API key, a cookie, or Basic — whichever arrives | credential_schemes.dart |
Databases #
A driver is opened once in main, attached with withState, and read back by a
handler that never opens or closes anything itself. These use SQLite, which
needs nothing installed; the PostgreSQL one shows what changes when the database
is over a socket.
| Question | File |
|---|---|
| Serving queries from a database | sqlite_database.dart |
| A request that must be all-or-nothing | database_transactions.dart |
| Turning a database failure into the right status code | database_errors.dart |
| Paging and sorting from the query string, without concatenating SQL | database_pagination.dart |
| What changes on PostgreSQL | postgres_database.dart |
Layers #
| Question | File |
|---|---|
| Allowing a browser on another origin | cors.dart |
| Compressing responses | compression.dart |
| Giving every request an id that reaches the logs | request_id.dart |
| Recording what was served | access_log.dart |
| Seeing what actually went over the wire | print_request_response.dart |
Making /todos and /todos/ the same route |
normalize_path.dart |
| The response headers a browser uses to lock a page down | security_headers.dart |
| A layer that runs only for routes that matched | route_layer.dart |
| Cutting off a request that takes too long | request_timeout.dart |
| Releasing what a layer owns, on shutdown | disposable_layers.dart |
Responses #
| Question | File |
|---|---|
| Redirecting, and which status to use | redirects.dart |
| Streaming events to a browser | sse.dart |
| A WebSocket on the same router as the HTTP routes | websockets.dart |
| Rendering HTML from a template | templates.dart |
| Serving a built front end, single-page routes included | static_files.dart |
Operations #
| Question | File |
|---|---|
| Shutting down without dropping requests in flight | graceful_shutdown.dart |
| Work that outlives the response | background_tasks.dart |
| Spans, and continuing a trace that started upstream | tracing.dart |
| Using every core | several_isolates.dart |
| Knowing when a serving isolate dies | isolate_failure.dart |
| Serving over TLS | tls.dart |
| Counting requests and timing them | metrics.dart |
| Signed sessions over the cookie extractor | sessions.dart |
| Liveness and readiness, which differ | health_checks.dart |
| Testing an application built on this runtime | testing.dart |
Handing a router to shelf middleware |
router_as_handler.dart |
metrics.dart and sessions.dart are examples rather than runtime features on
purpose. Both are policy: what to count, and what a session means. The runtime
supplies the layer and the cookie extractor and stops there — the same boundary
that keeps authorization out of it.
Every question in this index has a file, and every file is served over a real
socket by ../test/example/ — 216 tests, one group per
example. An example that stops compiling fails the suite, which is what keeps a
directory this size from rotting.
Running them #
dart run example/hello_world.dart