shelf

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Shelf Framework Guide

Shelf框架指南

Applies to: Shelf 1.x, Dart 3.x, REST APIs, Microservices, Backend Services Complements:
.claude/skills/dart-guide/SKILL.md
适用范围:Shelf 1.x、Dart 3.x、REST API、微服务、后端服务 补充文档:
.claude/skills/dart-guide/SKILL.md

Core Principles

核心原则

  1. Middleware Composition: Everything flows through
    Pipeline
    ; compose handlers with
    addMiddleware
    and
    addHandler
  2. Handler Simplicity: A
    Handler
    is just
    FutureOr<Response> Function(Request)
    -- keep it functional
  3. Immutable Requests: Use
    request.change()
    to pass data downstream via
    context
  4. Cascade Routing: Use
    Cascade
    to try multiple handlers in sequence until one succeeds
  5. Separation of Concerns: Handlers call services, services call repositories -- no business logic in middleware
  1. 中间件编排:所有流程都通过
    Pipeline
    流转;使用
    addMiddleware
    addHandler
    组合处理函数
  2. 处理函数简洁性
    Handler
    本质就是
    FutureOr<Response> Function(Request)
    ,保持其函数式特性
  3. 不可变请求:使用
    request.change()
    通过
    context
    向下游传递数据
  4. 级联路由:使用
    Cascade
    按顺序尝试多个处理函数,直到有一个成功响应
  5. 关注点分离:处理函数调用服务层,服务层调用仓库层,中间件中不要包含业务逻辑

Project Structure

项目结构

myapp/
├── bin/
│   └── server.dart               # Entry point (thin: config, serve, shutdown)
├── lib/
│   ├── src/
│   │   ├── app.dart              # Pipeline + Router assembly
│   │   ├── config/
│   │   │   └── config.dart       # Environment-based configuration
│   │   ├── handlers/
│   │   │   ├── health_handler.dart
│   │   │   └── users_handler.dart
│   │   ├── middleware/
│   │   │   ├── auth_middleware.dart
│   │   │   ├── cors_middleware.dart
│   │   │   └── logging_middleware.dart
│   │   ├── models/
│   │   │   └── user.dart
│   │   ├── repositories/
│   │   │   └── user_repository.dart
│   │   └── services/
│   │       └── user_service.dart
│   └── myapp.dart                # Library barrel export
├── test/
│   ├── handlers/
│   │   └── users_handler_test.dart
│   └── middleware/
│       └── auth_middleware_test.dart
├── pubspec.yaml
├── analysis_options.yaml
└── Dockerfile
Architectural rules:
  • bin/server.dart
    is thin: load config, create handler, call
    shelf_io.serve
    , wire shutdown
  • lib/src/app.dart
    owns the Pipeline and Router composition
  • Handlers are organized by resource; each handler class exposes a
    Router get router
  • Middleware functions return
    Middleware
    (a typedef for
    Handler Function(Handler)
    )
  • Models use
    freezed
    for immutability and
    json_serializable
    for serialization
  • Services contain business logic; repositories handle data access
myapp/
├── bin/
│   └── server.dart               # 入口文件(代码精简:仅做配置、启动、关停逻辑)
├── lib/
│   ├── src/
│   │   ├── app.dart              # Pipeline + Router 组装逻辑
│   │   ├── config/
│   │   │   └── config.dart       # 基于环境变量的配置
│   │   ├── handlers/
│   │   │   ├── health_handler.dart
│   │   │   └── users_handler.dart
│   │   ├── middleware/
│   │   │   ├── auth_middleware.dart
│   │   │   ├── cors_middleware.dart
│   │   │   └── logging_middleware.dart
│   │   ├── models/
│   │   │   └── user.dart
│   │   ├── repositories/
│   │   │   └── user_repository.dart
│   │   └── services/
│   │       └── user_service.dart
│   └── myapp.dart                # 库的桶导出文件
├── test/
│   ├── handlers/
│   │   └── users_handler_test.dart
│   └── middleware/
│       └── auth_middleware_test.dart
├── pubspec.yaml
├── analysis_options.yaml
└── Dockerfile
架构规则:
  • bin/server.dart
    代码保持精简:仅加载配置、创建处理函数、调用
    shelf_io.serve
    、绑定关停逻辑
  • lib/src/app.dart
    负责Pipeline和Router的组装
  • 处理函数按资源分类组织,每个处理函数类都暴露一个
    Router get router
    属性
  • 中间件函数返回
    Middleware
    类型(是
    Handler Function(Handler)
    的类型定义)
  • 模型使用
    freezed
    实现不可变性,使用
    json_serializable
    实现序列化
  • 服务层包含业务逻辑,仓库层负责数据访问

