Code generation is widely used in a lot of Dart and Flutter packages and I have nothing against it, but sometimes, especially for simple stuff, I don’t want to have to deal with it and I just want things to work on their own.

TL; DR

shelf_router_classes package provides an easy way to manage routes with shelf.

void main(List<String> arguments) async {
  Router router = getRoutersByClass([ExampleService]);
  await serve(logRequests().addHandler(router), 'localhost', 8080);
}

What’s the point

With the shelf package there are multiple ways to declare routes. You can either create the routes one by one and put them inside a Router to serve it or you can use shelf_router_generator and use code generation. To be fair this is not a very hard way to do this, you just need to write the Route as an annotation like @Route.get('/users/') and then the router code needs to be generated.

There is of course nothing wrong with this implementation. My only problem is that I want to get rid of this extra step. The controller-style declaration was something that I have always liked and I wanted to be able to write my server the same way without going through any extra steps. That’s when the idea to write up the shelf_router_classes came to my mind.

How it works

The dart:mirrors library mentions that it is still unstable and subject to change, which also could bring changes to shelf_router_classes in the future, but for the time being things seem to work just fine. Thanks to dart:mirrors I was able to get all the instance members of a class and all the relative metadata. This basically allowed me to read the methods inside a class and get the annotations that are attached to these methods through their metadata. So at this point a declaration like below would be enough:

class Example {
  @Route('GET', '/test')
  Response getTest(Request request) {
    return Response.ok('["1", "2", "3", "4"]');
  }
}

I could declare as many methods inside this class as I want and all I would have to do is pass the reference for the Example class into the getRoutersByClass function like below to create a Router:

  Router router = getRoutersByClass([Example]);

As this function accepts an array, it can take multiple classes, which allows compartmentalizing the routes. At this point you can do whatever you want inside the class and you don’t have to do anything extra like code generation or adding the route anywhere else as long as you declare the @Route annotation as shown above.

As mentioned earlier, behind the scenes, the class would be taken and all the instances would be scanned and if any method inside the class has the right annotation, if it does a dummy instance of the class would be created and the method would be passed as a route inside the Router.

Consideration

This package may not be very production-ready as dart:mirrors is subject to change, but as long as there are no breaking changes, it will keep working and it is definitely something I wanted to have and use as a utility instead of having to use code generation or manually declaring all the routes myself.