One Language, Everywhere
The Dart Server Landscape
Shelf — The Foundation
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart';
import 'package:shelf_router/shelf_router.dart';
final _router = Router()
..get('/', _rootHandler)
..get('/health', _healthHandler)
..get('/users/<id>', _getUserHandler);
Response _rootHandler(Request req) =>
Response.ok('Welcome to Dart API Server!\n');
Response _healthHandler(Request req) =>
Response.ok('{"status": "healthy"}',
headers: {'content-type': 'application/json'});
Response _getUserHandler(Request req, String id) =>
Response.ok('{"id": "$id", "name": "John Doe"}',
headers: {'content-type': 'application/json'});
void main(List<String> args) async {
final handler =
Pipeline().addMiddleware(logRequests()).addHandler(_router);
final server = await serve(handler, InternetAddress.anyIPv4, 8080);
print('Server listening on port ${server.port}');
}
Best for
Dart Frog — The Practical Choice
// routes/users/[id].dart — file-based routing!
import 'package:dart_frog/dart_frog.dart';
Response onRequest(RequestContext context, String id) {
return Response.json(
body: {'id': id, 'name': 'John Doe', 'status': 'active'},
);
}
When to use
Serverpod — The Enterprise Option
Framework Comparison
| Shelf | Dart Frog | Serverpod | |
|---|---|---|---|
| Learning curve | Low | Low-Medium | Medium-High |
| Flexibility | Very High | High | Medium |
| Built-in DB | ❌ | ❌ | ✅ PostgreSQL |
| Code generation | ❌ | ❌ | ✅ |
| Best for | Microservices | REST APIs | Full-stack apps |
Building a RESTful API with Dart
import 'dart:convert';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart';
import 'package:shelf_router/shelf_router.dart';
// Simple in-memory store (replace with DB in production)
final Map<String, Map<String, dynamic>> _users = {
'1': {'id': '1', 'name': 'Alice', 'email': '[email protected]'},
'2': {'id': '2', 'name': 'Bob', 'email': '[email protected]'},
};
// CRUD Handlers
Response getUsers(Request req) =>
_jsonResponse({'users': _users.values.toList()});
Response getUser(Request req, String id) {
final user = _users[id];
if (user == null) {
return Response.notFound(jsonEncode({'error': 'User not found'}),
headers: {'content-type': 'application/json'});
}
return _jsonResponse(user);
}
Future<Response> createUser(Request req) async {
final body = jsonDecode(await req.readAsString()) as Map<String, dynamic>;
final id = DateTime.now().millisecondsSinceEpoch.toString();
_users[id] = {'id': id, ...body};
return _jsonResponse(_users[id]!, statusCode: 201);
}
// Helper
Response _jsonResponse(dynamic data, {int statusCode = 200}) =>
Response(statusCode,
body: jsonEncode(data),
headers: {'content-type': 'application/json'});
void main() async {
final router = Router()
..get('/users', getUsers)
..get('/users/<id>', getUser)
..post('/users', createUser);
final handler =
Pipeline().addMiddleware(logRequests()).addHandler(router);
final server = await serve(handler, 'localhost', 8080);
print('API running at http://localhost:${server.port}');
}
Database Integration
PostgreSQL with `postgres` Package
# pubspec.yaml
dependencies:
postgres: ^3.0.0
import 'package:postgres/postgres.dart';
class UserRepository {
final Connection _db;
UserRepository(this._db);
Future<List<Map<String, dynamic>>> fetchAll() async {
final result = await _db.execute('SELECT id, name, email FROM users');
return result.map((row) => {
'id': row[0],
'name': row[1],
'email': row[2],
}).toList();
}
Future<void> insert(String name, String email) async {
await _db.execute(
'INSERT INTO users (name, email) VALUES (\$1, \$2)',
parameters: [name, email],
);
}
}
// Setup connection
Future<void> main() async {
final conn = await Connection.open(
Endpoint(
host: 'localhost',
database: 'mydb',
username: 'postgres',
password: 'secret',
),
);
final repo = UserRepository(conn);
final users = await repo.fetchAll();
print(users);
await conn.close();
}
SQLite for Lightweight Use Cases
dependencies:
sqlite3: ^2.0.0
Authentication and Middleware
JWT Middleware with Shelf
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
// Middleware factory
Middleware authMiddleware(String secret) {
return (Handler innerHandler) {
return (Request request) async {
// Skip auth for public routes
if (request.url.path == 'auth/login') {
return innerHandler(request);
}
final authHeader = request.headers['authorization'];
if (authHeader == null || !authHeader.startsWith('Bearer ')) {
return Response.unauthorized(
'{"error": "Missing or invalid token"}',
headers: {'content-type': 'application/json'},
);
}
try {
final token = authHeader.substring(7);
final jwt = JWT.verify(token, SecretKey(secret));
// Attach user context to request
final updatedRequest = request.change(
context: {'userId': jwt.payload['sub']},
);
return innerHandler(updatedRequest);
} catch (e) {
return Response.unauthorized(
'{"error": "Invalid token"}',
headers: {'content-type': 'application/json'},
);
}
};
};
}
// Apply in pipeline
final handler = Pipeline()
.addMiddleware(logRequests())
.addMiddleware(authMiddleware(mySecret))
.addHandler(router);
CORS Middleware
Middleware corsMiddleware() {
return createMiddleware(
responseHandler: (response) => response.change(headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}),
);
}
Deploying Dart Servers to the Cloud
Dockerizing Your Dart Server
# Dockerfile — Multi-stage build for production
FROM dart:stable AS build
WORKDIR /app
COPY pubspec.* ./
RUN dart pub get
COPY . .
RUN dart compile exe bin/server.dart -o bin/server
# Final minimal image
FROM debian:bullseye-slim
COPY --from=build /app/bin/server /app/bin/server
EXPOSE 8080
CMD ["/app/bin/server"]
Deployment Options
- Google Cloud Run: Ideal for containerized Dart APIs. Scales to zero when idle.
- AWS Lambda via Docker: Package your compiled Dart binary in a Lambda container.
- Fly.io / Railway: Simple `fly launch` or `railway up` deploys your Dockerfile.
- VPS (DigitalOcean, Hetzner): For always-on services, compile AOT and run as a systemd service.
The business advantage?
Explore project snapshots or discuss custom web solutions.
The best code is the code you don't have to switch languages to write.
Thank You for Spending Your Valuable Time
I truly appreciate you taking the time to read blog. Your valuable time means a lot to me, and I hope you found the content insightful and engaging!
Frequently Asked Questions
Yes. `pub.dev` itself runs on a Dart backend. Serverpod has production deployments serving millions of requests. Shelf and Dart Frog are both stable and actively maintained. AOT-compiled Dart starts faster than Node.js and matches Go in many I/O-heavy benchmarks.
Dart Frog features AOT compilation (lower latency and startup time) and Dart isolates for true concurrency, versus Node's event loop model. Recent community tests confirm Dart's edge in CPU-bound tasks and native deployments. For Flutter developers, the shared codebase advantage makes Dart Frog the smarter choice.
Absolutely — that's one of Dart's biggest advantages. Create a shared `packages/common` directory with your models, validators, and business logic. Both Flutter and your Shelf server can import it. No more duplicating validation rules or data models across stacks.
Serverpod includes its own ORM. Drift (formerly Moor) is excellent for SQLite. For PostgreSQL, the `postgres` package with a lightweight repository pattern works well. Stormberry is another type-safe PostgreSQL ORM worth evaluating.
Dart's AOT-compiled executables are competitive with Go for I/O-heavy APIs and significantly outperform Node.js for CPU-bound tasks. The key advantage isn't raw performance — it's code sharing with Flutter, which is a unique architectural benefit no other language offers.
Comments are closed