api_kit 0.0.4
api_kit: ^0.0.4 copied to clipboard
Production-ready REST API framework with comprehensive JWT authentication system, custom validators, annotation-based routing, token blacklisting, etc.
api_kit #
Production-ready REST API framework with annotation-based routing and comprehensive JWT validation system. Perfect for MVPs, rapid prototyping, and enterprise applications.
Features #
- π Annotation-based routing: Just add @GET, @POST, etc. and you're done
- π JWT Authentication System: Complete JWT validation with custom validators
- β‘ Fast setup: Perfect for MVPs and rapid prototyping
- π¦ Controller lists: Register controllers like simple_rest
- π Result pattern: Clean error handling with result_controller
- π Built-in logging: Structured logging with logger_rs
- π‘οΈ Production security: Enterprise-grade security features
- π― Flexible validation: Custom validators with AND/OR logic
- βοΈ Token blacklisting: Advanced token management system
Getting started #
Add this to your pubspec.yaml
:
dependencies:
api_kit: ^0.0.4
Quick Start #
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:api_kit/api_kit.dart';
void main() async {
// Create API server with JWT configuration
final server = ApiServer(config: ServerConfig.production());
// Configure JWT authentication
server.configureJWTAuth(
jwtSecret: 'your-256-bit-secret-key-here',
excludePaths: ['/api/public', '/health'],
);
// Start server with controllers
final result = await server.start(
host: 'localhost',
port: 8080,
controllerList: [UserController(), AdminController()],
);
result.when(
ok: (httpServer) => print('π Server running on http://localhost:8080'),
err: (error) => print('β Failed to start: ${error.msm}'),
);
}
Basic Controller #
@Controller('/api/v1/users')
class UserController extends BaseController {
@GET('/') // GET /api/v1/users/
Future<Response> getUsers(Request request) async {
logRequest(request, 'Getting users');
final response = ApiResponse.success(['user1', 'user2']);
return jsonResponse(response.toJson());
}
@GET('/<id>') // GET /api/v1/users/<id>
Future<Response> getUser(Request request) async {
final id = getRequiredParam(request, 'id');
final response = ApiResponse.success({'id': id, 'name': 'User $id'});
return jsonResponse(response.toJson());
}
}
JWT Authentication System #
JWT Annotations #
api_kit provides three powerful JWT annotations for complete access control:
@JWTPublic - Public Endpoints
@Controller('/api/public')
class PublicController extends BaseController {
@GET('/info')
@JWTPublic() // β
No JWT required - always accessible
Future<Response> getPublicInfo(Request request) async {
return jsonResponse({'message': 'Public data'});
}
}
@JWTController - Controller-Level Protection
@Controller('/api/admin')
@JWTController([
const MyAdminValidator(),
const MyBusinessHoursValidator(),
], requireAll: true) // π ALL validators must pass (AND logic)
class AdminController extends BaseController {
@GET('/users')
Future<Response> getUsers(Request request) async {
// Protected by controller-level validation
final jwtPayload = request.context['jwt_payload'] as Map<String, dynamic>;
return jsonResponse({'users': [], 'admin': jwtPayload['name']});
}
}
@JWTEndpoint - Endpoint-Level Override
@Controller('/api/finance')
@JWTController([
const MyDepartmentValidator(allowedDepartments: ['finance']),
])
class FinanceController extends BaseController {
@GET('/reports')
Future<Response> getReports(Request request) async {
// Uses controller validation (department = finance)
}
@POST('/transactions')
@JWTEndpoint([
const MyFinancialValidator(minimumAmount: 10000),
const MyAdminValidator(),
], requireAll: false) // π Either validator can pass (OR logic)
Future<Response> createTransaction(Request request) async {
// Override: Either financial validator OR admin validator
}
}
Custom JWT Validators #
Create your own validators by extending JWTValidatorBase
:
Basic Admin Validator
class MyAdminValidator extends JWTValidatorBase {
const MyAdminValidator();
@override
ValidationResult validate(Request request, Map<String, dynamic> jwtPayload) {
final role = jwtPayload['role'] as String?;
final isActive = jwtPayload['active'] as bool? ?? false;
final permissions = jwtPayload['permissions'] as List<dynamic>? ?? [];
if (role != 'admin') {
return ValidationResult.invalid('Administrator role required');
}
if (!isActive) {
return ValidationResult.invalid('Account is inactive');
}
if (!permissions.contains('admin_access')) {
return ValidationResult.invalid('Missing admin access permission');
}
return ValidationResult.valid();
}
@override
String get defaultErrorMessage => 'Administrator access required';
}
Advanced Financial Validator
class MyFinancialValidator extends JWTValidatorBase {
final double minimumAmount;
const MyFinancialValidator({this.minimumAmount = 0.0});
@override
ValidationResult validate(Request request, Map<String, dynamic> jwtPayload) {
final department = jwtPayload['department'] as String?;
final clearanceLevel = jwtPayload['clearance_level'] as int? ?? 0;
final certifications = jwtPayload['certifications'] as List<dynamic>? ?? [];
final maxTransactionAmount = jwtPayload['max_transaction_amount'] as double? ?? 0.0;
// Validate department
if (department != 'finance' && department != 'accounting') {
return ValidationResult.invalid('Access restricted to financial departments');
}
// Validate clearance level
if (clearanceLevel < 3) {
return ValidationResult.invalid('Insufficient clearance level for financial operations');
}
// Validate certifications
if (!certifications.contains('financial_ops_certified')) {
return ValidationResult.invalid('Financial operations certification required');
}
// Validate transaction amount limits
if (minimumAmount > 0 && maxTransactionAmount < minimumAmount) {
return ValidationResult.invalid('Transaction amount exceeds user authorization limit');
}
return ValidationResult.valid();
}
@override
String get defaultErrorMessage => 'Financial operations access required';
}
JWT Configuration Options #
void main() async {
final server = ApiServer(config: ServerConfig.production());
// Configure JWT with all options
server.configureJWTAuth(
jwtSecret: 'your-256-bit-secret-key-for-production',
excludePaths: ['/api/public', '/health', '/docs'],
);
// Token blacklist management
server.blacklistToken('jwt-token-to-invalidate');
server.clearTokenBlacklist();
print('Blacklisted tokens: ${server.blacklistedTokensCount}');
// Dynamic configuration changes
server.disableJWTAuth(); // Temporarily disable
server.configureJWTAuth(jwtSecret: 'new-secret'); // Re-enable with new config
}
JWT Payload Access #
Access JWT data in your endpoints:
@GET('/profile')
Future<Response> getProfile(Request request) async {
// Full JWT payload
final jwtPayload = request.context['jwt_payload'] as Map<String, dynamic>;
// Convenient shortcuts
final userId = request.context['user_id'] as String?;
final userEmail = request.context['user_email'] as String?;
final userRole = request.context['user_role'] as String?;
return jsonResponse({
'user_id': jwtPayload['user_id'],
'email': jwtPayload['email'],
'name': jwtPayload['name'],
'custom_data': jwtPayload['custom_field'],
// All JWT claims available
});
}
Validation Logic #
AND Logic (requireAll: true) #
@JWTController([
const MyAdminValidator(),
const MyBusinessHoursValidator(),
], requireAll: true) // β
BOTH validators must pass
OR Logic (requireAll: false) #
@JWTController([
const MyAdminValidator(),
const MyDepartmentValidator(allowedDepartments: ['support']),
], requireAll: false) // β
EITHER validator can pass
Built-in Validators #
api_kit includes production-ready validators:
MyAdminValidator #
- Validates
role: 'admin'
- Checks
active: true
- Requires
permissions: ['admin_access']
MyFinancialValidator #
- Department validation (finance/accounting)
- Clearance level requirements
- Certification validation
- Transaction amount limits
MyDepartmentValidator #
- Configurable allowed departments
- Optional management level requirements
- Employee level validation
MyBusinessHoursValidator #
- Working hours validation (configurable)
- Business days only
- After-hours access override
Error Responses #
Consistent error handling with detailed information:
401 Unauthorized (No/Invalid JWT) #
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "JWT token required",
"status_code": 401
},
"timestamp": "2024-01-15T10:30:00.000Z",
"request_id": "req_123456"
}
403 Forbidden (JWT Valid, Authorization Failed) #
{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "Administrator access required",
"status_code": 403,
"details": {
"validation_mode": "require_all",
"validators_count": 2,
"failed_validations": ["Administrator role required"]
}
},
"timestamp": "2024-01-15T10:30:00.000Z",
"request_id": "req_123456"
}
Available Annotations #
HTTP Methods #
@GET('/path')
,@POST('/path')
,@PUT('/path')
,@DELETE('/path')
,@PATCH('/path')
JWT Authentication #
@JWTPublic()
- Public endpoint (no JWT required)@JWTController([validators], requireAll: bool)
- Controller-level protection@JWTEndpoint([validators], requireAll: bool)
- Endpoint-level protection
Controllers #
@Controller('/base/path')
- Define base path for controller
Path Parameters #
Access path parameters using helper methods:
@GET('/users/<userId>/posts/<postId>')
Future<Response> getUserPost(Request request) async {
final userId = getRequiredParam(request, 'userId');
final postId = getRequiredParam(request, 'postId');
return jsonResponse({
'user_id': userId,
'post_id': postId,
'data': 'User $userId post $postId'
});
}
Security Features #
Automatic Security Headers #
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Strict-Transport-Security
Content-Security-Policy
JWT Token Blacklisting #
// Blacklist a specific token
server.blacklistToken('eyJhbGciOiJIUzI1NiIs...');
// Check blacklist size
print('Blacklisted tokens: ${server.blacklistedTokensCount}');
// Clear all blacklisted tokens
server.clearTokenBlacklist();
Rate Limiting #
final config = ServerConfig(
rateLimit: RateLimitConfig(
maxRequests: 100,
window: Duration(minutes: 1),
maxRequestsPerIP: 1000,
),
);
Reflection Support #
The framework automatically detects if reflection is available:
- With Reflection (Dart VM): Annotations work automatically via
dart:mirrors
- Without Reflection (Flutter Web): Falls back to manual route registration
@Controller('/api/v1/products')
class ProductController extends BaseController {
@override
Router get router {
if (ReflectionHelper.isReflectionAvailable) {
return super.router; // Automatic annotation-based routing
}
// Manual fallback for Flutter Web
final router = Router();
router.get('/', getProducts);
router.post('/', createProduct);
return router;
}
@GET('/')
Future<Response> getProducts(Request request) async {
// Works in both reflection and non-reflection environments
}
}
Configuration #
Production Configuration #
final server = ApiServer(
config: ServerConfig.production(),
);
Development Configuration #
final server = ApiServer(
config: ServerConfig.development(), // More permissive for development
);
Custom Configuration #
final config = ServerConfig(
cors: CorsConfig.permissive(),
rateLimit: RateLimitConfig(maxRequests: 1000),
security: SecurityConfig.strict(),
);
Logging #
Uses logger_rs
for structured logging:
Log.i('Server started successfully');
Log.w('Rate limit warning');
Log.e('Error occurred', error: error, stackTrace: stackTrace);
// JWT-specific logging
Log.i('π JWT authentication configured');
Log.w('π« Token added to blacklist');
Log.e('β JWT validation failed');
Production Deployment #
Docker Example #
FROM dart:stable AS build
WORKDIR /app
COPY pubspec.* ./
RUN dart pub get
COPY . .
RUN dart compile exe bin/server.dart -o bin/server
FROM scratch
COPY --from=build /app/bin/server /app/bin/server
EXPOSE 8080
ENTRYPOINT ["/app/bin/server"]
Environment Variables #
export JWT_SECRET="your-production-secret-key-256-bits-minimum"
export SERVER_PORT="8080"
export SERVER_HOST="0.0.0.0"
export LOG_LEVEL="INFO"
Testing #
The JWT system includes comprehensive tests:
# Run all tests
dart test
# Run JWT-specific tests
dart test test/jwt_validation_system_test.dart
dart test test/jwt_production_ready_test.dart
# Run with coverage
dart test --coverage=coverage
genhtml coverage/lcov.info -o coverage/html
Performance #
- 139/139 tests passing with 100% success rate
- Concurrent request handling tested and validated
- Token blacklisting with efficient lookup
- Reflection-based routing with fallback support
- Production-grade error handling
Migration from v0.0.1 #
Before (Old Authentication) #
@Controller('/api/admin')
class AdminController extends BaseController {
@GET('/users')
@RequireAuth(role: 'admin') // β Old system
Future<Response> getUsers(Request request) async {
// ...
}
}
After (New JWT System) #
@Controller('/api/admin')
@JWTController([
const MyAdminValidator(),
], requireAll: true) // β
New JWT system
class AdminController extends BaseController {
@GET('/users')
Future<Response> getUsers(Request request) async {
// JWT payload automatically available in request.context
}
}
Roadmap #
- β JWT authentication system with custom validators
- β Token blacklisting and management
- β Comprehensive test coverage (139 tests)
- β Production-ready security headers
- β Error handling and logging
- β Database integration helpers
- β WebSocket support with JWT
- β Metrics and monitoring
- β OpenAPI/Swagger documentation generation
- β Redis-based token blacklist for scaling
Contributing #
This library is production-ready with comprehensive test coverage. Contributions, suggestions, and feedback are welcome!
Development Setup #
git clone https://github.com/JhonaCodes/api_kit
cd api_kit
dart pub get
dart test
License #
MIT License - see LICENSE file for details.
Built with β€οΈ for Jhonacode who need production-ready APIs fast.