Introduction
Relic is a web server framework for Dart, built by the Serverpod team. It grew out of Shelf, and it keeps Shelf's model of handlers that turn requests into responses and middleware that wraps handlers.
A Relic server is an ordinary Dart program. This one answers a GET request for / with a greeting:
import 'package:relic/relic.dart';
Future<void> main() async {
final app = RelicApp()
..get('/', (req) => Response.ok(body: Body.fromString('Hello, world!\n')));
await app.serve();
}
Run it, ask it for http://localhost:8080/, and it answers Hello, world!. The Hello world page builds on this program one step at a time.
The pieces
The program above already shows most of what Relic is made of, and the reference pages take the pieces in turn.
A handler is a function that receives a Request and returns a result. Usually the result is a Response, but it can also upgrade the connection to a WebSocket, or take the connection over entirely. The router picks a handler by the method and the path of the request, and a path can hold parameters, wildcards and tails. Middleware wraps the handlers below a path, to log, check credentials or change responses, and context properties carry what it learns to the handler. The request gives a handler typed access to the URL, the query, the headers, the body and forms, and the response and its body decide what goes back on the wire. Headers are read and written through typed accessors, and a program can declare its own. Static files are served by a ready-made handler, hot reload applies most changes without a restart, and serving is where the address, the isolates and the adapter are chosen.
Features
🔒 Typed
Headers and path and query parameters are read as typed values, and a header that does not parse gets a 400.
⚡ Fast
A trie-based router, with headers and URLs parsed only when they are read.
🔌 WebSockets
A handler upgrades a connection by returning a WebSocketUpgrade.
🔧 Middleware
Middleware attaches to a path in the router and wraps every route below it.
👨💻 Hot reload
The router is rebuilt on every hot reload, so changed handlers apply without a restart.
🚀 Tested
More than 3,000 tests, run on Linux, macOS and Windows.
How Relic differs from Shelf
If you know Shelf, most of Relic will look familiar. These are the places where it works differently:
| Area | Shelf | Relic |
|---|---|---|
| Headers | Strings in a map | Typed accessors that validate values |
| Routing | shelf_router, which tries routes in order | A built-in router on a trie, matched segment by segment |
| WebSockets | The shelf_web_socket package | Built in |
| Path parameters | Strings, passed as extra handler arguments | Read from the request by symbol, with typed accessors |
| Static files | The shelf_static package | StaticHandler, with cache busting and ETags |
| Hijacking | A thrown HijackException | A returned Hijack or WebSocketUpgrade result |
| Request state | A Map<String, Object> context | Typed context properties |
| Body | Content and encoding set separately | One Body that carries content, type and encoding |
The Migration from Shelf page goes through each of these with code.