{"id":13552415,"url":"https://github.com/rknell/alfred","last_synced_at":"2025-05-15T05:05:36.599Z","repository":{"id":37021648,"uuid":"352642478","full_name":"rknell/alfred","owner":"rknell","description":"A performant, expressjs like server framework with a few gadgets that make life even easier.","archived":false,"fork":false,"pushed_at":"2025-01-17T07:53:52.000Z","size":677,"stargazers_count":535,"open_issues_count":17,"forks_count":33,"subscribers_count":18,"default_branch":"master","last_synced_at":"2025-04-10T10:09:55.460Z","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":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rknell.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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}},"created_at":"2021-03-29T12:53:37.000Z","updated_at":"2025-04-09T14:41:03.000Z","dependencies_parsed_at":"2024-01-03T01:20:30.611Z","dependency_job_id":"56ede44f-32f9-4e4e-aa17-840ec96b05d0","html_url":"https://github.com/rknell/alfred","commit_stats":{"total_commits":178,"total_committers":14,"mean_commits":"12.714285714285714","dds":0.4213483146067416,"last_synced_commit":"1601ad00bc999a11082772f7b09f737541bda6db"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rknell%2Falfred","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rknell%2Falfred/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rknell%2Falfred/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rknell%2Falfred/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rknell","download_url":"https://codeload.github.com/rknell/alfred/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254276446,"owners_count":22043866,"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":"2024-08-01T12:02:03.635Z","updated_at":"2025-05-15T05:05:36.578Z","avatar_url":"https://github.com/rknell.png","language":"Dart","readme":"# Alfred\n\nA performant, expressjs like web server / rest api framework thats easy to use and has all the bits in one place.\n[![Build Status](https://github.com/rknell/alfred/workflows/Dart/badge.svg)](https://github.com/rknell/alfred/actions)\n\nQuickstart:\n\n```dart\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n\n  app.get('/example', (req, res) =\u003e 'Hello world');\n\n  await app.listen();\n}\n```\n\nThere is also a 6 part video series that walks you through creating a web server using Alfred from start to deployment, including databases and authentication. You can find that here: https://www.youtube.com/playlist?list=PLkEq83S97rEWsgFEzwBW2pxB7pRYb9wAB\n\n# Index\n- [Core principles](#core-principles)\n- [Usage overview](#usage-overview)\n    - [Quick start guide](#quick-start-guide)\n- [Routing \u0026 incoming requests](#routing--incoming-requests)\n    - [Route params](#route-params)\n    - [Query string variables](#query-string-variables)\n    - [Body parsing](#body-parsing)\n    - [File uploads](#file-uploads)\n- [Middleware](#middleware)\n    - [No 'next'?](#what-no-next-how-do-i-even)\n    - [CORS](#cors)\n- [Responses](#responses)\n    - [Custom type handlers](#custom-type-handlers)\n    - [Static Files](#static-files)\n    - [File downloads](#file-downloads)\n- [Error handling](#error-handling)\n    - [404 Handling](#404-handling)\n- [Databases](#but-what-about-mongo-or-postgres-or-databse-x)\n- [What I want to do isn't listed](#what-i-want-to-do-isnt-listed)\n- [Websockets](#websockets)\n- [Logging](#logging)\n    - [Print routes](#print-routes)\n- [Multi threading \u0026 isolates](#multi-threading-and-isolates)\n- [Contributions](#contributions)\n\n## Core principles\n- A minimum of dependencies,\n- A minimum of code and sticking close to dart core libraries - easy to maintain!\n- Ease of use\n- Predictable, well established semantics\n- 90%+ of everything you need all ready to go\n\n[Read about the background behind the project or why its different to shelf](documentation/background.md)\n\n## Usage overview\n\nIf you have ever used expressjs before you should be right at home:\n\n```dart\nimport 'dart:io';\n\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n\n  app.get('/text', (req, res) =\u003e 'Text response');\n\n  app.get('/json', (req, res) =\u003e {'json_response': true});\n\n  app.get('/jsonExpressStyle', (req, res) {\n    res.json({'type': 'traditional_json_response'});\n  });\n\n  app.get('/file', (req, res) =\u003e File('test/files/image.jpg'));\n\n  app.get('/html', (req, res) {\n    res.headers.contentType = ContentType.html;\n    return '\u003chtml\u003e\u003cbody\u003e\u003ch1\u003eTest HTML\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e';\n  });\n\n  await app.listen(6565); //Listening on port 6565\n}\n```\n\nIt should do pretty much what you expect. Handling bodies though do need an \"await\":\n\n```dart\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n\n  app.post('/post-route', (req, res) async {\n    final body = await req.body; //JSON body\n    body != null; //true\n  });\n\n  await app.listen(); //Listening on port 3000\n}\n```\n\nInternally dart provides a body parser, so no extra dependencies there.\n\nThe big difference you will see is the option to not call `res.send` or `res.json` etc - although you still can.\nEach route accepts a Future as response. Currently you can pass back the following and it will be sent appropriately:\n\n| Return Dart Type | Returning REST type |\n| ----------------- | ------------------ |\n| `List\u003cdynamic\u003e` | JSON |\n| `Map\u003cString, Object?\u003e` | JSON |\n| Serializable object (Object.toJSON or Object.toJson) * see note | JSON |\n| `String` | Plain Text |\n| `Stream\u003cList\u003cint\u003e\u003e` | Binary |\n| `List\u003cint\u003e` | Binary |\n| `File` |  Binary, with mime type inferred by extension |\n| `Directory` | Serves static files |\n\n\\* If your object has a \"toJSON\" or \"toJson\" function, alfred will run it, and then return the result\n\nIf you want to return HTML, just set the content type to HTML like this:\n\n```dart\nimport 'dart:io';\n\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n\n  app.get('/html', (req, res) {\n    res.headers.contentType = ContentType.html;\n    return '\u003chtml\u003e\u003cbody\u003e\u003ch1\u003eTitle!\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e';\n  });\n\n  await app.listen(); //Listening on port 3000\n}\n```\n\nIf you want to return a different type and have it handled automatically, you can extend Alfred with\n[custom type handlers](#custom-type-handlers).\n\n### Quick start guide\n\nIf its all a bit overwhelming @iapicca put together a quick start guide which goes into a little \nmore detail: https://medium.com/@iapicca/alfred-an-express-like-server-framework-written-in-dart-1661e8963db9\n\n## Routing \u0026 incoming requests\n\nRouting follows a similar pattern to the more basic ExpressJS routes. While there is some regex\nmatching, mostly just stick with the route name and param syntax from Express:\n\n\"/path/to/:id/property\" etc\n\nThe Express syntax has been extended to support parameter patterns and types. To enforce parameter\nvalidation, a regular expression or a type specifier should be provided after the parameter name, using\nanother `:` as a separator:\n\n* `/path/to/:id:\\d+/property` will ensure \"id\" is a string consisting of decimal digits\n* `/path/to/:id:[0-9a-f]+/property` will ensure \"id\" is a string consisting of hexadecimal digits\n* `/path/to/:word:[a-z]+/property` will ensure \"word\" is a string consisting of letters only\n* `/path/to/:id:uuid/property` will ensure \"id\" is a string representing an UUID\n\nAvailable type specifiers are:\n\n* `int`: a decimal integer\n* `uint`: a positive decimal integer\n* `double`: a double (decimal form); note that scientific notation is not supported\n* `date`: a UTC date in the form of \"year/month/day\"; note how this type \"absorbs\" multiple segments of the URI\n* `timestamp`: a UTC date expressed in number of milliseconds since Epoch\n* `uuid`: a string resembling a UUID (hexadecimal number formatted as `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`); note that no effort is made to ensure this is a valid UUID\n\n| Type Specifier | Regular Expression | Dart type |\n| -------------- | ------------------ | --------- |\n| `int` | `-?\\d+` | `int` |\n| `uint` | `\\d+` | `int` |\n| `double` | `-?\\d+(?:\\.\\d+)` | `double` |\n| `date` | `-?\\d{1,6}/(?:0[1-9]\\|1[012])/(?:0[1-9]\\|[12][0-9]\\|3[01])` | `DateTime` |\n| `timestamp` | `-?\\d+` | `DateTime` |\n| `uuid` |  `[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}` | `String` |\n\nSo for example:\n\n```dart\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n  app.all('/example/:id/:name', (req, res) {\n    req.params['id'] != null;\n    req.params['name'] != null;\n  });\n  app.all('/typed-example/:id:int/:name', (req, res) {\n    req.params['id'] != null;\n    req.params['id'] is int;\n    req.params['name'] != null;\n  });\n  app.get('/blog/:date:date/:id:int', (req, res) {\n    req.params['date'] != null;\n    req.params['date'] is DateTime;\n    req.params['id'] != null;\n    req.params['id'] is int;\n  });\n  await app.listen();\n}\n```\n\nYou can also use a wildcard for a route, and provided another route hasn't already resolved the\nresponse it will be hit. So for example if you want to authenticate a whole section of an api youc \ncan do this:\n\n```dart\nimport 'dart:async';\n\nimport 'package:alfred/alfred.dart';\n\nFutureOr _authenticationMiddleware(HttpRequest req, HttpResponse res) async {\n  res.statusCode = 401;\n  await res.close();\n}\n\nvoid main() async {\n  final app = Alfred();\n\n  app.all('/resource*', (req, res) =\u003e _authenticationMiddleware);\n\n  app.get('/resource', (req, res) {}); //Will not be hit\n  app.post('/resource', (req, res) {}); //Will not be hit\n  app.post('/resource/1', (req, res) {}); //Will not be hit\n\n  await app.listen();\n}\n```\n\n### Route params\n\nYou can access any params for routes from the `req.params` object as below:\n\n```dart\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n  app.all('/example/:id/:name', (req, res) {\n    req.params['id'] != null;\n    req.params['name'] != null;\n  });\n  app.all('/typed-example/:id:int/:name', (req, res) {\n    req.params['id'] != null;\n    req.params['id'] is int;\n    req.params['name'] != null;\n  });\n  app.get('/blog/:date:date/:id:int', (req, res) {\n    req.params['date'] != null;\n    req.params['date'] is DateTime;\n    req.params['id'] != null;\n    req.params['id'] is int;\n  });\n  await app.listen();\n}\n```\n\n### Query string variables\n\nQuerystring variables are exposed `req.uri.queryParameters` object in the request as below:\n\n```dart\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n\n  app.post('/route', (req, res) async {\n    /// Handle /route?qsvar=true\n    final result = req.uri.queryParameters['qsvar'];\n    result == 'true'; //true\n  });\n\n  await app.listen(); //Listening on port 3000\n}\n```\n\n### Body parsing\n\nTo access the body, simply call `await req.body`.\n\nAlfred will interpret the body type from the content type headers and parse it appropriately. It handles url encoded, multipart \u0026 json bodies out of the box.\n\n```dart\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n\n  app.post('/post-route', (req, res) async {\n    final body = await req.body; //JSON body\n    body != null; //true\n  });\n\n  await app.listen(); //Listening on port 3000\n}\n```\n\n### File uploads\n\nTo upload a file the body parser will handle exposing the data you need. Its actually pretty easy\njust give it a go and set a breakpoint to see what the body parser spits back.\n\nA working example of file uploads is below to get you started:\n\n```dart\nimport 'dart:io';\n\nimport 'package:alfred/alfred.dart';\n\nfinal _uploadDirectory = Directory('uploadedFiles');\n\nFuture\u003cvoid\u003e main() async {\n  final app = Alfred();\n\n  app.get('/files/*', (req, res) =\u003e _uploadDirectory);\n\n  /// Example of handling a multipart/form-data file upload\n  app.post('/upload', (req, res) async {\n    final body = await req.bodyAsJsonMap;\n\n    // Create the upload directory if it doesn't exist\n    if (await _uploadDirectory.exists() == false) {\n      await _uploadDirectory.create();\n    }\n\n    // Get the uploaded file content\n    final uploadedFile = (body['file'] as HttpBodyFileUpload);\n    var fileBytes = (uploadedFile.content as List\u003cint\u003e);\n\n    // Create the local file name and save the file\n    await File('${_uploadDirectory.absolute}/${uploadedFile.filename}')\n        .writeAsBytes(fileBytes);\n\n    /// Return the path to the user\n    ///\n    /// The path is served from the /files route above\n    return ({\n      'path': 'https://${req.headers.host ?? ''}/files/${uploadedFile.filename}'\n    });\n  });\n\n  await app.listen();\n}\n```\n\n## Middleware\n\nYou can specify a middleware for all routes by using wildcards:\n\n```dart\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n  app.all('*', (req, res) {\n    // Perform action\n    res.headers.set('x-custom-header', \"Alfred isn't bad\");\n\n    /// No need to call next as we don't send a response.\n    /// Alfred will find the next matching route\n  });\n\n  app.get('/otherFunction', (req, res) {\n    //Action performed next\n    return {'message': 'complete'};\n  });\n\n  await app.listen();\n}\n```\n\nMiddleware declared this way will be executed in the order its added to the app.\n\nYou can also add middleware to a route, this is great to enforce authentication etc on an endpoint:\n\n```dart\nimport 'dart:async';\n\nimport 'package:alfred/alfred.dart';\n\nFutureOr exampleMiddleware(HttpRequest req, HttpResponse res) {\n  // Do work\n  if (req.headers.value('Authorization') != 'apikey') {\n    throw AlfredException(401, {'message': 'authentication failed'});\n  }\n}\n\nvoid main() async {\n  final app = Alfred();\n  app.all('/example/:id/:name', (req, res) {}, middleware: [exampleMiddleware]);\n\n  await app.listen(); //Listening on port 3000\n}\n```\n\n### What? No 'next'? how do I even?  \nOK, so the rules are simple. If a middleware resolves a http request, no future middleware gets executed.\n\nSo if you return an object from the middleware, you are preventing future middleware from executing.\n\nIf you return null it will yield to the next middleware or route.\n\n** returning null is the equivalent of 'next' **\n\n### CORS\n\nThere is a cors middleware supplied for your convenience. Its also a great example of how to write a middleware for Alfred\n\n```dart\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n\n  // Warning: defaults to origin \"*\"\n  app.all('*', cors(origin: 'myorigin.com'));\n\n  await app.listen();\n}\n```\n\n## Responses\n\nAlfred is super easy, generally you just return JSON, a file, a String or a Binary stream and you are all good.\n\nThe big difference from express is you will see is the option to not call `res.send` or `res.json` etc - although you still can.\nEach route accepts a Future as response. Currently you can pass back the following and it will be sent appropriately:\n\n- `List\u003cdynamic\u003e` - JSON\n- `Map\u003cString, Object?\u003e` - JSON\n- `String` - Plain text\n- `Stream\u003cList\u003cint\u003e\u003e` - Binary\n- `List\u003cint\u003e` - Binary\n- `File` - Binary, with mime type inferred by extension\n- `Directory` - Serves static files\n\nEach type listed above has a `Type Handler` build in. [You can create your own custom type handlers](#custom-type-handlers)\n\n### Custom type handlers\nAlfred has a pretty cool mechanism thanks to Dart's type system to automatically resolve a response\nbased on the returned type from a route. These are called `Type Handlers`.\n\nIf you want to create custom type handlers, just add them to the type handler\narray in the app object. This is a bit advanced, and I expect it would be more\nfor devs wanting to extend Alfred:\n\n```dart\nimport 'package:alfred/alfred.dart';\n\nclass Chicken {\n  String get response =\u003e 'I am a chicken';\n}\n\nvoid main() {\n  final app = Alfred();\n\n  app.typeHandlers.add(TypeHandler\u003cChicken\u003e((req, res, Chicken val) async {\n    res.write(val.response);\n    await res.close();\n  }));\n\n  /// The app will now return the Chicken.response if you return one from a route\n\n  app.get('/kfc', (req, res) =\u003e Chicken()); //I am a chicken;\n\n  app.listen(); //Listening on 3000\n}\n```\n\n### Static Files, uploads and deleting\n\nThis one is super easy - just pass in a public path and a dart Directory object and Alfred does\nthe rest.\n\n```dart\nimport 'dart:io';\n\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n\n  /// Note the wildcard (*) this is very important!!\n  app.get('/public/*', (req, res) =\u003e Directory('test/files'));\n\n  await app.listen();\n}\n```\n\nYou can also pass in a directory and a POST or PUT command and upload files to a local directory if \nyou are using multipart/form encoding. Simply supply the field as `file`:\n\n```dart\nimport 'dart:io';\n\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n\n  app.post('/public', (req, res) =\u003e Directory('test/files'));\n\n  await app.listen();\n}\n```\n\nIf you want to delete a file?\n\n```dart\nimport 'dart:async';\nimport 'dart:io';\n\nimport 'package:alfred/alfred.dart';\n\nFutureOr isAuthenticatedMiddleware(HttpRequest req, HttpResponse res) {\n  if (req.headers.value('Authorization') != 'MYAPIKEY') {\n    throw AlfredException(\n        401, {'error': 'You are not authorized to perform this operation'});\n  }\n}\n\nvoid main() async {\n  final app = Alfred();\n\n  /// Note the wildcard (*) this is very important!!\n  ///\n  /// You almost certainly want to protect this endpoint with some middleware\n  /// to authenticate a user.\n  app.delete('/public/*', (req, res) =\u003e Directory('test/files'),\n      middleware: [isAuthenticatedMiddleware]);\n\n  await app.listen();\n}\n```\n\nSecurity? Build in a middleware function to authenticate a user etc. \n\n### File downloads\n\nAs mentioned above - if you want to return a file, simply return it from the route callback.\nHowever the browser will probably try to render it in browser, and not download it.\n\nYou can just set the right headers, but there is a handy little helper that will do it all for you.\n\nThe file handler fully supports resumable downloads (RFC9110 compliant), including:\n- Range requests for partial content\n- Multiple range requests with multipart responses\n- Conditional requests using ETags and Last-Modified dates\n- Proper handling of If-Range header\n- Support for suffix ranges (e.g., last 500 bytes)\n\nSee `res.setDownload` below.\n\n```dart\nimport 'dart:io';\n\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n\n  app.get('/image/download', (req, res) {\n    res.setDownload(filename: 'image.jpg');\n    return File('test/files/image.jpg');\n  });\n\n  await app.listen(); //Listening on port 3000\n}\n```\n\n## Error handling\n\nYou can either set the status code on the response object yourself and send the data manually, or\nyou can do this from any route:\n\napp.get(\"/\",(req, res) =\u003e throw AlfredException(400, {\"message\": \"invalid request\"}));\n\nIf any of the routes bubble an unhandled error, it will catch it and throw a 500 error.\n\nIf you want to handle the logic when a 500 error is thrown, you can add a custom handler when you\ninstantiate the app. For example:\n\n```dart\nimport 'dart:async';\n\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred(onInternalError: errorHandler);\n  await app.listen();\n  app.get('/throwserror', (req, res) =\u003e throw Exception('generic exception'));\n}\n\nFutureOr errorHandler(HttpRequest req, HttpResponse res) {\n  res.statusCode = 500;\n  return {'message': 'error not handled'};\n}\n```\n\n### 404 Handling\n\n404 Handling works the same as 500 error handling (or uncaught error handling). There is a default\nbehaviour, but if you want to override it, simply handle it in the app declaration.\n\n```dart\nimport 'dart:async';\n\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred(onNotFound: missingHandler);\n  await app.listen();\n}\n\nFutureOr missingHandler(HttpRequest req, HttpResponse res) {\n  res.statusCode = 404;\n  return {'message': 'not found'};\n}\n```\n\n## But what about Mongo or Postgres or \u003cDatabase x\u003e?\n\nThe other two systems that inspired this project to be kicked off - Aqueduct and Angel - both had\nsome sort of database integration built in.\n\n**You do not need this.**\n\nAccess the dart drivers for the database system you want directly, they all use them behind the scenes:\n\n- Mongo - https://pub.dev/packages/mongo_dart\n- Postgres - https://pub.dev/packages/postgres\n- SQLLite -  https://pub.dev/packages/sqlite3\n\nYou will be fine. I have used them this way and they work.\n\nI have rolled my own classes that act as a sort of ORM, especially around Mongo. Its suprisingly effective\nand doesn't rely on much code.\n\n## What I want to do isn't listed\n\nWhile there are bunch of helpers built in - you have direct access to the low level apis available\nfrom the dart:io package. All helpers are just extension methods to:\n\n- HttpRequest: https://api.dart.dev/stable/2.10.5/dart-io/HttpRequest-class.html\n- HttpResponse: https://api.dart.dev/stable/2.10.5/dart-io/HttpResponse-class.html\n\nSo you can compose and write any content you can imagine there. If there is something you want to do\nthat isn't expressly listed by the library, you will be able to do it with a minimum of research into\nunderlying libraries. A core part of the architecture is to not build you into a wall.\n\n## Websockets\n\nAlfred supports websockets too!\n\nThere is a quick chat client in the examples\n\n```dart\nimport 'dart:async';\nimport 'dart:io';\n\nimport 'package:alfred/alfred.dart';\nimport 'package:alfred/src/type_handlers/websocket_type_handler.dart';\n\nFuture\u003cvoid\u003e main() async {\n  final app = Alfred();\n\n  // Path to this Dart file\n  var dir = File(Platform.script.path).parent.path;\n\n  // Deliver web client for chat\n  app.get('/', (req, res) =\u003e File('$dir/chat-client.html'));\n\n  // Track connected clients\n  var users = \u003cWebSocket\u003e[];\n\n  // WebSocket chat relay implementation\n  app.get('/ws', (req, res) {\n    return WebSocketSession(\n      onOpen: (ws) {\n        users.add(ws);\n        users\n            .where((user) =\u003e user != ws)\n            .forEach((user) =\u003e user.send('A new user joined the chat.'));\n      },\n      onClose: (ws) {\n        users.remove(ws);\n        for (var user in users) {\n          user.send('A user has left.');\n        }\n      },\n      onMessage: (ws, dynamic data) async {\n        for (var user in users) {\n          user.send(data);\n        }\n      },\n    );\n  });\n\n  final server = await app.listen();\n\n  print('Listening on ${server.port}');\n}\n```\n\n## Logging\n\nFor more details on logging [click here](documentation/logging.md).\n\n### Print routes\n\nWant to quickly print out the registered routes? (recommended when you fire up the server) \ncall Alfred.printRoutes ie:\n\n```dart\nimport 'package:alfred/alfred.dart';\n\nvoid main() async {\n  final app = Alfred();\n\n  app.get('/html', (req, res) {});\n\n  app.printRoutes(); //Will print the routes to the console\n\n  await app.listen();\n}\n```\n\n## Multi threading and isolates\n\nYou can use the app in multithreaded mode. When spawning this way, requests are evenly distributed\namongst the various isolates. Alfred is not particularly prescriptive about how you manage the isolates\njust that \"it works\" when you fire up multiples.\n\n```dart\nimport 'dart:isolate';\n\nimport 'package:alfred/alfred.dart';\n\nFuture\u003cvoid\u003e main() async {\n  // Fire up 5 isolates\n  for (var i = 0; i \u003c 5; i++) {\n    unawaited(Isolate.spawn(startInstance, ''));\n  }\n  // Start listening on this isolate also\n  startInstance(null);\n}\n\n/// The start function needs to be top level or static. You probably want to\n/// run your entire app in an isolate so you don't run into trouble sharing DB\n/// connections etc. However you can engineer this however you like.\n///\nvoid startInstance(dynamic message) async {\n  final app = Alfred();\n\n  app.all('/example', (req, res) =\u003e 'Hello world');\n\n  await app.listen();\n}\n\n/// Simple function to prevent linting errors, can be ignored\nvoid unawaited(Future future) {}\n```\n\n# Deployment\n\nThere are many ways to skin this cat, you can upload the source code to a VPS yourself, build a binary locally and upload it to a server somewhere, but a fairly elegant way to accomplish a production level deployment is to containerize an AOT build of the server and run it on a PAAS.\n\nLucky there is a tutorial for that!\nhttps://ryan-knell.medium.com/build-and-deploy-a-dart-server-using-alfred-docker-and-google-cloud-run-from-start-to-finish-d5066e3ab3c6\n\n# Contributions\n\nPRs are welcome and encouraged! This is a community project and as long as the PR keeps within the key principles listed it will probably be accepted. If you have an improvement you would like to to add but are not sure just reach out in the issues section.\n\nNB. The readme is generated from the file in tool/templates/README.md which pulls in the actual source code from the example dart files - this way we can be sure its no pseudocode! If you need to change anything in the documentation please edit it there.\n\nBefore you submit your code, you can run the `ci_checks.sh` shell script that will do many of the tests the CI suite will perform.","funding_links":[],"categories":["Dart","服务端框架","Server Frameworks"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frknell%2Falfred","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frknell%2Falfred","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frknell%2Falfred/lists"}