1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

サーバーサイドDart入門

Last updated at Posted at 2025-01-09

はじめに

最近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でよくない?というのが素直な感想でした。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?