Dependencies (pubspec.yaml)

依赖(pubspec.yaml)

yaml
dependencies:
  shelf: ^1.4.0
  shelf_router: ^1.1.0
  shelf_static: ^1.1.0        # Static file serving
  shelf_web_socket: ^1.0.0    # WebSocket support

  # Data
  freezed_annotation: ^2.4.0
  json_annotation: ^4.8.0

  # Auth
  dart_jsonwebtoken: ^2.12.0
  bcrypt: ^1.1.0

dev_dependencies:
  test: ^1.24.0
  mocktail: ^1.0.0
  build_runner: ^2.4.0
  freezed: ^2.4.0
  json_serializable: ^6.7.0
yaml
dependencies:
  shelf: ^1.4.0
  shelf_router: ^1.1.0
  shelf_static: ^1.1.0        # 静态文件服务
  shelf_web_socket: ^1.0.0    # WebSocket支持

  # 数据处理
  freezed_annotation: ^2.4.0
  json_annotation: ^4.8.0

  # 认证
  dart_jsonwebtoken: ^2.12.0
  bcrypt: ^1.1.0

dev_dependencies:
  test: ^1.24.0
  mocktail: ^1.0.0
  build_runner: ^2.4.0
  freezed: ^2.4.0
  json_serializable: ^6.7.0

Application Entry Point

应用入口点

dart
// bin/server.dart
import 'dart:io';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:myapp/myapp.dart';

Future<void> main() async {
  final config = Config.fromEnvironment();
  final app = Application(config);
  final handler = await app.createHandler();

  final server = await shelf_io.serve(
    handler,
    InternetAddress.anyIPv4,
    config.port,
  );

  print('Server running on http://${server.address.host}:${server.port}');

  // Graceful shutdown
  ProcessSignal.sigint.watch().listen((_) async {
    print('Shutting down...');
    await app.close();
    await server.close();
    exit(0);
  });
}
dart
// bin/server.dart
import 'dart:io';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:myapp/myapp.dart';

Future<void> main() async {
  final config = Config.fromEnvironment();
  final app = Application(config);
  final handler = await app.createHandler();

  final server = await shelf_io.serve(
    handler,
    InternetAddress.anyIPv4,
    config.port,
  );

  print('Server running on http://${server.address.host}:${server.port}');

  // 优雅关停
  ProcessSignal.sigint.watch().listen((_) async {
    print('Shutting down...');
    await app.close();
    await server.close();
    exit(0);
  });
}

Entry Point Rules

入口点规则

  • Load configuration from environment variables (never hardcode secrets)
  • Wire graceful shutdown via
    ProcessSignal.sigint
  • Call
    app.close()
    before
    server.close()
    to release database connections
  • Keep
    main()
    under 25 lines
  • 从环境变量加载配置(绝对不要硬编码密钥)
  • 通过
    ProcessSignal.sigint
    绑定优雅关停逻辑
  • 在调用
    server.close()
    前先调用
    app.close()
    释放数据库连接
  • 保持
    main()
    函数代码行数不超过25行

Configuration

配置

  • Use a
    Config
    class with
    factory Config.fromEnvironment()
    reading from
    Platform.environment
  • Provide dev defaults for non-secret values (port, database URL)
  • Throw
    StateError
    in production if critical secrets are missing (JWT_SECRET, API keys)
  • Use
    const
    constructor for Config to enable compile-time checks
  • Never log secret values
  • 使用
    Config
    类,通过
    factory Config.fromEnvironment()
    Platform.environment
    读取配置
  • 为非敏感配置提供开发环境默认值(端口、数据库URL)
  • 生产环境下如果关键密钥缺失(JWT_SECRET、API密钥)要抛出
    StateError
  • 为Config类使用
    const
    构造函数以支持编译时检查
  • 绝对不要打印日志输出敏感配置值

