Embla

Embla is a powerful but simple server side application framework for Dart.

Usage

Install like so:

# pubspec.yaml
dependencies:
  embla: any
> pub get

Embla scripts can be run directly with dart my_script.dart, but for development we can use the Embla CLI:

> pub global activate embla
# Add pub's binaries to PATH, to be able to omit "pub run" (Also: https://github.com/dart-lang/pub/issues/1204)
> PATH=$PATH:~/.pub-cache/bin
> embla start

Currently, embla start will look for a bin/server.dart file and start the app. If you make changes to your project files, the app will automatically restart.

Overview

Here's an example of a super simple Embla app.

export 'package:embla/bootstrap.dart';
import 'package:embla/http.dart';

get embla => [
  new HttpBootstrapper(
    pipeline: pipe(() => 'Hello world!')
  )
];

This application starts a server, and responds with "Hello world!" on every request. Looks weird? Let's figure out what's going on.

Bootstrapping

Instead of the good old main function, Embla requires a getter called embla in the main entry point script. The actual main function will be provided by bootstrap.dart.

export 'package:embla/bootstrap.dart';

get embla => [];

If we were to run the above script, we would get an empty Dart process that did nothing, and would close on Ctrl+C.

To hook into the application, we can add Bootstrappers to the embla function. HttpBootstrapper comes out of the box if we just import 'package:embla/http.dart'. Each bootstrapper should be instantiated in the embla function, and any configuration needed is passed through the constructor.

HTTP Pipeline

It just so happens the HttpBootstrapper takes a named pipeline parameter, that represents the request/response pipeline for the server.

To create a pipeline, we use the pipe helper provided by embla/http.dart. A pipeline consists of a series of Middleware. Embla wraps Shelf for this.

import 'dart:async';

export 'package:embla/bootstrap.dart';
import 'package:embla/http.dart';

get embla => [
  new HttpBootstrapper(
    pipeline: pipe(
      MyMiddleware
    )
  )
];

class MyMiddleware extends Middleware {
  Future<Response> handle(Request request) {
    // Pass along to the next middleware
    return super.handle(request);
  }
}

The pipe allows for different formats for Middleware. You can pass in a Shelf Middleware directly, or the Type of a middleware class. It also supports passing in a Function, which will be converted to a route handler.

Routing

Routes are nothing more than conditional paths in the pipeline. Here's an example:

pipeline: pipe(

  MiddlewareForAllRoutes,

  Route.get('/', () => 'Hello world'),

  Route.all('subroutes/*',
    MiddlewareForAllRoutesInSubroutes,

    Route.get('', () => 'Will be reached by GET /subroutes'),

    Route.put('action', () => 'Will be reached by PUT /subroutes/action'),

    Route.get('another',
      SpecialMiddlewareForThisRoute,
      () => 'Will be reached by PUT /subroutes/another'
    ),

    Route.get('deeper/:wildcard',
      ({String wildcard}) => 'GET /subroutes/deeper/$wildcard'
    )
  ),

  () => 'This will be reached by request not matching the routes above'
)

Controller

In Embla, controllers are also middleware. They are collections of routes, after all. The controllers use annotations to declare routes.

export 'package:embla/bootstrap.dart';
import 'package:embla/http.dart';
import 'package:embla/http_annotations.dart';

get embla => [new HttpBootstrapper(pipeline: pipe(MyController))];

class MyController extends Controller {
  /// GET /action  ->  'Response'
  @Get() action() {
    return 'Response';
  }

  /// POST /endpoint  ->  302 /
  @Post('endpoint') methodName() {
    return redirect('/action');
  }
}

Since controllers are middleware too, we can easily route our controllers to endpoints like this:

Route.all('pages/*', PagesController)

Custom Bootstrappers

Bootstrappers hook into the initialization and deinitialization of the application. Creating one is super simple.

export 'package:embla/bootstrap.dart';
import 'package:embla/application.dart';

get embla => [new MyBootstrapper()];

class MyBootstrapper extends Bootstrapper {
  @Hook.init
  init() {
    print('Initializing the application!');
  }
}

Libraries

application
bootstrap
http
http_annotations