internal/skills/content/shelf/SKILL.md
Shelf framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Shelf (Dart HTTP server) projects, or when the user mentions Shelf. Provides middleware patterns, request handling, pipeline composition, and server guidelines.
npx skillsauth add ar4mirez/samuel shelfInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Applies to: Shelf 1.x, Dart 3.x, REST APIs, Microservices, Backend Services Complements:
.claude/skills/dart-guide/SKILL.md
Pipeline; compose handlers with addMiddleware and addHandlerHandler is just FutureOr<Response> Function(Request) -- keep it functionalrequest.change() to pass data downstream via contextCascade to try multiple handlers in sequence until one succeedsmyapp/
├── 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 shutdownlib/src/app.dart owns the Pipeline and Router compositionRouter get routerMiddleware (a typedef for Handler Function(Handler))freezed for immutability and json_serializable for serializationdependencies:
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
// 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);
});
}
ProcessSignal.sigintapp.close() before server.close() to release database connectionsmain() under 25 linesConfig class with factory Config.fromEnvironment() reading from Platform.environmentStateError in production if critical secrets are missing (JWT_SECRET, API keys)const constructor for Config to enable compile-time checks// 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();
}
}
.call when mounting a Router or passing to addHandlerconst Pipeline() for the initial empty pipelineHandlers are classes that expose a Router get router property. Each route method receives a Request and returns a Response.
// 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'},
);
}
}
_withAuth(handler) helper to wrap individual routes with auth middlewarerequest.readAsString() then jsonDecode()Content-Type: application/json on JSON responses201 for creation, 204 for deletion, 200 for reads/updatesshelf_router path parameters with angle brackets: /<id>A Middleware is a function that takes a Handler and returns a new Handler.
// Middleware type signature
typedef Middleware = Handler Function(Handler innerHandler);
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;
};
};
}
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 (a function), not Handler directlyawait innerHandler(request) -- never skip calling the inner handler unless short-circuiting (auth failure, rate limit)request.change(context: {...request.context, 'key': value}) to pass data downstreamshelf_io.serve| Source | How | Example |
|--------|-----|---------|
| 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 params | Extra 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 |
| Status | Method |
|--------|--------|
| 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'}) |
int.tryParse and defaultsreadAsString() twicerequest.change(context:) for downstream data, never mutable globalsContent-Type on every response that has a bodyfinal 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);
..method('/path', handler) cascade syntax for readability..mount('/prefix', router.call)/<id>, /<slug>/api/v1/...Cascade tries handlers in order until one returns a non-404 response.
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 treats 404 and 405 as "not handled" by defaultstatusCodes parameter to customize which codes trigger fallthroughclass 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 (not Error)Exception('message') -- use typed exceptions# Development
dart run bin/server.dart # Start server
dart run --enable-vm-service bin/server.dart # With debugger
# Code generation (freezed, json_serializable)
dart run build_runner build --delete-conflicting-outputs
dart run build_runner watch # Watch mode for codegen
# Testing
dart test # Run all tests
dart test test/handlers/ # Run specific directory
dart test --coverage # With coverage
# Quality
dart format . # Format all files
dart analyze # Static analysis
dart fix --apply # Auto-fix lint issues
# Build
dart compile exe bin/server.dart -o server # AOT compile to native binary
Pipeline; order matters (outermost runs first)request.change(context:)shelf directly in tests (no HTTP server needed); mock services with mocktailFor detailed middleware examples, WebSocket support, static files, authentication flows, rate limiting, and testing patterns, see:
development
Zig language guardrails, patterns, and best practices for AI-assisted development. Use when working with Zig files (.zig), build.zig, or when the user mentions Zig. Provides comptime patterns, allocator conventions, C interop guidelines, and testing standards specific to this project's coding standards.
tools
WordPress framework guardrails, patterns, and best practices for AI-assisted development. Use when working with WordPress projects, or when the user mentions WordPress. Provides theme development, plugin architecture, REST API, blocks, and security guidelines.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. Use when testing web apps, automating browser interactions, or debugging frontend issues.
tools
Suite of tools for creating elaborate, multi-component web applications using modern frontend technologies (React, Tailwind CSS, shadcn/ui). Use for complex projects requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX pages.