Application Setup (Pipeline + Router)

应用初始化(Pipeline + Router)

dart
// lib/src/app.dart
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';

class Application {
  final Config config;
  late final UserRepository _userRepository;
  late final UserService _userService;

  Application(this.config);

  Future<Handler> createHandler() async {
    _userRepository = UserRepository();
    _userService = UserService(_userRepository, config);

    final healthHandler = HealthHandler();
    final usersHandler = UsersHandler(_userService);

    final router = Router()
      ..mount('/health', healthHandler.router.call)
      ..mount('/api/v1/users', usersHandler.router.call);

    final pipeline = const Pipeline()
        .addMiddleware(loggingMiddleware())
        .addMiddleware(corsMiddleware())
        .addMiddleware(handleErrors())
        .addHandler(router.call);

    return pipeline;
  }

  Future<void> close() async {
    await _userRepository.close();
  }
}
dart
// lib/src/app.dart
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';

class Application {
  final Config config;
  late final UserRepository _userRepository;
  late final UserService _userService;

  Application(this.config);

  Future<Handler> createHandler() async {
    _userRepository = UserRepository();
    _userService = UserService(_userRepository, config);

    final healthHandler = HealthHandler();
    final usersHandler = UsersHandler(_userService);

    final router = Router()
      ..mount('/health', healthHandler.router.call)
      ..mount('/api/v1/users', usersHandler.router.call);

    final pipeline = const Pipeline()
        .addMiddleware(loggingMiddleware())
        .addMiddleware(corsMiddleware())
        .addMiddleware(handleErrors())
        .addHandler(router.call);

    return pipeline;
  }

  Future<void> close() async {
    await _userRepository.close();
  }
}

Pipeline Rules

Pipeline规则

  • Middleware order: logging -> CORS -> error handling -> router
  • Logging outermost so it captures all requests including CORS preflight
  • Error handling wraps the router so thrown exceptions are caught
  • Always call
    .call
    when mounting a
    Router
    or passing to
    addHandler
  • Use
    const Pipeline()
    for the initial empty pipeline
  • 中间件顺序:日志 -> CORS -> 错误处理 -> 路由
  • 日志放在最外层,确保可以捕获所有请求包括CORS预检请求
  • 错误处理包裹路由,确保抛出的异常都能被捕获
  • 挂载
    Router
    或者传递给
    addHandler
    时必须调用
    .call
    方法
  • 初始化空管道时使用
    const Pipeline()

Handlers

处理函数(Handlers)

Handlers are classes that expose a
Router get router
property. Each route method receives a
Request
and returns a
Response
.
dart
// lib/src/handlers/users_handler.dart
class UsersHandler {
  final UserService _userService;

  UsersHandler(this._userService);

  Router get router {
    final router = Router();

    // Public routes
    router.post('/register', _register);
    router.post('/login', _login);

    // Protected routes
    router.get('/', _withAuth(_getAll));
    router.get('/<id>', _withAuth(_getById));
    router.put('/<id>', _withAuth(_update));
    router.delete('/<id>', _withAuth(_delete));

    return router;
  }

  Handler _withAuth(Handler handler) {
    return const Pipeline()
        .addMiddleware(authMiddleware())
        .addHandler(handler);
  }

  Future<Response> _register(Request request) async {
    final body = await request.readAsString();
    final json = jsonDecode(body) as Map<String, dynamic>;

    final user = await _userService.register(
      email: json['email'] as String,
      password: json['password'] as String,
      name: json['name'] as String,
    );

    return Response(
      201,
      body: jsonEncode(user.toJson()),
      headers: {'Content-Type': 'application/json'},
    );
  }
}
处理函数是暴露
Router get router
属性的类,每个路由方法接收
Request
并返回
Response
dart
// lib/src/handlers/users_handler.dart
class UsersHandler {
  final UserService _userService;

