Skip to content

Développement Backend avec Dart

Le développement backend avec Dart offre plusieurs frameworks et outils puissants pour créer des serveurs robustes et performants :

  • Shelf - Framework minimal et composable
  • DartFrog - Framework web moderne et rapide
  • Serverpod - Framework backend complet avec ORM
  • Celest - Plateforme cloud native pour Dart

Shelf est un framework web minimal et composable pour Dart. Il utilise une architecture basée sur les middlewares et fournit une API simple pour gérer les requêtes HTTP.

Le package shelf_router permet de définir facilement des routes et des gestionnaires pour votre application web.

import 'package:shelf_router/shelf_router.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
var app = Router();
app.get('/hello', (Request request) {
return Response.ok('hello-world');
});
app.get('/user/<user>', (Request request, String user) {
return Response.ok('hello $user');
});
var server = await io.serve(app, 'localhost', 8080);

Shelf propose également un générateur de code pour créer des routes de manière déclarative avec des annotations :

import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
part 'userservice.g.dart'; // generated with 'pub run build_runner build'
class UserService {
final DatabaseConnection connection;
UserService(this.connection);
@Route.get('/users/')
Future<Response> listUsers(Request request) async {
return Response.ok('["user1"]');
}
@Route.get('/users/<userId>')
Future<Response> fetchUser(Request request, String userId) async {
if (userId == 'user1') {
return Response.ok('user1');
}
return Response.notFound('no such user');
}
// Create router using the generate function defined in 'userservice.g.dart'.
Router get router => _$UserServiceRouter(this);
}
void main() async {
// You can setup context, database connections, cache connections, email
// services, before you create an instance of your service.
var connection = await DatabaseConnection.connect('localhost:1234');
// Create an instance of your service, using one of the constructors you've
// defined.
var service = UserService(connection);
// Service request using the router, note the router can also be mounted.
var router = service.router;
var server = await io.serve(router.handler, 'localhost', 8080);
}

DartFrog est un framework web moderne développé par Very Good Ventures. Il se concentre sur la performance, la simplicité et la productivité des développeurs.

Caractéristiques principales :

  • Routage basé sur les fichiers
  • Middlewares intégrés
  • Support du hot reload
  • Génération automatique de documentation API
  • Performance optimisée

Serverpod est un framework backend complet qui inclut :

Fonctionnalités :

  • ORM intégré avec génération automatique de code
  • Authentification et autorisation
  • Cache Redis intégré
  • Support des WebSockets
  • Monitoring et logging avancés
  • Déploiement cloud facilité

Avantages :

  • Type safety de bout en bout
  • Génération automatique du client
  • Scaling horizontal simple
  • Intégration native avec PostgreSQL

Celest est une plateforme cloud native spécialement conçue pour les applications Dart et Flutter.

Fonctionnalités :

  • Déploiement serverless automatique
  • Authentification intégrée
  • Base de données managed
  • APIs générées automatiquement
  • Intégration CI/CD
