modular_api 0.0.9
modular_api: ^0.0.9 copied to clipboard
Use case centric API toolkit for Shelf UseCase base class, HTTP adapters, CORS/API-Key middlewares, and OpenAPI specifications.
modular_api #
Use-case–centric toolkit for building Modular APIs with Shelf.
Define UseCase classes (input → validate → execute → output), connect them to HTTP routes,
add CORS / API Key middlewares, and expose Swagger / OpenAPI documentation.
Designed for the MACSS ecosystem — modular, explicit and testable server code.
🚀 Quick start #
This quick start mirrors the included example implementation (example/example.dart).
import 'package:modular_api/modular_api.dart';
Future<void> main(List<String> args) async {
final api = ModularApi(basePath: '/api');
// POST api/module1/hello-world
api.module('module1', (m) {
m.usecase('hello-world', HelloWorld.fromJson);
});
final port = 8080;
await api.serve(port: port,);
print('Docs on http://localhost:$port/docs');
}
Example request (example server registers /api/module1/hello-world as a POST):
curl -H "Content-Type: application/json" -d '{"word":"world"}' \
-H "x-api-key: SECRET" \
"http://localhost:8080/api/module1/hello-world"
Example response (HelloOutput):
{"output":"Hello, world!"}
📖 Documentation #
Start here: doc/INDEX.md — Complete documentation index and vertical development guide
Quick Links #
Core guides for vertical feature development:
- doc/INDEX.md — Entry point: vertical development flow from DB to UI
- AGENTS.md — Framework overview and implementation guide (optimized for AI assistants)
- doc/usecase_dto_guide.md — Creating Input/Output DTOs
- doc/usecase_implementation.md — Implementing UseCases with business logic
- doc/testing_guide.md — Testing UseCases with
useCaseTestHandler - doc/http_client_guide.md — Using httpClient in Flutter and Dart apps
- doc/authentication_guide.md — Token management and storage adapters
- doc/auth_implementation_guide.md — Complete JWT authentication system
✨ Features #
-
✅
UseCase<I extends Input, O extends Output>base classes and DTOs (Input/Output). -
🎯 Custom HTTP Status Codes: Output DTOs can override
statusCodegetter to return appropriate HTTP status codes (200, 201, 400, 401, 404, 422, 500, etc.) -
🧩
useCaseHttpHandler()adapter: accepts a factoryUseCase Function(Map<String, dynamic>)and returns a ShelfHandler. -
🔐 OAuth2 & Authentication:
- OAuth2 Client Credentials: Built-in OAuth2 Authorization Server with Client Credentials grant type
- JWT Tokens: HS256 (HMAC-SHA256) signed tokens with configurable TTL
- Bearer Token Middleware: Automatic token validation and scope-based authorization
- Auto-mounting: OAuth token endpoint (
POST /oauth/token) automatically registered whenoauthServiceis provided - Scope Protection: Per-usecase scope requirements via
requiredScopesparameter httpClient()— intelligent HTTP client with automatic authentication, token management, and auto-refresh on 401Token— in-memory session management (access tokens, expiration checking)TokenVault— configurable persistent storage for refresh tokens (with adapters for memory, file, and custom storage)JwtHelper— JWT generation and validation utilitiesPasswordHasher— bcrypt password hashing and verificationTokenHasher— SHA-256 token hashing for secure storageAuthReLoginException— specialized exception for authentication flow control
-
🧱 Included middlewares:
cors()— simple CORS support.apiKey()— header-based authentication; the key is read from theAPI_KEYenvironment variable (viaEnv).bearer()— OAuth2 Bearer token validation with scope checking.
-
📄 OpenAPI / Swagger helpers:
OpenApi.init(title)andOpenApi.docs— generate an OpenAPI spec from registered usecases (uses DTOtoSchema()), and provide a Swagger UIHandler.
-
📡 Automatic health endpoint:
- The server registers a simple health check endpoint at
GET /healthwhich responds with 200 OK and bodyok. This is implemented inmodular_api.dartas:_root.get('/health', (Request request) => Response.ok('ok'));
- The server registers a simple health check endpoint at
-
⚙️ Utilities:
Env.getString,Env.getInt,Env.setString(.env support via dotenv). -
Envbehavior: if a.envfile is not present the library will read values fromPlatform.environment; if a requested key is missing from both sources anEnvKeyNotFoundExceptionis thrown. -
🧪 Example project and tests included in
example/andtest/. -
🗄️ ODBC database client: a minimal ODBC
DbClientimplementation (DSN-based) tested with Oracle and SQL Server — seeNOTICEfor provenance and details.Usage:
import 'package:modular_api/modular_api.dart'; Future<void> runQuery() async { // Create a DSN-based client (example factories available in example/lib/db/db.dart) final db = createSqlServerClient(); try { final rows = await db.execute('SELECT @@VERSION as version'); print(rows); } finally { // ensure disconnect await db.disconnect(); } }See
NOTICEfor provenance details.
📦 Installation #
In pubspec.yaml:
dependencies:
modular_api: ^0.0.10
Or from the command line:
dart pub add modular_api
dart pub get
🔐 OAuth2 Client Credentials #
The framework provides built-in OAuth2 Authorization Server support with Client Credentials grant type, JWT tokens, and automatic Bearer token validation.
Quick Setup #
import 'package:modular_api/modular_api.dart';
Future<void> main() async {
// 1. Create OAuth2 service
final oauthService = OAuthService(
jwtSecret: Env.getString('JWT_SECRET'),
issuer: 'your-domain.com',
audience: 'your-domain.com',
tokenTtlSeconds: 86400, // 24 hours
);
// 2. Register OAuth2 clients
oauthService.registerClient(
OAuthClient(
clientId: 'client-id',
clientSecret: 'client-secret',
allowedScopes: ['read', 'write'],
name: 'Client Name',
isActive: true,
),
);
// 3. Create API with OAuth2 (auto-mounts /oauth/token endpoint)
final api = ModularApi(
basePath: '/api',
oauthService: oauthService,
);
// 4. Protect specific usecases with scopes
api.module('resources', (m) {
m.usecase(
'create',
CreateResource.fromJson,
requiredScopes: ['write'], // Requires 'write' scope
);
});
await api.serve(port: 8080);
}
OAuth2 Flow #
# 1. Obtain access token
curl -X POST http://localhost:8080/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "client-id",
"client_secret": "client-secret",
"scope": "read write"
}'
# Response:
# {
# "access_token": "eyJhbGc...",
# "token_type": "Bearer",
# "expires_in": 86400,
# "scope": "read write"
# }
# 2. Use access token to call protected endpoints
curl -X POST http://localhost:8080/api/resources/create \
-H "Authorization: Bearer eyJhbGc..." \
-H "Content-Type: application/json" \
-d '{"name": "Resource 1"}'
Features #
- ✅ Client Credentials Grant: OAuth2 standard flow for machine-to-machine authentication
- ✅ JWT Tokens: HS256 signed tokens with standard claims (iss, aud, sub, iat, exp, scopes)
- ✅ Auto-mounting:
/oauth/tokenendpoint automatically registered - ✅ Scope Validation: Per-usecase scope requirements
- ✅ Bearer Middleware: Automatic token validation with 401/403 responses
- ✅ 405 Support: Proper HTTP method validation (POST required for token endpoint)
🔐 Authentication with httpClient #
The intelligent httpClient simplifies authentication by automatically managing tokens for single-user applications:
import 'package:modular_api/modular_api.dart';
Future<void> authenticatedFlow() async {
const baseUrl = 'http://localhost:8080';
// Login - httpClient automatically captures and stores tokens
final loginResponse = await httpClient(
method: 'POST',
baseUrl: baseUrl,
endpoint: 'api/auth/login',
body: {'username': 'user', 'password': 'pass'},
auth: true, // Auto-captures tokens from response
) as Map<String, dynamic>;
print('Access token: ${loginResponse['access_token']}');
// Protected request - httpClient automatically:
// 1. Attaches Bearer token
// 2. Retries with refresh token on 401
// 3. Throws AuthReLoginException if refresh fails
try {
final profile = await httpClient(
method: 'POST',
baseUrl: baseUrl,
endpoint: 'api/users/profile',
body: {'user_id': 123},
auth: true, // Auto-attaches Bearer token
);
print('Profile: $profile');
} on AuthReLoginException {
// Session expired, redirect to login
print('Please log in again');
}
}
See doc/http_client_guide.md for complete examples and configuration.
🧭 Architecture #
- UseCase layer — pure logic, independent of HTTP.
- HTTP adapter — turns a
UseCaseinto aHandler. - Middlewares — cross-cutting concerns (CORS, auth, logging).
- Swagger UI — documentation served automatically from registered use cases.
🧩 Middlewares #
final api = ModularApi(basePath: '/api');
.use(cors())
.use(apiKey());
📄 Swagger/OpenAPI #
To auto-generate the spec from registered routes and serve a UI:
Open http://localhost:<port>/docs to view the UI.
🧱 Example #
This repository includes a minimal runnable example:
example/— minimal, simplified runnable example. Checkexample/example.dartfor a concreteUseCase+ DTO example.
🧪 Tests #
The repository includes example tests (test/usecase_test.dart) that demonstrate the
recommended pattern and the useCaseTestHandler helper for unit-testing UseCase logic.
Run tests with:
dart test
🛠️ Compile to executable #
-
Windows
dart compile exe example/example.dart -o build/api_example.exe -
Linux / macOS
dart compile exe example/example.dart -o build/api_example
📄 License #
MIT © ccisne.dev