  UsersHandler(this._userService);

  Router get router {
    final router = Router();

    // 公开路由
    router.post('/register', _register);
    router.post('/login', _login);

    // 受保护路由
    router.get('/', _withAuth(_getAll));
    router.get('/<id>', _withAuth(_getById));
    router.put('/<id>', _withAuth(_update));
    router.delete('/<id>', _withAuth(_delete));

    return router;
  }

  Handler _withAuth(Handler handler) {
    return const Pipeline()
        .addMiddleware(authMiddleware())
        .addHandler(handler);
  }

  Future<Response> _register(Request request) async {
    final body = await request.readAsString();
    final json = jsonDecode(body) as Map<String, dynamic>;

    final user = await _userService.register(
      email: json['email'] as String,
      password: json['password'] as String,
      name: json['name'] as String,
    );

    return Response(
      201,
      body: jsonEncode(user.toJson()),
      headers: {'Content-Type': 'application/json'},
    );
  }
}

Handler Rules

处理函数规则

  • One handler class per resource (UsersHandler, ProductsHandler)
  • Keep handler methods under 20 lines; delegate business logic to services
  • Use
    _withAuth(handler)
    helper to wrap individual routes with auth middleware
  • Parse request body with
    request.readAsString()
    then
    jsonDecode()
  • Always set
    Content-Type: application/json
    on JSON responses
  • Return appropriate status codes:
    201
    for creation,
    204
    for deletion,
    200
    for reads/updates
  • Use
    shelf_router
    path parameters with angle brackets:
    /<id>
  • 每个资源对应一个处理函数类(UsersHandler、ProductsHandler)
  • 处理函数方法代码行数不超过20行,业务逻辑全部委托给服务层
  • 使用
    _withAuth(handler)
    辅助方法为单个路由包裹认证中间件
  • 使用
    request.readAsString()
    读取请求体,再通过
    jsonDecode()
    解析
  • JSON响应必须设置
    Content-Type: application/json
  • 返回合适的状态码:创建成功返回
    201
    ,删除成功返回
    204
    ,查询/更新成功返回
    200
  • 使用
    shelf_router
    的尖括号语法定义路径参数:
    /<id>

Middleware

中间件(Middleware)

A
Middleware
is a function that takes a
Handler
and returns a new
Handler
.
dart
// Middleware type signature
typedef Middleware = Handler Function(Handler innerHandler);
Middleware
是接收
Handler
并返回新
Handler
的函数。
dart
// Middleware类型签名
typedef Middleware = Handler Function(Handler innerHandler);

Writing Middleware

编写中间件

dart
Middleware loggingMiddleware() {
  return (Handler innerHandler) {
    return (Request request) async {
      final stopwatch = Stopwatch()..start();
      print('[${DateTime.now()}] ${request.method} ${request.requestedUri}');

      final response = await innerHandler(request);

      stopwatch.stop();
      print(
        '[${DateTime.now()}] ${request.method} ${request.requestedUri} '
        '${response.statusCode} ${stopwatch.elapsedMilliseconds}ms',
      );

      return response;
    };
  };
}
dart
Middleware loggingMiddleware() {
  return (Handler innerHandler) {
    return (Request request) async {
      final stopwatch = Stopwatch()..start();
      print('[${DateTime.now()}] ${request.method} ${request.requestedUri}');

      final response = await innerHandler(request);

      stopwatch.stop();
      print(
        '[${DateTime.now()}] ${request.method} ${request.requestedUri} '
        '${response.statusCode} ${stopwatch.elapsedMilliseconds}ms',
      );

      return response;
    };
  };
}

Error Handling Middleware

错误处理中间件

dart
Middleware handleErrors() {
  return (Handler innerHandler) {
    return (Request request) async {
      try {
        return await innerHandler(request);
      } on NotFoundException catch (e) {
        return _jsonError(404, e.message);
      } on ValidationException catch (e) {
        return _jsonError(422, e.message, errors: e.errors);
      } on UnauthorizedException {
        return _jsonError(403, 'Unauthorized');
      } catch (e, stack) {
        print('Error: $e\n$stack');
        return _jsonError(500, 'Internal server error');
      }
    };
  };
}

