はじめに
最近Dartを触ることが多く、簡単なAPIくらいならそのまま同じ言語で作った方がコンテキストスイッチしなくていいんじゃね?と思い、調べていたらDartでサーバーサイド開発ができることを知ったので紹介していきます。
環境
Dart: v3.5.3
Shelfについて
ShelfとはDart公式から出ているサーバーアプリケーション用のフレームワークです
これを使えば結構簡単にサーバーサイド開発ができるので今回はShelfを使っていきます。
環境構築してみる
dart createにshelfを使ったサーバーアプリケーションを作成するオプションがあるのでそれを使っていきます。
$ dart create -t server-shelf sample-server-app
Creating sample_server_app using template server-shelf...
.gitignore
analysis_options.yaml
CHANGELOG.md
pubspec.yaml
README.md
Dockerfile
.dockerignore
test/server_test.dart
bin/server.dart
Running pub get... 1.2s
Resolving dependencies...
Downloading packages...
Changed 53 dependencies!
4 packages have newer versions incompatible with dependency constraints.
Try `dart pub outdated` for more information.
Created project sample_server_app in sample-server-app! In order to get started, run the following commands:
cd sample-server-app
dart run bin/server.dart
動かしてみる
早速動かしてみましょう
アプリケーションを作った時に出てくるdart run bin/server.dart
で動くみたいなので、一旦それで動かしてみます
$ dart run bin/server.dart
Server listening on port 8080
8080番ポートで動いてるみたいですね
bin/server.dartを見ていきましょう
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart';
import 'package:shelf_router/shelf_router.dart';
// Configure routes.
final _router = Router()
..get('/', _rootHandler)
..get('/echo/<message>', _echoHandler);
Response _rootHandler(Request req) {
return Response.ok('Hello, World!\n');
}
Response _echoHandler(Request request) {
final message = request.params['message'];
return Response.ok('$message\n');
}
void main(List<String> args) async {
// Use any available host or container IP (usually `0.0.0.0`).
final ip = InternetAddress.anyIPv4;
// Configure a pipeline that logs requests.
final handler =
Pipeline().addMiddleware(logRequests()).addHandler(_router.call);
// For running in containers, we respect the PORT environment variable.
final port = int.parse(Platform.environment['PORT'] ?? '8080');
final server = await serve(handler, ip, port);
print('Server listening on port ${server.port}');
}
難しいことは置いといて、/
と/echo/<message>
というエンドポイントがあるっぽい
試しに叩いてみます
$ curl http://localhost:8080/
Hello, World!
$ curl http://localhost:8080/echo/I_love_Dart
I_love_Dart
無事に動いてますね
エンドポイントを追加してみる
どうもRouterとやらにどんどんエンドポイントを追加していけば良さそうな予感
ということでこんな感じに書き換えてみる
final _router = Router()
..get('/', _rootHandler)
..get('/echo/<message>', _echoHandler)
..get('/todos', _todosHandler);
Response _todosHandler(Request req) {
final todos = {'id': 1, 'title': 'Buy milk'};
return Response.ok(jsonEncode(todos));
}
$ curl http://localhost:8080/todos
{"id":1,"title":"Buy milk"}
Response.ok
に渡せるのはStringかList、Stream>のみのため、jsonEncodeでMapをStringに変換してます。(めっちゃ微妙い)
エンドポイントをまとめてみる
たくさんエンドポイントが生えてきたらこんな感じになっちゃいますね
final _router = Router()
..get('/', _rootHandler)
..get('/echo/<message>', _echoHandler)
..get('/todos', _todosHandler)
..get('/todo/<id>', _findTodoHandler)
..post('/todo', _createTodoHandler)
..put('/todo', _updateTodoHandler)
..delete('/todo/<id>', _deleteTodoHandler)
..
..;
エンドポイントが増えれば増えるだけ_router
に対して付け加えなくてはいけないのは結構不便ですよね。
shelfでは以下のようにエンドポイントをまとめることができます。
final _router = Router()
..mount('/todo', _todoRouter.call)
..mount('/user', _userRouter.call);
final _todoRouter = Router()
..get('/<id>', _findTodoHandler)
..post('/', _createTodoHandler);
final _userRouter = Router()
..get(...);
これでスッキリですね。
ミドルウェアを追加してみる
リクエストやレスポンスでログを吐かせたい、特定のエンドポイントは認証したいなど実際にリクエストを捌く前にミドルウェアで何かしらしたい場合はよくあると思います。
shelfでは以下のようにミドルウェアを追加できます。
final authMiddleware = createMiddleware(
requestHandler: (req) {
// 何かしら認証処理
},
);
void main(List<String> args) async {
// Use any available host or container IP (usually `0.0.0.0`).
final ip = InternetAddress.anyIPv4;
// Configure a pipeline that logs requests.
final handler = Pipeline()
.addMiddleware(logRequests())
.addMiddleware(authMiddleware)
.addHandler(_router.call);
// For running in containers, we respect the PORT environment variable.
final port = int.parse(Platform.environment['PORT'] ?? '8080');
final server = await serve(handler, ip, port);
print('Server listening on port ${server.port}');
}
createMiddleware
関数が提供されているので簡単にミドルウェアが作れます。
ホットリロードしてみる
コードを書き換えたらホットリロードしたいですよね。僕はしたいです。
shelf自体にホットリロードの機能は搭載されていないので、今回はパッケージを使います。
void main(List<String> args) async {
withHotreload(() {
// Use any available host or container IP (usually `0.0.0.0`).
final ip = InternetAddress.anyIPv4;
// Configure a pipeline that logs requests.
final handler = Pipeline()
.addMiddleware(logRequests())
.addMiddleware(authMiddleware)
.addHandler(_router.call);
// For running in containers, we respect the PORT environment variable.
final port = int.parse(Platform.environment['PORT'] ?? '8080');
return await serve(handler, ip, port);
}
}
感想
Dartは易しい言語だし、functionalに書けるのは非常にいいし、ある程度機能も揃ってるしでFlutterアプリエンジニアがシュッとAPI書くのにはちょうどいいのかなと思いました。
実行ファイル作ってくれるるDockerfileもバンドルされてるので、デプロイもある程度簡単そうな印象です。
感想(本音)
Dartで書きたいならshelf使えばいいけど、別に言語にこだわらないならExpressでよくない?というのが素直な感想でした。