Skip to content
AgileLogics
6 min read

Server-Side Dart

Prerequisites: Futures and async/await, Working with JSON

Dart also runs on servers. Its async I/O handles many connections efficiently, it compiles to a small native executable, and a Flutter team can share model classes and validation between app and back end.

A JSON API with Shelf

Shelf is the Dart team's composable web server library:

dart create -t server-shelf todo_api
import 'dart:convert';
import 'dart:io';

import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart';

final _tasks = <Map<String, Object?>>[];
var _nextId = 1;

Response _json(Object? body, {int status = 200}) => Response(
      status,
      body: jsonEncode(body),
      headers: {'content-type': 'application/json'},
    );

Future<void> main() async {
  final router = Router()
    ..get('/health', (Request req) => _json({'status': 'ok'}))
    ..get('/tasks', (Request req) => _json(_tasks))
    ..post('/tasks', (Request req) async {
      final body = jsonDecode(await req.readAsString());
      if (body case {'title': String title} when title.trim().isNotEmpty) {
        final task = {'id': _nextId++, 'title': title, 'done': false};
        _tasks.add(task);
        return _json(task, status: 201);
      }
      return _json({'error': 'title is required'}, status: 422);
    });

  final handler = const Pipeline().addMiddleware(logRequests()).addHandler(router.call);
  final server = await io.serve(handler, InternetAddress.anyIPv4, 8080);
  print('Listening on port ${server.port}');
}
$ curl -X POST localhost:8080/tasks -H "Content-Type: application/json" -d '{"title":"Learn Shelf"}'
{"id":1,"title":"Learn Shelf","done":false}
  • A handler is simply a function from Request to Response.
  • Middleware (such as logRequests) wraps handlers to add behavior to every request.
  • The if-case pattern validates the JSON body's shape in one step.

Frameworks such as Dart Frog and Serverpod build on these ideas with file-based routing, code generation, and database integration.

Deployment

dart compile exe bin/server.dart -o build/server

The result is a single native executable with no Dart SDK needed at runtime, making for small Docker images (the dart create template includes a Dockerfile).

When to choose it

Server-side Dart makes most sense for Flutter teams who want one language across the stack. For a team starting fresh, larger back-end ecosystems (Go, Node.js, Python, Java, Kotlin) offer more libraries and hosting options.