{"id":28612635,"url":"https://github.com/serverpod/relic","last_synced_at":"2025-06-12T00:30:36.182Z","repository":{"id":267942095,"uuid":"902383909","full_name":"serverpod/relic","owner":"serverpod","description":"Strictly typed HTTP server with incredible performance.","archived":false,"fork":false,"pushed_at":"2025-06-09T06:28:20.000Z","size":835,"stargazers_count":38,"open_issues_count":19,"forks_count":5,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-06-09T06:28:34.807Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/serverpod.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-12-12T13:11:56.000Z","updated_at":"2025-06-04T14:04:59.000Z","dependencies_parsed_at":"2024-12-13T12:18:36.349Z","dependency_job_id":"99f816eb-c552-4edd-83ff-e9eaa8a8838f","html_url":"https://github.com/serverpod/relic","commit_stats":null,"previous_names":["serverpod/relic"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/serverpod/relic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverpod%2Frelic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverpod%2Frelic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverpod%2Frelic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverpod%2Frelic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/serverpod","download_url":"https://codeload.github.com/serverpod/relic/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverpod%2Frelic/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259369540,"owners_count":22847091,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-12T00:30:35.258Z","updated_at":"2025-06-12T00:30:36.147Z","avatar_url":"https://github.com/serverpod.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Relic web server banner](https://github.com/serverpod/relic/raw/main/misc/images/github-banner.jpg)\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://github.com/serverpod/relic/actions\"\u003e\u003cimg src=\"https://github.com/serverpod/relic/workflows/Relic CI/badge.svg\" alt=\"build\"\u003e\u003c/a\u003e\n\u003ca href=\"https://codecov.io/gh/serverpod/relic\"\u003e\u003cimg src=\"https://codecov.io/gh/serverpod/relic/branch/main/graph/badge.svg\" alt=\"codecov\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/serverpod/relic\"\u003e\u003cimg src=\"https://img.shields.io/github/stars/serverpod/relic.svg?style=flat\u0026logo=github\u0026colorB=deeppink\u0026label=stars\" alt=\"Stars on Github\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opensource.org/license/bsd-3-clause\"\u003e\u003cimg src=\"https://img.shields.io/badge/license/bsd-3-clause.svg\" alt=\"License: BSD-3-Clause\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n# Relic web server\n\nRelic is a web server based on Shelf that supports middleware. It's currently available as a tech preview to gather feedback before we release a stable version. __Beware that the API is still subject to change.__ The best way to provide your feedback is through issues on GitHub here:\n[https://github.com/serverpod/relic/issues](https://github.com/serverpod/relic/issues)\n\nThis package was born out of the needs of [Serverpod](https://serverpod.dev), as we wanted a more modern and performant base for our web server. Relic is based on [Shelf](https://pub.dev/packages/shelf), but we have made several improvements:\n\n- We removed all `List\u003cint\u003e` in favor of `Uint8List`.\n- We made everything type-safe (no more dynamic).\n- Encoding types have been moved to the `Body` of a `Request`/`Response` to simplify the logic when syncing up the headers and to have a single source of truth.\n- We've added parsers and validation for all commonly used HTTP headers. E.g., times are represented by `DateTime`, cookies have their own class with validation of formatting, etc.\n- Routing has been implemented using a [trie](https://en.wikipedia.org/wiki/Trie) data-structure (`PathTrie`) for efficient route matching and parameter extraction.\n- Extended test coverage.\n- There are lots of smaller fixes here and there.\n\nAlthough the structure is very similar to Shelf, this is no longer backward compatible. We like to think that a transition would be pretty straightforward, and we are planning put a guide in place.\n\nBefore a stable release, we're also planning on adding the following features:\n- We want to more tightly integrate a http server (i.e., start with `HttpServer` from `dart:io` as a base) with Relic so that everything uses the same types. This will also improve performance as fewer conversions will be needed.\n- We're planning to add an improved testing framework.\n- Performance testing and optimizations.\n\nIn addition, we're planning to include Relic in [Serverpod](https://serverpod.dev), both for powering our RPC and as a base for our web server. This would add support for middleware in our RPC. In our web server integration, we have support for HTML templates and routing. You also get access to the rest of the Serverpod ecosystem in terms of serialization, caching, pub-sub, and database integrations.\n\n## Example\n\nSee `relic/example/example.dart` for a runnable example. The following code demonstrates basic routing and request handling:\n\n```dart\nimport 'dart:io';\n\nimport 'package:relic/relic.dart';\nimport 'package:relic/src/adapter/context.dart';\nimport 'package:relic/src/middleware/routing_middleware.dart';\n\n/// A simple 'Hello World' server\nFuture\u003cvoid\u003e main() async {\n  // Setup router\n  final router = Router\u003cHandler\u003e()..get('/user/:name/age/:age', hello);\n\n  // Setup a handler.\n  //\n  // A [Handler] is function consuming and producing [RequestContext]s,\n  // but if you are mostly concerned with converting [Request]s to [Response]s\n  // (known as a [Responder] in relic parlor) you can use [respondWith] to\n  // wrap a [Responder] into a [Handler]\n  final handler = const Pipeline()\n      .addMiddleware(logRequests())\n      .addMiddleware(routeWith(router))\n      .addHandler(respondWith((final _) =\u003e Response.notFound(\n          body: Body.fromString(\"Sorry, that doesn't compute\"))));\n\n  // Start the server with the handler\n  await serve(handler, InternetAddress.anyIPv4, 8080);\n\n  print('Serving at http://localhost:8080');\n  // Check the _example_ directory for other examples.\n}\n\nResponseContext hello(final RequestContext ctx) {\n  final name = ctx.pathParameters[#name];\n  final age = int.parse(ctx.pathParameters[#age]!);\n\n  return (ctx as RespondableContext).withResponse(Response.ok(\n      body: Body.fromString('Hello $name! To think you are $age years old.')));\n}\n```\n\n## Handlers and Middleware\n\nA [Handler][] is any function that handles a [Request][] and returns a\n[Response][]. It can either handle the request itself–for example, a static file\nserver that looks up the requested URI on the filesystem–or it can do some\nprocessing and forward it to another handler–for example, a logger that prints\ninformation about requests and responses to the command line.\n\n[handler]: https://pub.dev/documentation/relic/latest/relic/Handler.html\n[request]: https://pub.dev/documentation/relic/latest/relic/Request-class.html\n[response]: https://pub.dev/documentation/relic/latest/relic/Response-class.html\n\nThe latter kind of handler is called \"[middleware][]\", since it sits in the\nmiddle of the server stack. Middleware can be thought of as a function that\ntakes a handler and wraps it in another handler to provide additional\nfunctionality. A Shelf application is usually composed of many layers of\nmiddleware with one or more handlers at the very center; the [Pipeline][] class\nmakes this sort of application easy to construct.\n\n[middleware]: https://pub.dev/documentation/relic/latest/relic/Middleware.html\n[pipeline]: https://pub.dev/documentation/relic/latest/relic/Pipeline-class.html\n\nSome middleware can also take multiple handlers and call one or more of them for\neach request. For example, a routing middleware might choose which handler to\ncall based on the request's URI or HTTP method, while a cascading middleware\nmight call each one in sequence until one returns a successful response.\n\nMiddleware that routes requests between handlers should be sure to update each\nrequest's [`handlerPath`][handlerpath] and [`url`][url]. This allows inner\nhandlers to know where they are in the application so they can do their own\nrouting correctly. This can be easily accomplished using\n[`Request.copyWith()`][change]:\n\n[handlerpath]:\n  https://pub.dev/documentation/relic/latest/relic/Request/handlerPath.html\n[url]: https://pub.dev/documentation/relic/latest/relic/Request/url.html\n[change]: https://pub.dev/documentation/relic/latest/relic/Request/copyWith.html\n\n```dart\n// In an imaginary routing middleware...\nvar component = request.url.pathSegments.first;\nvar handler = _handlers[component];\nif (handler == null) return Response.notFound();\n\n// Create a new request just like this one but with whatever URL comes after\n// [component] instead.\nreturn handler(request.copyWith(path: component));\n```\n\n## Adapters\n\nAn adapter is any code that creates [Request][] objects, passes them to a\nhandler, and deals with the resulting [Response][]. For the most part, adapters\nforward requests from and responses to an underlying HTTP server;\n[serve][] is this sort of adapter. An adapter might also synthesize\nHTTP requests within the browser using `window.location` and `window.history`,\nor it might pipe requests directly from an HTTP client to a Shelf handler.\n\n[serve]: https://pub.dev/documentation/relic/latest/relic/serve.html\n\n### API Requirements\n\nAn adapter must handle all errors from the handler, including the handler\nreturning a `null` response. It should print each error to the console if\npossible, then act as though the handler returned a 500 response. The adapter\nmay include body data for the 500 response, but this body data must not include\ninformation about the error that occurred. This ensures that unexpected errors\ndon't result in exposing internal information in production by default; if the\nuser wants to return detailed error descriptions, they should explicitly include\nmiddleware to do so.\n\nAn adapter should ensure that asynchronous errors thrown by the handler don't\ncause the application to crash, even if they aren't reported by the future\nchain. Specifically, these errors shouldn't be passed to the root zone's error\nhandler; however, if the adapter is run within another error zone, it should\nallow these errors to be passed to that zone. The following function can be used\nto capture only errors that would otherwise be top-leveled:\n\n```dart\n/// Run [callback] and capture any errors that would otherwise be top-leveled.\n///\n/// If `this` is called in a non-root error zone, it will just run [callback]\n/// and return the result. Otherwise, it will capture any errors using\n/// [runZoned] and pass them to [onError].\nvoid catchTopLevelErrors(\n  void Function() callback,\n  void Function(Object error, StackTrace stackTrace) onError,\n) {\n  if (Zone.current.inSameErrorZone(Zone.root)) {\n    return runZonedGuarded(callback, onError);\n  } else {\n    return callback();\n  }\n}\n```\n\nAn adapter that knows its own URL should provide an implementation of the\n[`RelicServer`][server] interface.\n\n[server]: https://pub.dev/documentation/relic/latest/relic/RelicServer-class.html\n\n### Request Requirements\n\nIf the underlying request uses a chunked transfer coding, the adapter must\ndecode the body before passing it to [Request][] and should remove the\n`Transfer-Encoding` header. This ensures that message bodies are chunked if and\nonly if the headers declare that they are.\n\n### Response Requirements\n\nAn adapter must not add or modify any [entity headers][] for a response.\n\n[entity headers]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.1\n\nIf _none_ of the following conditions are true, the adapter must apply [chunked\ntransfer coding][] to a response's body and set its Transfer-Encoding header to\n`chunked`:\n\n- The status code is less than 200, or equal to 204 or 304.\n- A Content-Length header is provided.\n- The Content-Type header indicates the MIME type `multipart/byteranges`.\n- The Transfer-Encoding header is set to anything other than `identity`.\n\n[chunked transfer coding]:\n  https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1\n\nAdapters may find the [`addChunkedEncoding()`][addchunkedencoding] middleware\nuseful for implementing this behavior, if the underlying server doesn't\nimplement it manually.\n\n[addchunkedencoding]:\n  https://pub.dev/documentation/shelf/latest/shelf/addChunkedEncoding.html\n\nWhen responding to a HEAD request, the adapter must not emit an entity body.\nOtherwise, it shouldn't modify the entity body in any way.\n\nAn adapter should include information about itself in the Server header of the\nresponse by default. If the handler returns a response with the Server header\nset, that must take precedence over the adapter's default header.\n\nAn adapter should include the Date header with the time the handler returns a\nresponse. If the handler returns a response with the Date header set, that must\ntake precedence.\n\n## Inspiration\n\n- [Shelf](https://pub.dev/packages/shelf) for Dart.\n- [Connect](https://github.com/senchalabs/connect) for NodeJS.\n- [Rack](https://github.com/rack/rack) for Ruby.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserverpod%2Frelic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fserverpod%2Frelic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserverpod%2Frelic/lists"}