Response _jsonError(int status, String message, {Map<String, dynamic>? errors}) {
  return Response(
    status,
    body: jsonEncode({
      'error': message,
      if (errors != null) 'errors': errors,
    }),
    headers: {'Content-Type': 'application/json'},
  );
}
dart
Middleware handleErrors() {
  return (Handler innerHandler) {
    return (Request request) async {
      try {
        return await innerHandler(request);
      } on NotFoundException catch (e) {
        return _jsonError(404, e.message);
      } on ValidationException catch (e) {
        return _jsonError(422, e.message, errors: e.errors);
      } on UnauthorizedException {
        return _jsonError(403, 'Unauthorized');
      } catch (e, stack) {
        print('Error: $e\n$stack');
        return _jsonError(500, 'Internal server error');
      }
    };
  };
}

Response _jsonError(int status, String message, {Map<String, dynamic>? errors}) {
  return Response(
    status,
    body: jsonEncode({
      'error': message,
      if (errors != null) 'errors': errors,
    }),
    headers: {'Content-Type': 'application/json'},
  );
}

Middleware Rules

中间件规则

  • Return
    Middleware
    (a function), not
    Handler
    directly
  • Always
    await innerHandler(request)
    -- never skip calling the inner handler unless short-circuiting (auth failure, rate limit)
  • Use
    request.change(context: {...request.context, 'key': value})
    to pass data downstream
  • Error-handling middleware must catch all exceptions and return proper HTTP responses
  • Never let unhandled exceptions propagate to
    shelf_io.serve
  • 返回
    Middleware
    (函数类型),不要直接返回
    Handler
  • 始终
    await innerHandler(request)
    ,除非需要短路返回(认证失败、限流)否则不要跳过调用内部处理函数
  • 使用
    request.change(context: {...request.context, 'key': value})
    向下游传递数据
  • 错误处理中间件必须捕获所有异常,返回正确的HTTP响应
  • 绝对不要让未处理的异常传播到
    shelf_io.serve

Request and Response

请求与响应

Reading Request Data

读取请求数据

SourceHowExample
Body
await request.readAsString()
then
jsonDecode()
final json = jsonDecode(await request.readAsString())
Query params
request.url.queryParameters['key']
int.tryParse(request.url.queryParameters['page'] ?? '1') ?? 1
Path paramsExtra function arguments (shelf_router)
Future<Response> _getById(Request request, String id)
Headers
request.headers['Header-Name']
request.headers['Authorization']
Context
request.context['key']
request.context['user'] as User
来源获取方式示例
请求体
await request.readAsString()
后调用
jsonDecode()
final json = jsonDecode(await request.readAsString())
查询参数
request.url.queryParameters['key']
int.tryParse(request.url.queryParameters['page'] ?? '1') ?? 1
路径参数额外的函数参数(shelf_router提供)
Future<Response> _getById(Request request, String id)
请求头
request.headers['Header-Name']
request.headers['Authorization']
上下文
request.context['key']
request.context['user'] as User

Building Responses

构建响应

StatusMethod
200 OK
Response.ok(jsonEncode(data), headers: {'Content-Type': 'application/json'})
201 Created
Response(201, body: jsonEncode(data), headers: {'Content-Type': 'application/json'})
204 No Content
Response(204)
404 Not Found
Response.notFound(jsonEncode({'error': 'Not found'}), headers: {'Content-Type': 'application/json'})
Modify response
response.change(headers: {...response.headers, 'X-Custom': 'value'})
状态码用法
200 OK
Response.ok(jsonEncode(data), headers: {'Content-Type': 'application/json'})
201 Created
Response(201, body: jsonEncode(data), headers: {'Content-Type': 'application/json'})
204 No Content
Response(204)
404 Not Found
Response.notFound(jsonEncode({'error': 'Not found'}), headers: {'Content-Type': 'application/json'})
修改响应
response.change(headers: {...response.headers, 'X-Custom': 'value'})

Request/Response Rules

