{"id":18234346,"url":"https://github.com/viniciusamelio/scouter","last_synced_at":"2025-07-08T07:33:55.489Z","repository":{"id":50394754,"uuid":"518857883","full_name":"viniciusamelio/scouter","owner":"viniciusamelio","description":"Scouter is a package which was made following the goal to provide a NestJS-like experience to Dart Developers that want to develop Rest APIS","archived":false,"fork":false,"pushed_at":"2023-05-21T21:32:24.000Z","size":159,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-11T21:40:48.093Z","etag":null,"topics":["api","backend","dart","rest"],"latest_commit_sha":null,"homepage":"","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/viniciusamelio.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}},"created_at":"2022-07-28T13:29:00.000Z","updated_at":"2024-06-22T08:35:57.000Z","dependencies_parsed_at":"2023-01-31T19:15:42.222Z","dependency_job_id":null,"html_url":"https://github.com/viniciusamelio/scouter","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viniciusamelio%2Fscouter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viniciusamelio%2Fscouter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viniciusamelio%2Fscouter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viniciusamelio%2Fscouter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/viniciusamelio","download_url":"https://codeload.github.com/viniciusamelio/scouter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230788229,"owners_count":18280300,"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":["api","backend","dart","rest"],"created_at":"2024-11-04T21:03:26.118Z","updated_at":"2024-12-22T02:42:39.691Z","avatar_url":"https://github.com/viniciusamelio.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ctable\u003e\n  \u003ctd width=\"175px\"\u003e\n    \u003cimg src=\"https://github.com/viniciusamelio/scouter/blob/master/assets/logo.png?raw=true\" height=\"150px\"/\u003e \u003cbr\u003e\n  \u003c/td\u003e\n  \u003ctd\u003e\n    Scouter is a package which was made following the goal to provide a NestJS-like experience to Dart Developers that want to develop Rest APIS\n  \u003c/td\u003e\n\u003c/table\u003e\n\n\n## Features\n\n- Routing system through annotations\n- Modules\n- Middlewares\n- DI\n\n## Getting started\n\nAll you need to do is to add the package as a dependency, just like:\n```dart\ndart pub add scouter\n```\n\n## Usage\n\nYou can check a more complete example in example/scouter_example.dart\nHowever, here you can find everything you need to start your application\n\n### Initializing your app\nYou just need to call runServer() function inside main and make your setup, such as:\n\n```dart\nvoid main() async{\n    final appModule = AppModule(\n      modules: [\n        TestModule(),\n      ],\n      middlewares: [\n        ExampleMidleware(),\n        LoggerMiddleware(),\n      ],\n      controllers: [\n        FeatureController(),\n        GameController(),\n      ],\n    );\n\n    runServer(\n        appModule,\n        port: 8084,\n    );\n}\n```\n\n### App Module\nYour app entrypoint and also main module, will be the AppModule class, which will receive your other Modules. Such as Middlewares, Controllers and Global Middlewares. \u003cbr\u003e\nGlobal Middlewares are, in a nutshell, middlewares which will be applied to every single route in your project, whatever the controller that will be wrapping it, or even the module itself.\nThe module preffix for endpoints declared in an app module's controller is \"/\". You can read more about it below.\n\n### Modules\nA module is designed to be a context wrapper of your application's business, it was made thinking about ease when talking about domain division. Each module will have their own controllers and middlewares. Scouter also requires a preffix, that will be responsible to mount the url. Usually, the url will follow this model: http://host/Module.preffix/Controller.name/Route.\nEach module can also have their own middlewares. A module middleware will be applied to every route, inside every controller, declared in this middleware.\n\n```dart\nclass TestModule extends Module {\n  TestModule({super.preffix = \"teste\"});\n\n  @override\n  List\u003cRestController\u003e get controllers =\u003e [\n        GameController(),\n        ProfileController(),\n      ];\n\n  @override\n  List\u003cHttpMiddleware\u003e get middlewares =\u003e [];\n}\n```\n\n### Controllers\nEach controller, just like a module, will have a preffix, but the thing about it is that you dont need to explicitly declare it if you dont want to. That's because Scouter can handle the classname itself to compose the preffix. You just need to ensure that your controller contains the word \"Controller\" on its name, it must be this way.\nYou also must extends RestController class. By default RestController will provide a way to inject and get injectables inside it, it will be clearer below, but for now, it is important to say that DI is made globally, so, you do not need to declare same DI Container twice.\n\n```dart\n@HttpController()\nclass ProfileController extends RestController {\n  @Get(\"/:id\")\n  getById(HttpRequest request) {\n    return ResponseSample();\n  }\n\n  @Get(\"/\")\n  get(HttpRequest request) {\n    return ResponseSample();\n  }\n}\n\nclass ResponseSample {\n  String message = \"Salvo com sucesso!\";\n  int savedId = 70;\n  int httpStatus = 206;\n}\n```\n\nFollowing the sample above, the endpoint declared on the \"getById\" method would be: http://host/somemodule/profile/private.\nIf you want to change the controller preffix, you can just do the following:\n\n```dart\n@HttpController(name: \"pro\")\nclass ProfileController extends RestController {\n...\n```\n\nWhich would result in the given url for same endpoint above:\nhttp://host/somemodule/pro/private\n\n### Routes\nNow, talking about routes, you just need to give the desired http verb annotation to a Controller method. such as:\n\n```dart\n@HttpController()\nclass ProfileController extends RestController {\n  @Get(\"/\")\n  get(HttpRequest request) {\n    ...\n  }\n\n  @Post(\"/\")\n  save(HttpRequest request) {\n    ...\n  }\n}\n```\n\nYou can declare dynamic routes this way:\n\n```dart\n  @Get(\"/:id\")\n  getById(HttpRequest request) {\n    final id = request.params![\"id\"];\n    ...\n  }\n```\n\nYou can also get your params declared in the route path this way:\n```dart\n  @Get(\"/:id\")\n  getById(int id) async {\n    await doSomething(id)\n    ...\n  }\n```\nIt is important to notice you can also get more than one path variable this way, but you have to be aware of two points: \u003cbr\u003e\n1 - Your function name \u003cb\u003emust\u003c/b\u003e be the same as your url variable; \u003cbr\u003e\n2 - You need to get them in the same order they are being declared in your url.\n```dart\n  @Post(\"/:docId/:id\")\n  saveThroughDocument(int docId,int id) async {\n    final document = await repo.getDocById(docId);\n    await doSomething(id)\n    ...\n  }\n```\n\n\n### Body parsing\nYou can automatically parse your body from a custom class you choose, just make sure your variables have the desired name. Such as in Response parsing, soon, it will be added a way to change the key name it will be get from the payload.\n```dart\n  @Post(\"/save/user\")\n  saveUser(@Body() User user){\n    return user;\n  }\n```\n\n***Notice that:***\n- If your custom class to be used as request body contains a list of other non-native types, you **must** extends [MappableInput], this way:\n```dart\n  class ComplexDto extends MappableInput {\n    const ComplexDto({\n      this.name,\n      this.xesquedele,\n      this.data,\n    });\n    final String? name;\n    final int? xesquedele;\n    final List\u003cData\u003e? data;\n\n    @override\n    MappableInput parse(dynamic map) {\n      return ComplexDto(\n        name: map[\"name\"],\n        xesquedele: map[\"xesquedele\"],\n        data: (map[\"data\"] as List)\n            .map((e) =\u003e Data(status: e[\"status\"], id: e[\"id\"]))\n            .toList(),\n      );\n    }\n}\n```\nThat is needed due to a difficult found about infering a type to a list of non-native objects, in the example above: a *Data* list. The list was always being casted as List\u003c dynamic \u003e even if its contents is only some *Data* instances.\n\nYou can set the @Body() the same way you would do with other non-MappableInput class:\n```dart\n  @Put(\"/method\")\n  aMethod(@Body() ComplexDto dto){\n    ...\n  }\n```\n\n\n### Query Params\nJust like **Body**, query parameters can be added to your route by adding an annotation to your method argument.\nIts type will always need to be set as Map, and you always will need to declare the query param as the last argument.\n\n```dart\n  @Get(\"/query/:uuid\")\n  search(String uuid, @QueryParam() Map queryParams) {\n    return {\n      \"name\": queryParams[\"name\"],\n    };\n  }\n```\nIf you prefer, you can algo get it from the default HttpRequest object:\n```dart\n  @Get(\"/query/:uuid\")\n  search(HttpRequest request) {\n    return {\n      \"name\": request.queryParams[\"name\"],\n    };\n  }\n```\n\n\n### Response parsing\nRoutes can have a return type of HttpResponse itself, but it also supports a Map or even a CustomClass. The best part of using a custom class is\nthat you will not need to parse it to a Map, Json or whatever. For example, if you try to return the following object from a route:\n```dart\nclass Profile  {\n  final int id = 1;\n  final String name = \"Anything\";\n  final List\u003cString\u003e tags = [\"cool guy\", \"developer\"];\n  final String url = \"https://pub.dev\";\n}\n```\nScouter will parse it to the following format:\n```json\n{\n  \"id\": 1,\n  \"name\" : \"Anything\",\n  \"url\": \"https://pub.dev\",\n  \"tags\" : [\n    \"cool guy\",\n    \"developer\",\n  ]\n}\n```\nThis method will not parse any method that your class may contain.\u003cbr\u003e\nAlso, if you prefer, you can return a class which contains one of the following methods: toJson() or toMap(). Both should return a Map of String,dynamic.\nJust like this:\n\n```dart\nclass Profile  {\n  final int id = 1;\n  final String name = \"Anything\";\n  final List\u003cString\u003e tags = [\"cool guy\", \"developer\"];\n  final String url = \"https://pub.dev\";\n\n  Map\u003cString,dynamic\u003e toMap() =\u003e {\n    \"name\" : name,\n    \"tags\" : tags,\n    \"url\" : url,\n  };\n}\n```\n\nAs you can see, you can ommit and apply whatever logic you want to compose your map, it is important that in the typing, the subtypes of entry, for the parsed map, to be declared, it may always be String and dynamic. \u003cbr\u003e\n\nBy default, status 200 will be applied to response, but, if you are returning an object that will be parsed, just like above, you can set it through httpStatus property, which needs to be an int. It will be applied to the final response. Such as:\n\n```dart\nclass Profile  {\n  final int id = 1;\n  final String name = \"Anything\";\n  final List\u003cString\u003e tags = [\"cool guy\", \"developer\"];\n  final String url = \"https://pub.dev\";\n  final int httpStatus = 201;\n}\n```\n\nIt is important to notice that: \n- variables from custom objects will be parsed exactly how it is declared, soon, a way to customize the desired key will be introduced, but for now, if you declare something like this:\n  ```dart\n  class Profile  {\n    final int idUser = 5;\n  }\n  ```\n  It will be parsed to this:\n\n  ```json\n  {\n    \"idUser\" : 5\n  }\n  ```\n- if you are using the toMap() or toJson() methods, no processing will be made in your instance, instead, this methods will be called, just the way you implemented it\n- no methods will be parsed, it includes getters.\n\nYou \u003cb\u003e Must \u003c/b\u003e start the route with \"/\", otherwise an exception will be thrown when trying to run the server; \u003cbr\u003e\nYou \u003cb\u003e Must \u003c/b\u003e return something from your route, otherwise it will cause a timeout exception\n\n## Middlewares\nAs said above, you can define middlewares to the whole application through your AppModule. Also, you can define it to a single module, including all of its children. The same as Controllers. \u003cbr\u003e\nA Middleware is nothing but a class which implements the HTTP Middleware interface. It just need to contains a handle(HttpRequest request) method, which MUST return a:\n```dart\nFuture\u003cEither\u003cHttpResponse, void\u003e\u003e handle(HttpRequest request)\n```\nSo, if you are not used to Either return, it is basically a way to have two return types, a Left and a Right.\nLeft would be a case when you dont want to proceed in your logic flow, and handle it as a exception, or something like this.\nRight would be the opposite. \u003cbr\u003e\nSo, knowing that, basically, you will want to return a HttpResponse if you do not want the request to going further in your api.\nOtherwise, you can just return a Right() with a null, for example. \u003cbr\u003e\nIt is importat to say that Scouter's uses FPDart package, which exports some functional programming stuff, thats where this Either comes from. If you want to, there is no need to add fpdart to your project, once Scouter does exports it too.\n\u003cbr\u003e\nExample of middleware:\n\n```dart\nclass ExampleMidleware implements HttpMiddleware {\n  const ExampleMidleware();\n  @override\n  Future\u003cEither\u003cHttpResponse, void\u003e\u003e handle(HttpRequest request) async {\n    if (request.path.contains(\"/game\")) {\n      return Left(\n        HttpResponse(\n          status: 400,\n          body: {\"status\": \"/game is not available\"},\n        ),\n      );\n    }\n    return Right(null);\n  }\n}\n```\n\n\nThats how you add middlewares to your module:\n\n```dart\nclass TestModule extends Module {\n  TestModule({super.preffix = \"teste\"});\n\n  @override\n  List\u003cRestController\u003e get controllers =\u003e [\n        GameController(),\n        ProfileController(),\n      ];\n\n  @override\n  List\u003cHttpMiddleware\u003e get middlewares =\u003e [\n    ExampleMidleware(),\n  ];\n}\n```\n\nAnd that is how you add middlewares to your controller:\n\n```dart\n@HttpController(\n  middlewares: [\n    ExampleMidleware(),\n  ],\n)\nclass GameController extends RestController {\n  @Get(\"/\")\n  HttpResponse getById(HttpRequest request) {\n    return HttpResponse(\n      body: {\"game\": \"Mario\"},\n      status: 202,\n    );\n  }\n}\n```\n\n## DI\n\nAs saied above, RestControllers already have a DI system, which is provided through the Injectable mixin. You can apply it whatever you want to. \u003cbr\u003e\nScouter's DI uses a Kiwi implementation, but the injection system is abstracted, soon, it will be possible to choose and implement the DI System you want to.\nBut for now, if you want to use Scouter's, you just need to the following:\n\n```dart\n@HttpController()\nclass FeatureController extends RestController {\n  FeatureController() {\n    inject\u003cFakeProvider\u003e(\n      SingletonInjection(\n        FakeProvider(),\n      ),\n    );\n  }\n\n  @Post(\"/save\")\n  HttpResponse save(HttpRequest request) {\n    final FakeProvider teste = injected();\n    return HttpResponse(\n      body: {\n        \"teste\": teste.name,\n      },\n    );\n  }\n}\n```\nSo, inject will be responsible for saving your container, and will receive a injection, which can be a SingletonInjection or a FactoryInjection. So, inside \u003c\u003e() you just put the type you want to attribute to your container and voilà, the magic is done. \u003cbr\u003e\n\nAnd if you want to apply Scouter's DI to anywhere else in your project, you can just do the following:\n\n```dart\nimport \"package:scouter/scouter.dart\";\nclass MyOwnClass with Injectable{\n}\n```\n\nThrough this, you will can use the inject and injected functions.\n\n\n\n## Compile \u0026 Deploy\nSo, because of Scouter depends on dart:mirrors, it cannot be compiled as an AOT, because the lack of support to Runtime. So, you should compile it as a Kernel Module:\n\n```dart\ndart compile kernel bin/your_app.dart\n```\n\nIt will result in a \".dill\" file, which you can run through:\n\n```dart\ndart run bin/your_app.dill\n```\n\nFor deploying it is recommended to use a docker container, which, unfortunately, will need to have Dart installed on it. You can use something like this  .Dockerfile\n\n```Dockerfile\nFROM dart:stable AS build\n\nWORKDIR /app\n\nCOPY pubspec.* ./\nRUN dart pub get\n\nCOPY . \n\nRUN dart pub get --offline\nRUN dart compile kernel bin/server.dart -o bin/server\n\nExpose 3000\nCMD [\"dart\", \"run\", \"bin/server\"]\n```\n\nWe are aware that this is not the best experience for deploying purposes, specially when talking about the container size due to the need of Dart. Soon We'll be trying to provide a better experience.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fviniciusamelio%2Fscouter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fviniciusamelio%2Fscouter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fviniciusamelio%2Fscouter/lists"}