internal/skills/content/dart-frog/SKILL.md
Dart Frog framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Dart Frog projects, or when the user mentions Dart Frog. Provides file-based routing, middleware, dependency injection, and server-side Dart guidelines.
npx skillsauth add ar4mirez/samuel dart-frogInstall 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: Dart Frog 1.x, Dart 3.x, REST APIs, Full-Stack Dart, Serverless Functions
Dart Frog is a fast, minimalistic backend framework for Dart built on top of Shelf. It provides file-based routing (similar to Next.js), built-in middleware support, dependency injection via providers, hot reload during development, and easy deployment with Docker. Dart Frog pairs naturally with Flutter for full-stack Dart applications.
_middleware.dart files apply to all routes in their directory subtreeprovider<T>() and context.read<T>() for clean service accessdart_frog dev watches for changes automaticallyroutes/lib/src/lib/<project_name>.darttest/routes/ to match routes/ layoutdart format . before every commitdart analyze and fix all warnings before committingvery_good_analysis lint rules (or lints package at minimum)*.g.dart, *.freezed.dart) from analysisAppException{"error": "<message>"}dart:io HttpStatus constants instead of magic status code numbersConfig class*)myapp/
routes/
_middleware.dart # Global middleware (logging, CORS, error handler, DI)
index.dart # GET /
health.dart # GET /health
api/
_middleware.dart # JSON content-type header
v1/
_middleware.dart # Auth provider
users/
_middleware.dart # Require authentication
index.dart # GET|POST /api/v1/users
[id].dart # GET|PUT|DELETE /api/v1/users/:id
posts/
index.dart
[id].dart
auth/
login.dart # POST /api/v1/auth/login
register.dart # POST /api/v1/auth/register
ws.dart # WebSocket endpoint
lib/
myapp.dart # Barrel export
src/
models/
user.dart
repositories/
user_repository.dart
services/
user_service.dart
middleware/
auth_provider.dart
exceptions.dart
test/
routes/
api/v1/users/
index_test.dart
pubspec.yaml
Dockerfile
| URL Path | File |
|---|---|
| / | routes/index.dart |
| /health | routes/health.dart |
| /api/v1/users | routes/api/v1/users/index.dart |
| /api/v1/users/:id | routes/api/v1/users/[id].dart |
| /api/v1/users/:uid/posts/:pid | routes/api/v1/users/[uid]/posts/[pid].dart |
Every route file must export a top-level onRequest function:
// Synchronous handler (no async work)
Response onRequest(RequestContext context) { ... }
// Async handler
Future<Response> onRequest(RequestContext context) async { ... }
// Dynamic route — parameters are positional String arguments
Future<Response> onRequest(RequestContext context, String id) async { ... }
// Nested dynamic route — one String per segment
Future<Response> onRequest(
RequestContext context,
String userId,
String postId,
) async { ... }
Use a switch on context.request.method to dispatch by HTTP verb:
Future<Response> onRequest(RequestContext context) async {
return switch (context.request.method) {
HttpMethod.get => _handleGet(context),
HttpMethod.post => _handlePost(context),
_ => Future.value(Response(statusCode: HttpStatus.methodNotAllowed)),
};
}
Always return 405 Method Not Allowed for unsupported verbs.
// Query parameters
final page = int.tryParse(
context.request.uri.queryParameters['page'] ?? '1',
) ?? 1;
// JSON body
final body = await context.request.json() as Map<String, dynamic>;
// Headers
final auth = context.request.headers['Authorization'];
Read any provider-registered dependency:
final userService = context.read<UserService>();
final config = context.read<Config>();
final currentUser = context.read<User?>(); // nullable for optional auth
// JSON with default 200
Response.json({'message': 'OK'});
// JSON with explicit status
Response.json(user.toJson(), statusCode: HttpStatus.created);
// No content
Response(statusCode: HttpStatus.noContent);
// Custom headers
response.copyWith(headers: {...response.headers, 'X-Custom': 'value'});
Middleware files are named _middleware.dart and apply to every route in the same directory
and all subdirectories. They execute from outermost to innermost (root first).
Handler middleware(Handler handler) {
return handler
.use(someMiddleware())
.use(provider<SomeType>((context) => SomeType()));
}
routes/_middleware.dart -> runs first (global)
routes/api/_middleware.dart -> runs second
routes/api/v1/_middleware.dart -> runs third (e.g., auth provider)
routes/api/v1/users/_middleware.dart -> runs last (e.g., require auth)
routes/api/v1/users/index.dart -> handler
A Middleware is a function Handler Function(Handler):
Middleware myMiddleware() {
return (handler) {
return (context) async {
// Before handler
final response = await handler(context);
// After handler
return response;
};
};
}
Access-Control-Allow-* headers, handle OPTIONS preflightContent-Type: application/json to responsesAuthorization header, provide User?context.read<User?>(), return 401 if nullDart Frog uses the provider<T>() middleware to register dependencies:
Handler middleware(Handler handler) {
return handler
.use(provider<Config>((_) => Config.fromEnvironment()))
.use(provider<Database>((ctx) => Database(ctx.read<Config>().dbUrl)))
.use(provider<UserRepository>((ctx) =>
UserRepositoryImpl(ctx.read<Database>())))
.use(provider<UserService>((ctx) => UserService(
ctx.read<UserRepository>(),
ctx.read<Config>(),
)));
}
context.read<T>()User?) for optional dependencies like authenticated userclass AppException implements Exception {
final String message;
const AppException(this.message);
}
class ValidationException extends AppException {
final Map<String, List<String>> errors;
const ValidationException(super.message, [this.errors = const {}]);
}
class NotFoundException extends AppException {
const NotFoundException(super.message);
}
class UnauthorizedException extends AppException {
const UnauthorizedException([super.message = 'Unauthorized']);
}
class ForbiddenException extends AppException {
const ForbiddenException([super.message = 'Forbidden']);
}
Middleware errorHandler() {
return (handler) {
return (context) async {
try {
return await handler(context);
} on ValidationException catch (e) {
return Response.json(
{'error': e.message, 'errors': e.errors},
statusCode: HttpStatus.unprocessableEntity,
);
} on NotFoundException catch (e) {
return Response.json(
{'error': e.message}, statusCode: HttpStatus.notFound);
} on UnauthorizedException catch (e) {
return Response.json(
{'error': e.message}, statusCode: HttpStatus.unauthorized);
} on ForbiddenException catch (e) {
return Response.json(
{'error': e.message}, statusCode: HttpStatus.forbidden);
} catch (e, stack) {
print('Unhandled: $e\n$stack');
return Response.json(
{'error': 'Internal server error'},
statusCode: HttpStatus.internalServerError,
);
}
};
};
}
Use freezed + json_serializable for immutable, serializable models:
@freezed
class User with _$User {
const User._();
const factory User({
required String id,
required String email,
required String name,
@Default(false) bool isAdmin,
@JsonKey(includeToJson: false) String? passwordHash,
required DateTime createdAt,
DateTime? updatedAt,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
Run code generation after model changes:
dart run build_runner build --delete-conflicting-outputs
# Scaffold a new project
dart_frog create myapp
# Development server with hot reload
dart_frog dev
# Production build
dart_frog build
# Run the compiled server
./build/bin/server
# Generate a new route file
dart_frog new route /api/v1/users
# Generate a new middleware file
dart_frog new middleware auth
# Run tests
dart test
# Code generation (freezed, json_serializable)
dart run build_runner build --delete-conflicting-outputs
# Format and analyze
dart format .
dart analyze
Use mocktail to mock RequestContext and injected services:
class MockRequestContext extends Mock implements RequestContext {}
class MockUserService extends Mock implements UserService {}
void main() {
late MockRequestContext context;
late MockUserService userService;
setUp(() {
context = MockRequestContext();
userService = MockUserService();
when(() => context.read<UserService>()).thenReturn(userService);
});
test('GET returns users list', () async {
when(() => userService.getAll(page: any(named: 'page'),
limit: any(named: 'limit')))
.thenAnswer((_) async => [mockUser]);
when(() => context.request).thenReturn(
Request.get(Uri.parse('http://localhost/api/v1/users')),
);
final response = await route.onRequest(context);
expect(response.statusCode, equals(HttpStatus.ok));
});
}
routes/api/v1/users/index.dart -> test/routes/api/v1/users/index_test.dartcontext.read<T>()For detailed patterns and examples, 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.