请求/响应规则

  • Parse query parameters defensively with
    int.tryParse
    and defaults
  • Read the body only once (it is a stream); do not call
    readAsString()
    twice
  • Use
    request.change(context:)
    for downstream data, never mutable globals
  • Set
    Content-Type
    on every response that has a body
  • 解析查询参数时做好防御性处理,使用
    int.tryParse
    和默认值
  • 请求体只能读取一次(是流类型),不要重复调用
    readAsString()
  • 使用
    request.change(context:)
    向下游传递数据,绝对不要使用可变全局变量
  • 所有带响应体的返回都要设置
    Content-Type

Routing with shelf_router

使用shelf_router实现路由

dart
final router = Router()
  ..get('/health', _health)
  ..get('/users', _listUsers)
  ..get('/users/<id>', _getUser)
  ..post('/users', _createUser)
  ..put('/users/<id>', _updateUser)
  ..delete('/users/<id>', _deleteUser);

// Mount sub-routers with path prefix
final root = Router()
  ..mount('/api/v1', apiRouter.call)
  ..mount('/ws', webSocketHandler);
dart
final router = Router()
  ..get('/health', _health)
  ..get('/users', _listUsers)
  ..get('/users/<id>', _getUser)
  ..post('/users', _createUser)
  ..put('/users/<id>', _updateUser)
  ..delete('/users/<id>', _deleteUser);

// 带路径前缀挂载子路由
final root = Router()
  ..mount('/api/v1', apiRouter.call)
  ..mount('/ws', webSocketHandler);

Routing Rules

路由规则

  • Use
    ..method('/path', handler)
    cascade syntax for readability
  • Mount sub-routers with
    ..mount('/prefix', router.call)
  • Path parameters use angle brackets:
    /<id>
    ,
    /<slug>
  • Always version API routes:
    /api/v1/...
  • Group related routes in a single handler class
  • 使用
    ..method('/path', handler)
    级联语法提升可读性
  • 使用
    ..mount('/prefix', router.call)
    挂载子路由
  • 路径参数使用尖括号定义:
    /<id>
    /<slug>
  • API路由必须加版本号:
    /api/v1/...
  • 相关路由分组到同一个处理函数类中

Cascade (Fallback Routing)

Cascade(降级路由)

Cascade
tries handlers in order until one returns a non-404 response.
dart
import 'package:shelf/shelf.dart';
import 'package:shelf_static/shelf_static.dart';

final cascade = Cascade()
    .add(apiRouter)
    .add(createStaticHandler('public', defaultDocument: 'index.html'));

final handler = const Pipeline()
    .addMiddleware(loggingMiddleware())
    .addHandler(cascade.handler);
Cascade
会按顺序尝试处理函数,直到有一个返回非404响应。
dart
import 'package:shelf/shelf.dart';
import 'package:shelf_static/shelf_static.dart';

final cascade = Cascade()
    .add(apiRouter)
    .add(createStaticHandler('public', defaultDocument: 'index.html'));

final handler = const Pipeline()
    .addMiddleware(loggingMiddleware())
    .addHandler(cascade.handler);

Cascade Rules

Cascade规则

  • Place specific handlers (API) before generic handlers (static files)
  • Cascade
    treats 404 and 405 as "not handled" by default
  • Use
    statusCodes
    parameter to customize which codes trigger fallthrough
  • Useful for SPAs: API routes first, then static file handler as fallback
  • 具体的处理函数(API)放在通用处理函数(静态文件)前面
  • 默认情况下
    Cascade
    会将404和405视为“未处理”,触发下一个处理函数
  • 使用
    statusCodes
    参数自定义触发降级的状态码
  • 非常适合SPA场景:先匹配API路由,静态文件处理作为降级兜底

Custom Exception Types

自定义异常类型

dart
class UnauthorizedException implements Exception {
  final String message;
  UnauthorizedException([this.message = 'Unauthorized']);
}

class NotFoundException implements Exception {
  final String message;
  NotFoundException(this.message);
}

