Shelf — Web Server with Dart
Tags: dart, backend
Published: 2021-11-06 15:09:20

---
title: "Shelf — Web Server with Dart"
author: ["Filipe Barroso"]
date: 2021-11-05 00:00:00 UTC
tags: ["dart", "backend"]
published: true
canonical_url: https://filipebarroso.com/articles/dart_shelf_backend/

---

![Shelf - Web Server with Dart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iysi5iti6u5541yie1jo.png)

This article is mostly a gathering of examples from Shelf and its official add-ons. Where I add my own comments that helped me understand the project and building a web server with Shelf, more about that in the end of the blogpost.


## Why Shelf

[Shelf](<https://github.com/dart-lang/shelf>), is a Web Server Middleware, not a full blown server framework like Django, it is modular, and you add add-ons for the functionalities you need. This modular structure gives the community an easy way to expand the project.

While [other Dart for backend solutions exist](<https://github.com/yissachar/awesome-dart#server-frameworks>). At the time of writing, two of the most promising Dart server frameworks, [Aqueduct](<https://github.com/stablekernel/aqueduct>) and [Angel](<https://github.com/angel-dart/angel>), have stopped their development. Relying on the community to grab where they left and continuing the work done. While from [Angel](<https://github.com/angel-dart/angel>) came [Angel3](<https://angel3-docs.dukefirehawk.com/>) and from [Aqueduct](<https://github.com/stablekernel/aqueduct>) came [Conduit](<https://github.com/conduit-dart/conduit>), I prefer to use a more stable solution, stable as maintained by a team with more resources. So, never forget to support the open-source community projects!

Shelf, is maintained by the Dart team itself but still lacks functionalities, again because is not a server framework. If we want Shelf to compete with other server frameworks, such as Django (Python) it is necessary quite a bit of work. However, at the current status, we can create a web server that suits our needs.

These are the official add-ons maintained by the Dart team:

-   Shelf (core)
-   Shelf Router
-   Shelf Router Generator
-   Shelf Static
-   Shelf WebSocket
-   Shelf CORS Headers


## Starting a backend Project

_I am assuming you have Dart installed on your machine, if not you can follow the instructions at <https://dart.dev/get-dart>._

To run a server you need to create a new Dart project, I'll be using the terminal and the following command

```bash
dart create shelf_core_example
```

This will create a folder with all the code inside `shelf_core_example` if you move into the new folder created you will notice the following file structure

```text
 CHANGELOG.md
 analysis_options.yaml
 pubspec.lock
 pubspec.yaml
 README.md
 bin/shelf_core_example.dart  # Same name as the project name created
```

The file `shelf_core_example.dart` has a simple main file.

```dart
void main(List<String> arguments) {
  print('Hello world!');
}
```

You can run the existing file, with a simple command:

```bash
dart run bin/shelf_core_example.dart
```

You will be prompted with the following message

```bash
$ dart run bin/shelf_core_example.dart
Hello World!
```


### Adding Shelf

**package**: https://pub.dev/packages/shelf


Now that you have the project created, to use Shelf we simply add a Dart package dependency.

In the project root you can add it by running the following:

```bash
dart pub add shelf
```

Now you are ready to work on your dart server! This command will also be used to add the other Shelf add-ons we will later add.
#### Shelf Example

```dart
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io; // For an easier evocation of its methods

void main() async {
  var handler =
      const Pipeline().addHandler(_echoRequest);

  // Starts the server, in 'localhost' and with port 8080.
  var server = await shelf_io.serve(handler, 'localhost', 8080);

  // From the source documentation
  /**
   * Whether the [HttpServer] should compress the content, if possible.
   *
   * The content can only be compressed when the response is using
   * chunked Transfer-Encoding and the incoming request has `gzip`
   * as an accepted encoding in the Accept-Encoding header.
   *
   * The default value is `false` (compression disabled).
   * To enable, set `autoCompress` to `true`.
   */
  server.autoCompress = true;

  print('Serving at http://${server.address.host}:${server.port}');
}

// Method Handler that replies with the path called.
// The return =Response.ok= is our helper static function to return a 200 code result, but we have many other, such as, Response.found() for 302 redirects or Response.notFound() for 404 not founds and all other codes that you can expect.
Response _echoRequest(Request request) =>
    Response.ok('Request for "${request.url}"');                                                                                                                                                                                           
```

-   Permalink to `autoCompress` source code: [autoCompress](<https://github.com/dart-lang/sdk/blob/74dea5d1bdc40c2aac1ba3440513de3f46099173/sdk/lib/%5Fhttp/http.dart#l134>)

-   Permalink to implementation of other [Response static methods](<https://github.com/dart-lang/shelf/blob/769c76abf5550d1a0441822f69e8382a62ca19f1/lib/src/response.dart#L66>)


## Adding Shelf Add-ons

The core library for Shelf, gives you the main functionalities of a server. The following example shows the barebones of a simplest server you can start running with Shelf.

### Shelf Router

**package**: https://pub.dev/packages/shelf_router

While Shelf (core) lacks the functionality of setting Routes, we use the add-on `shelf_router` which will give you the `router` instance, with all the REST methods, such as GET and POST and all other calls that make up REST.

```bash
dart pub add shelf_router
```


#### Shelf Router Example

_server.dart_

```dart
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'service.dart';

/// Similar with the previous example but here we create the routing in our new class 'Service' and we call its handler.
void main() async {
  final service = Service();

  final server = await shelf_io.serve(service.handler, 'localhost', 8080);

  print('Serving at http://${server.address.host}:${server.port}');
}
```

_service.dart_

```dart
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import 'api.dart';

/// Class used to set up all the routing for your server
class Service {

  // The handler/middleware that will be used by the server, all the routing for the server will be implemented here.
  Handler get handler {
    final router = Router();

    // GET
    // Replies with the text inserted in the path, example: http://localhost:8080/say-hi/Filipe will prompt "hi Filipe".
    router.get('/say-hi/<name>', (Request request, String name) {
      return Response.ok('hi $name');
    });

    // GET
    // You can use regex to limit the capture of the path arguments, in this case the userId param needs to be only digits. Note how you can also used 'async'
    // With the server running try to open http://localhost:8080/user/132 and http://localhost:8080/user/a123
    router.get('/user/<userId|[0-9]+>', (Request request) async {
      await Future.delayed(Duration(milliseconds: 100));
      return Response.ok('_o/');
    });

    // You can also embed other routers, in this case it will help organize your routers
    // Note: This needs be before the catch 'router.all' that follows
    router.mount('/api/', Api().router);

    // A catch all of the non implemented routing, useful for showing 404 page now found
    // With the server running try to open http://localhost:8080/abc
    router.all('/<ignored|.*>', (Request request) {
      return Response.notFound('Page not found');
    });

    return router;
  }
}
```

_api.dart_

```dart
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import 'service.dart'

/// Decoupled endpoints for a easier organisation of all endpoints
class Api {
  Future<Response> _messages(Request request) async {
    return Response.ok('[]');
  }

  Router get router {
    final router = Router();

    router.get('/messages', _messages);
    router.get('/messages/', _messages);
    router.post('/messages/', _messages);
    router.update('/messages/', _messages);
    router.delete('/messages/', _messages);

    router.all('/<ignored|.*>', (Request request) => Response.notFound('null'));
    return router;
  }
}
```

With the previous code setup you can now run the server in the root of the project simply by executing

```text
dart run bin/server.dart
```


### Shelf Router Generator

**package**: https://pub.dev/packages/shelf_router_generator

To reduce boilerplate, the add-on `shelf_router_generator` helps us with the code generation. The add-on gives us annotations that replaces the need to call the 'router' instance.

Shelf router generator leverages `build_runner` to generate code, check the bullet Dependency for more information.

```bash
dart pub add shelf_router_generator
```


#### Dependency

**package**: https://pub.dev/packages/build_runner

To learn on how to use `build_runner` check check the package URL.

Add the `build_runner` has `dev_dependencies`

```bash
dart pub add builder_runner
```


#### Shelf Router Generator Example

_server.dart_

```dart
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'service.dart';

part 'server.g.dart';

void main() async {
  final service = Service();
  final server = await shelf_io.serve(service.handler, 'localhost', 8080);

  print('Serving at http://${server.address.host}:${server.port}');
}
```

_service.dart_

```dart
class Service {
  @Route.get('/say-hi/<name>')
  Response _hi(Request request, String name) => Response.ok('hi $name');

  @Route.all('/<ignored|.*>')
  Response _notFound(Request request) => Response.notFound('Page not found');

  Handler get handler => _$ServiceRouter(this);
}
```

You need run once the `build_runner` and only then the `dart run`.


### Shelf Static

If you need server side processing, to show an html page, you will need to add this add-on so that so you can present the users to

**package**: https://pub.dev/packages/shelf_static

```bash
dart pub add shelf_static
```


#### Shelf Static Example

```dart
import 'package:shelf_static/shelf_static.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;

void main() async {
  final handler = createStaticHandler('files', defaultDocument: 'index.html');

  final server = await shelf_io.serve(handler, 'localhost', 8080);

  print('Serving at http://${server.address.host}:${server.port}');
}
```


### Shelf WebSocket

**package**: https://pub.dev/packages/shelf_web_socket/

With Shelf, you don't necessary need to work with HTTP, you also have the possibility to have a socket open through `shelf_web_socket` might be an add-on that not everyone uses, but it is interesting to know that it has.

```bash
dart pub add self_web_socket
```


#### Shelf WebSocket Example

```dart
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io; // For an easier evocation of its methods
import 'package:shelf_web_socket/shelf_web_socket.dart';

void main() {
  var handler = webSocketHandler((webSocket) {
    webSocket.stream.listen((message) {
      webSocket.sink.add('Echo: $message');
    });
  });

  shelf_io.serve(handler, 'localhost', 8080).then((server) {
    print('Serving at ws://${server.address.host}:${server.port}');
  });
}
```

To test this server with Web Socket I used the "Simple WebSocket Client" plugin for Chromium
<https://chrome.google.com/webstore/detail/simple-websocket-client/pfdhoblngboilpfeibdedpjgfnlcodoo?hl=en>


### Shelf CORS

**package**: https://pub.dev/packages/shelf_cors_headers

Last but not least, we have this add-on to help us change the headers to have CORS working locally. Exposes a method called `corsHeaders()` that returns a Middleware used in the Shelf Pipeline.

Do not confuse this add-on with `shelf_cors` that is not available for Dart 2.0

```nil
dart pub add shelf_cors_headers
```


#### Shelf CORS Headers Example

```dart
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_cors_headers/shelf_cors_headers.dart';

void main() async {
  final overrideHeaders = {
    ACCESS_CONTROL_ALLOW_HEADERS: 'example.com',
    'Content-Type': 'application/json;charset=utf-8',
  };

  final handler = const Pipeline()
    .addMiddleware(
      corsHeaders( // Middleware from 'shelf_cors_headers'
          headers: overrideHeaders,
      ),
    )
    .addHandler(_echoRequest);

  final server = await shelf_io.serve(handler, 'localhost', 8080);

  print('Serving at http://${server.address.host}:${server.port}');
}

Response _echoRequest(Request request) {
  return Response.ok('Request for "${request.url}"');
}
```


## Conclusion

While we wait for other Dart server frameworks to mature, we can always rely on Shelf to implement our project. There is still a lot needed to implement and few add-ons available that extends Shelf ([https://pub.dev/packages?q=shelf](https://pub.dev/packages?q=shelf))

Next article on the topic of Dart Backend will be about integrating Stripe into a server to pay recurrent or by metered API consumption. If I see myself using other Shelf add-on I'll write a new blogpost

--

Originally published at https://filipebarroso.com/articles/dart_shelf_backend/