I tried Dart backend

Photo by Goran Ivos on Unsplash

Flutter には盛り上がりがみられる Dart でバックエンドもやってみました.
この時気になるのがフレームワークどうするか問題ですね.
メジャーどころは下記の 2 つです.

Aqueduct

最終 commit 2020/09/01

Angel

最終 commit 2020/05/05

とこんな感じで開発はどちらも止まっており,もはや選択肢に入らない状態です.
というわけで shelf と shelf_router で実装するのがよさそうな雰囲気です.

shelf

shelf_router

ひな形が stagehand で作れるのでそちらで.
ただし,shelf_router は初期だと含まれていないので pubspec.yaml に追加してください.

$ stagehand server-shelf
Creating server-shelf application `server_test`:
  /home/yuzumone/server_test/.gitignore
  /home/yuzumone/server_test/CHANGELOG.md
  /home/yuzumone/server_test/README.md
  /home/yuzumone/server_test/analysis_options.yaml
  /home/yuzumone/server_test/bin/server.dart
  /home/yuzumone/server_test/pubspec.yaml
  6 files written.
--> to provision required packages, run 'pub get'
--> run your app via `dart bin/server.dart`.

初期 server.dart は下記の通り.

import 'dart:io';import 'package:args/args.dart';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;// For Google Cloud Run, set _hostname to '0.0.0.0'.
const _hostname = 'localhost';void main(List<String> args) async {
  var parser = ArgParser()..addOption('port', abbr: 'p');
  var result = parser.parse(args);// For Google Cloud Run, we respect the PORT environment variable
  var portStr = result['port'] ?? Platform.environment['PORT'] ?? '8080';
  var port = int.tryParse(portStr);if (port == null) {
    stdout.writeln('Could not parse port value "$portStr" into a number.');
    // 64: command line usage error
    exitCode = 64;
    return;
  }var handler = const shelf.Pipeline()
      .addMiddleware(shelf.logRequests())
      .addHandler(_echoRequest);var server = await io.serve(handler, _hostname, port);
  print('Serving at http://${server.address.host}:${server.port}');
}shelf.Response _echoRequest(shelf.Request request) =>
    shelf.Response.ok('Request for "${request.url}"');

これを shelf_router を使っていい感じに GET と POST を生やしてみます.
下記の感じで handler のところを書き換えます.
shelf_router を使うと対応 method と endpoint が分かりやすくなります.
ちなみに shelf_router の README では app.handler を渡す感じで記載されていますが,deprecated になっており,下記のように app 自体を渡すのが正解です.

var app = Router();
app.get('/status', (shelf.Request request) {
  return shelf.Response.ok('ok');
});
app.post('/post', (shelf.Request request) async {
  final content = await request.readAsString();
  final data = json.decode(content);
  return shelf.Response.ok('Hello, ${data["text"]}');
});
var server = await io.serve(app, _hostname, port);

せっかく生やしたので叩いてみます.

$ curl http://localhost:8080/status
ok
$ curl -X POST -d '{"text": "world"}' http://localhost:8080/post
Hello, world

ちなみに shelf_router_generator という package もありこちらは Flask のようにデコレータで method や endpoint を指定できます.
こっちも良さそうです.

shelf_router_generator | Dart Package

あとこれは完全に備考ですが,shelf はもちろん dart2native に対応しています.
Aqueduct は対応していなかったので一応.

$ dart2native bin/server.dart -o test
Generated: /home/yuzumone/server_test/test