class ValidationException implements Exception {
  final String message;
  final Map<String, List<String>> errors;
  ValidationException(this.message, [this.errors = const {}]);
}
dart
class UnauthorizedException implements Exception {
  final String message;
  UnauthorizedException([this.message = 'Unauthorized']);
}

class NotFoundException implements Exception {
  final String message;
  NotFoundException(this.message);
}

class ValidationException implements Exception {
  final String message;
  final Map<String, List<String>> errors;
  ValidationException(this.message, [this.errors = const {}]);
}

Exception Rules

异常规则

  • Define domain exceptions that implement
    Exception
    (not
    Error
    )
  • Map exceptions to HTTP status codes in error-handling middleware only
  • Never throw generic
    Exception('message')
    -- use typed exceptions
  • Include structured error details (field-level validation errors)
  • 定义实现
    Exception
    (不是
    Error
    )的领域异常
  • 仅在错误处理中间件中将异常映射为HTTP状态码
  • 不要抛出泛型
    Exception('message')
    ,使用带类型的异常
  • 包含结构化的错误详情(字段级别的校验错误)

Commands

常用命令

bash
undefined
bash
undefined

Development

开发

dart run bin/server.dart # Start server dart run --enable-vm-service bin/server.dart # With debugger
dart run bin/server.dart # 启动服务 dart run --enable-vm-service bin/server.dart # 带调试器启动

Code generation (freezed, json_serializable)

代码生成(freezed、json_serializable)

dart run build_runner build --delete-conflicting-outputs dart run build_runner watch # Watch mode for codegen
dart run build_runner build --delete-conflicting-outputs dart run build_runner watch # 监听模式自动生成代码

Testing

测试

dart test # Run all tests dart test test/handlers/ # Run specific directory dart test --coverage # With coverage
dart test # 运行所有测试 dart test test/handlers/ # 运行指定目录的测试 dart test --coverage # 生成覆盖率报告

Quality

代码质量

dart format . # Format all files dart analyze # Static analysis dart fix --apply # Auto-fix lint issues
dart format . # 格式化所有文件 dart analyze # 静态代码分析 dart fix --apply # 自动修复lint问题

Build

构建

dart compile exe bin/server.dart -o server # AOT compile to native binary
undefined
dart compile exe bin/server.dart -o server # AOT编译为原生二进制文件
undefined

Best Practices Summary

最佳实践总结

  • Pipeline: Compose middleware with
    Pipeline
    ; order matters (outermost runs first)
  • Handlers: Keep thin; delegate to services; one class per resource
  • Error Handling: Centralize in middleware; catch all exceptions; return structured JSON errors
  • Context: Pass data between middleware and handlers via
    request.change(context:)
  • Testing: Use
    shelf
    directly in tests (no HTTP server needed); mock services with
    mocktail
  • Security: Validate all inputs; use parameterized database queries; never log secrets
  • Performance: Compile to native executable for production; use streaming for large responses
  • Pipeline:使用
    Pipeline
    编排中间件,顺序很重要(最外层的中间件最先执行)
  • 处理函数:保持精简,逻辑委托给服务层,每个资源对应一个类
  • 错误处理:在中间件中集中处理,捕获所有异常,返回结构化的JSON错误
  • 上下文:通过
    request.change(context:)
    在中间件和处理函数之间传递数据
  • 测试:测试中直接使用
    shelf
    的API(不需要启动HTTP服务),使用
    mocktail
    模拟服务层
  • 安全:校验所有输入,使用参数化数据库查询,绝对不要打印日志输出密钥
  • 性能:生产环境编译为原生可执行文件,大响应使用流式返回

Advanced Topics

高级主题

For detailed middleware examples, WebSocket support, static files, authentication flows, rate limiting, and testing patterns, see:
  • references/patterns.md -- Full middleware patterns, WebSocket handler, static file serving, JWT auth, rate limiting, repository pattern, integration testing, Dockerfile
如需详细的中间件示例、WebSocket支持、静态文件、认证流程、限流、测试模式,请参考:
  • references/patterns.md -- 完整的中间件模式、WebSocket处理函数、静态文件服务、JWT认证、限流、仓库模式、集成测试、Dockerfile

External References

外部参考