// Couche de données (Repository Pattern)
abstract class UserRepository {
Future<User?> findById(String id);
Future<List<User>> findAll();
Future<void> save(User user);
Future<void> delete(String id);
}
class DatabaseUserRepository implements UserRepository {
final DatabaseConnection db;
DatabaseUserRepository(this.db);
@override
Future<User?> findById(String id) async {
// Implémentation base de données
}
// ... autres méthodes
}
// Couche service (Business Logic)
class UserService {
final UserRepository repository;
UserService(this.repository);
Future<User?> getUserById(String id) async {
return await repository.findById(id);
}
Future<void> createUser(CreateUserRequest request) async {
// Validation métier
final user = User(
id: generateId(),
name: request.name,
email: request.email,
);
await repository.save(user);
}
}
// Couche présentation (Controllers)
class UserController {
final UserService userService;
UserController(this.userService);
@Route.get('/users/<id>')
Future<Response> getUser(Request request, String id) async {
try {
final user = await userService.getUserById(id);
if (user == null) {
return Response.notFound('User not found');
}
return Response.ok(jsonEncode(user.toJson()));
} catch (e) {
return Response.internalServerError(body: 'Server error');
}
}
}
// Container DI simple
class DIContainer {
final Map<Type, dynamic> _services = {};
void register<T>(T service) {
_services[T] = service;
}
T get<T>() {
final service = _services[T];
if (service == null) {
throw Exception('Service $T not registered');
}
return service as T;
}
}
// Configuration des dépendances
void setupDependencies(DIContainer container) {
container.register<DatabaseConnection>(
DatabaseConnection.create('localhost:5432')
);
container.register<UserRepository>(
DatabaseUserRepository(container.get<DatabaseConnection>())
);
container.register<UserService>(
UserService(container.get<UserRepository>())
);
}
// Middleware d'authentification
Middleware authMiddleware() {
return (Handler innerHandler) {
return (Request request) async {
final authHeader = request.headers['authorization'];
if (authHeader == null || !isValidToken(authHeader)) {
return Response.forbidden('Access denied');
}
// Ajouter l'utilisateur au contexte de la requête
final updatedRequest = request.change(
context: {'user': getUserFromToken(authHeader)}
);
return await innerHandler(updatedRequest);
};
};
}
// Middleware de logging
Middleware loggingMiddleware() {
return logRequests(logger: (message, isError) {
if (isError) {
print('ERROR: $message');
} else {
print('INFO: $message');
}
});
}
// Utilisation des middlewares
final handler = Pipeline()
.addMiddleware(corsHeaders())
.addMiddleware(loggingMiddleware())
.addMiddleware(authMiddleware())
.addHandler(router);
// Result pattern pour la gestion d'erreurs
abstract class Result<T> {
const Result();
}
class Success<T> extends Result<T> {
final T value;
const Success(this.value);
}
class Failure<T> extends Result<T> {
final String error;
final int? code;
const Failure(this.error, [this.code]);
}
// Utilisation dans les services
class UserService {
Future<Result<User>> createUser(CreateUserRequest request) async {
try {
// Validation
if (request.email.isEmpty) {
return const Failure('Email is required', 400);
}
// Logique métier
final user = User(
id: generateId(),
email: request.email,
name: request.name,
);
await repository.save(user);
return Success(user);
} catch (e) {
return Failure('Failed to create user: $e', 500);
}
}
}
// Utilisation dans les controllers
@Route.post('/users')
Future<Response> createUser(Request request, String body) async {
final createRequest = CreateUserRequest.fromJson(jsonDecode(body));
final result = await userService.createUser(createRequest);
return switch (result) {
Success(:final value) => Response.ok(jsonEncode(value.toJson())),
Failure(:final error, :final code) => Response(
code ?? 500,
body: jsonEncode({'error': error}),
),
};
}
lib/
├── controllers/ # Couche présentation
│ ├── user_controller.dart
│ └── auth_controller.dart
├── services/ # Logique métier
│ ├── user_service.dart
│ └── auth_service.dart
├── repositories/ # Couche données
│ ├── user_repository.dart
│ └── interfaces/
├── models/ # Modèles de données
│ ├── user.dart
│ └── auth_token.dart
├── middleware/ # Middlewares personnalisés
│ ├── auth_middleware.dart
│ └── cors_middleware.dart
├── config/ # Configuration
│ ├── database.dart
│ └── app_config.dart
└── main.dart # Point d'entrée

Configuration et Variables d’Environnement

Section titled “Configuration et Variables d’Environnement”
class AppConfig {
static String get databaseUrl =>
Platform.environment['DATABASE_URL'] ?? 'localhost:5432';
static String get jwtSecret =>
Platform.environment['JWT_SECRET'] ?? 'dev-secret';
static int get port =>
int.parse(Platform.environment['PORT'] ?? '8080');
static bool get isDevelopment =>
Platform.environment['ENVIRONMENT'] == 'development';
}
// Test d'intégration
void main() {
group('User API', () {
late TestServer server;
setUp(() async {
server = await TestServer.start();
});
tearDown(() async {
await server.stop();
});
test('should create user', () async {
final response = await server.post(
'/users',
body: jsonEncode({
'name': 'John Doe',
'email': 'john@example.com',
}),
);
expect(response.statusCode, 201);
final user = jsonDecode(response.body);
expect(user['name'], 'John Doe');
});
});
}

Ces frameworks et patterns permettent de construire des backends Dart robustes, maintenables et performants, que ce soit pour des APIs REST, des applications en temps réel ou des microservices.