Properly handling exceptions with Dart Shelf
·15 min read

Blog post image


Handling exceptions properly is necessary when writing any code to keep things running smoothly, give proper feedback if something didn't work as expected, and numerous other reasons. Shelf is a fantastic web server package made by Google but it's difficult to to cleanly handle exceptions without lots of boilerplate code that is prone to errors, time consuming to write, and less maintainable. A good way to fix this problem is to make a custom adapter to use with Shelf.

What is an Adapter?

According to the API reference an adapter is "any code that creates Request objects, passes them to a handler, and deals with the resulting Response."


For most efficient use of Exceptions adapter, we need to properly use Exception throws. First of all make sure that you have properly implemented all exceptions that will be thrown.

For example:

class MyException extends Exception {    final String message;    MyException(this.message);}

Make few exceptions for your use case. Generally you should have at least one exception for each main error that will be thrown. (e.g. NotAuthorisedException, BadRequestException, etc.)

We will throw them and then we will use Exceptions adapter to handle them.

void somefunc(UserModel? user) {    if (user == null) {        throw NotAuthorisedException("User is not authorised");    }}

How to init the Exceptions adapter

In desired folder create a file called exception_adapter.dart. Don't forget to add export to exports.dart.

This will be a file that will contain all Exceptions adapter code.

class ExceptionsAdapter {  final Handler _handler; // this is the next handler in the chain  ExceptionsAdapter(this._handler); /// It will be called for every request, /// and it will be responsible for handling exceptions above  Handler get handler => (Request request) async {        try {          return await _handler(request);        } on BadRequestException catch (e) {          return Response.badRequest(body: e.message);,          );        } on NotAuthorisedException catch (e) {          return Response.(            401 // Unauthorized http status code            body: e.message);        }         /// you should add all other exceptions here that you want to handle        /// Don't forget to add general catch for all other exceptions        catch (e) {          return Response.serverError(body: "Internal server error");           // for safery reasons we shouldn't return unknown exception to user        }    };}

How to use Exceptions adapter

Now we have created adapter but it now needs to be used in the pipeline.

In a file with your server pipeline you need to add the following lines:

  // this is your main handler that will be called for every request  final handler = MainController().handler;   // this is handler that will handle all exceptions  // it takes the next handler in the chain as an argument  final exceptionAdapterHandler = ExceptionsAdapter(handler).handler;   final _handler = Pipeline()      .addMiddleware(logRequests())      .addHandler(exceptionAdapterHandler);  final server = await serve(_handler, ip, port);

Our request will follow the following pipeline:

  1. log middleware
  2. exception adapter middleware
  3. main controller handler is temporarily down for maintenence. Please check back later.