Dart on Server
Tags: dart, backend, server, http
Published: 2021-04-05 22:00:26

Dart is practically not inferior to the NodeJS on the server in terms of performance, but its ecosystem is rather small by comparison. But still, sometimes you come across something interesting.

Today we will talk about a new HTTP server on dart - [Dia](https://pub.dev/packages/dia). This is a small simple web server inspired by KoaJS. The main idea of the project: the context of the http request, which passes the queue of the middleware.

To install it, add this to your pubspec.yaml:

```yaml
dependencies:
  dia: ^0.0.7
```

It is generally very simple to use:

```dart
import 'package:dia/dia.dart';

main() {
  /// Create instance of Dia
  final app = App();
  
  /// Add middleware
  app.use((ctx, next) async {
    /// write response on all query
    ctx.body = 'Hello world!';
  });

  /// Listen localhost:8080
  app
      .listen('localhost', 8080)
      .then((info) => print('Server started on http://localhost:8080'));
}
```

The main brick of the application is middleware. This is an asynchronous function that takes a Context and a reference to the next middleware as arguments.

The Сontext contains getters and setters for the dart.io.HttpRequest and dart.io.HttpResponse fields. If you are familiar with koa, then you will understand the basic idea of the context. Most importantly, you can use your own extended context with additional fields, such as the current user.

```dart
class MyContext extends Context{
  User? user;

  MyContext(HttpRequest request): super(request);
}


main() {
  /// Create instance of Dia
  final app = App<MyContext>();

  app.use((ctx, next) async {
    ctx.user = new User('test');
    await next();
  });

  app.use((ctx, next) async {
    if(ctx.user==null){
      ctx.trowError(401);
    }
  });
  
  /// Add middleware
  app.use((ctx, next) async {
    /// write response on all query
    ctx.body = 'Hello world!';
  });

  /// Listen localhost:8080
  app
      .listen('localhost', 8080)
      .then((info) => print('Server started on http://localhost:8080'));
}
```

The next - a link to the next middleware in the queue. When a middleware does not return the final result, but only changes the context, it often needs to wait for the next middleware to complete. This is necessary, for example, to add logging or error handling.

```dart
app.use((ctx, next) async {
  final start = DateTime.now();
  await next();
  final diff = DateTime.now().difference(start).inMicroseconds;
  print('microseconds=$diff')
});
```

There are ready-made middleware in the form of separate packages. This allows, without increasing the code base, to use only those of them that are necessary in the project. For example:

* [dia_cors](https://pub.dev/packages/dia_cors) - middleware for add CORS headers
* [dia_static](https://pub.dev/packages/dia_static) - middleware for serving static files
* [dia_router](https://pub.dev/packages/dia_router) - middleware like as koa_router, for work with url path
* [dia_body](https://pub.dev/packages/dia_body) - middleware for parse request body.

Let us dwell on the last two of them in more detail. 

The first one is [dia_router](https://pub.dev/packages/dia_router) - allows you to set middleware for specific URLs and specific http methods.

It requires a special Context with the Routing mixin. They contains additional parameters that can be passed to url

```dart
class ContextWithRouting extends Context with Routing {
  ContextWithRouting(HttpRequest request) : super(request);
}

void main() {
  /// create Dia app with Routing mixin on Context
  final app = App<ContextWithRouting>();

  /// create router
  final router = Router<ContextWithRouting>('/route');

  /// add handler to GET request
  router.get('/data/:id', (ctx, next) async {
    ctx.body = '${ctx.params}';
  });

  /// start server
  app
      .listen('localhost', 8080)
      .then((_) => print('Started on http://localhost:8080'));
}
```

If we open the http://localhost:8080/data/12 in the browser, we will see `{id: 12}`

The second package that we will consider is [dia_body](https://pub.dev/packages/dia_body) - it allows you to receive the parameters passed with the http request. It also needs an extended context with the ParsedBody mixin.

```dart
class ContextWithBody extends Context with ParsedBody {
  ContextWithBody(HttpRequest request) : super(request);
}

void main() {
  final app = App<ContextWithBody>();

  app.use(body());

  app.use((ctx, next) async {
    ctx.body = ''' 
    query=${ctx.query}
    parsed=${ctx.parsed}
    files=${ctx.files}
    ''';
  });

  /// Start server listen on localhost:8080
  app
      .listen('localhost', 8080)
      .then((info) => print('Server started on http://localhost:8080'));
}
```

* `ctx.query` - contain url get parameters. `?param=value` - `{param: value}`
* `ctx.parsed` - contains json body or x-www-form-urlencoded or form-data params of http request
* `ctx.files` - contains uploaded files. By default, files are loaded into the temporary system directory, but the path can be changed.

We can use the Context with all extensions and our fields at the same time:

```dart
class CustomContext extends Context with Routing, ParsedBody {
  User? user;
  ContextWithBody(HttpRequest request) : super(request);
}
``` 

We can run the http server version as well:

```dart
  final serverContext = SecurityContext();
  serverContext
      .useCertificateChainBytes(await File(certificateChain).readAsBytes());
  serverContext.usePrivateKey(serverKey, password: 'password');

  final server = await app.listen(
      'localhost', 8444,
      securityContext: serverContext);
```

All sources of the given packages are on [GitHub](https://github.com/unger1984/dia) and published under the MIT license. I would be glad to receive any feedback, issue and pull requests!