Learn Dart - Server-side Dart & Backend
Series/Learn Dart/Episode 10
Episode 10 of 23

Learn Dart - Server-side Dart & Backend

This episode builds a backend with Dart: getting to know the Shelf, Dart Frog, and Serverpod frameworks, using routing and middleware, handling requests and responses, JSON serialization and persistence, and deploying a Dart server application.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Dart's capabilities don't stop at mobile applications. With a fast runtime, a strong async model, and AOT compilation, Dart is a legitimate choice for building backends. Episode 10 takes you through building an HTTP server, handling routing, using middleware, serializing data, and preparing deployment.

We'll focus on Shelf — the official lightweight, modular framework — while also recognizing alternatives like Dart Frog and Serverpod. After this episode, you can write a real API and run it in production.

Backend Frameworks for Dart

Shelf: The Official Modular Framework

Shelf was built by the Dart team and is the foundation for many other frameworks. It treats handlers as simple functions that accept a Request and return a Response. Here's your first server:

HTTP server with Shelf
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
 
void main() async {
  var handler = const Pipeline()
      .addMiddleware(logRequests())
      .addHandler((Request req) {
    return Response.ok('Halo dari Shelf');
  });
 
  var server = await io.serve(handler, '0.0.0.0', 8080);
  print('Server berjalan di port ${server.port}');
}

Pipeline() and addMiddleware(logRequests()) add logging middleware before the handler runs. io.serve binds the server to port 8080.

Dart Frog and Serverpod

Besides Shelf, there are two popular frameworks:

  • Dart Frog: a framework with file-based routing like Next.js, very fast for REST APIs.
  • Serverpod: a full-stack framework with code generation, ORM, and integrated client-server communication.

Choose according to scale: Shelf for full control, Dart Frog for productivity, Serverpod for complete applications with a database.

Routing, Request, and Response

Routing with shelf_router

For many endpoints, use shelf_router:

Routing with shelf_router
import 'package:shelf_router/shelf_router.dart';
import 'package:shelf/shelf.dart';
 
final router = Router()
  ..get('/api/halo', (Request req) => Response.ok('Halo'))
  ..get('/api/users/<id>', (Request req, String id) {
    return Response.ok('User $id');
  });
 
void main() async {
  var server = await io.serve(router, '0.0.0.0', 8080);
  print('Server berjalan');
}

router.get('/api/users/<id>', ...) captures the id parameter from the path. Routes are defined declaratively, so the endpoint list is easy to read.

Reading Request Data

Handlers can read the body, query parameters, and headers:

Reading a query parameter
import 'package:shelf/shelf.dart';
 
Response halo(Request req) {
  var nama = req.url.queryParameters['nama'] ?? 'tamu';
  return Response.ok('Halo, $nama');
}

req.url.queryParameters['nama'] gets the query parameter from the URL. For POST requests, read the JSON body with await req.readAsString() then parse it.

JSON Serialization and Persistence

JSON Serialization

Data exchange between services commonly uses JSON:

JSON serialization
import 'dart:convert';
 
class User {
  final String id;
  final String nama;
 
  User(this.id, this.nama);
 
  Map<String, dynamic> toJson() => {'id': id, 'nama': nama};
}
 
void main() {
  var user = User('1', 'Arman');
  var json = jsonEncode(user.toJson());
  print(json);
}

jsonEncode(user.toJson()) converts an object into a JSON string. dart:convert is the bridge between Dart objects and the format sent over the network.

Data Persistence

To store data, connect to a database. The most common directions in Dart:

  • PostgreSQL: via package:postgres, with a connection pool.
  • Object document: MongoDB via package:mongo_dart.
  • ORM: package:serverpod or package:angel3_orm for an abstraction layer.

Start with a connection created once and reused — don't open a new connection per request.

Deploying a Dart Server Application

AOT Compilation and Docker

The biggest benefit of a Dart backend: AOT compilation into a single self-contained binary:

Compile a production binary
dart compile exe bin/server.dart -o bin/server

dart compile exe bin/server.dart produces a native executable without the Dart runtime. This binary can be wrapped in a container:

Dockerfile for a Dart server
FROM debian:bookworm-slim
WORKDIR /app
COPY bin/server /app/server
EXPOSE 8080
CMD ["/app/server"]

The built container can be deployed anywhere: a VM, Kubernetes, or a serverless platform that supports containers.

Conclusion

Key takeaways:

  • Shelf is the official modular framework; Pipeline and middleware compose handlers.
  • Dart Frog for file-based routing; Serverpod for full-stack with ORM.
  • shelf_router handles path parameters and many endpoints.
  • dart:convert with jsonEncode and toJson for JSON serialization.
  • Database connections are created once and reused, not per request.
  • dart compile exe produces a self-contained binary ready to wrap in Docker.

In the next episode 11, we'll cover web development with Dart — compilation to JavaScript with dart2js and build tools, interop with JavaScript, state management and client-side DOM manipulation, and frameworks like Flutter Web.

Learn Dart - Server-side Dart & Backend | Learn Dart