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.2

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

  • x JWT authentication system with custom validators
  • x Token blacklisting and management
  • x Comprehensive test coverage (139 tests)
  • x Production-ready security headers
  • x 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.

Libraries

api_kit
Simple, fast REST API framework with annotation-based routing.