A lightweight web framework for dartlang :)
Inspired by unjs H3''s simple, composable API.
- Composable utilities.
- Functional style.
- Clean, useful abstractions.
This is a new project under active development.
Add H4 to your pubspec.yaml
:
dependencies:
h4: 0.4.1
Or install with dart pub get
dart pub add h4
Import the library and start building your server:
import 'package:h4/create.dart';
void main() {
var app = createApp();
var router = createRouter();
app.use(router);
router.get("/", (event) => "Hello world!");
}
void main() {
var app = createApp(port: 4000);
var router = createRouter();
app.use(router);
// Simply return values from your handlers and H4 handles serialization
router.get("/", (event) => "Hello world")
router.post("/post", (event) => {'easy': 'peasy'})
}
Register application-wide hooks (middleware pattern):
onRequest
: Run when a request is instantiated.onError
: Run when the application breaks due to an error.afterResponse
: Run after the request is handled.
void main() {
var app = createApp(
port: 5173,
onRequest: (event) {
// Add request timestamp and logging
event.context['requestTime'] = DateTime.now();
print('${event.method} ${event.path}');
},
onError: (error, stacktrace, event) {
// Log errors to your monitoring service
MyLogger.captureException(error, stacktrace, {
'path': event.path,
'method': event.method,
});
},
);
var router = createRouter();
app.use(router);
}
Example with authentication middleware:
void main() {
var app = createApp(
port: 5173,
onRequest: (event) {
// Skip auth for public routes
if (event.path.startsWith('/public')) return;
final token = event.headers.value('Authorization')?.replaceAll('Bearer ', '');
if (token == null) {
throw CreateError(message: 'Unauthorized', errorCode: 401);
}
// Add user to context for route handlers
event.context['user'] = verifyToken(token);
},
);
var router = createRouter();
app.use(router);
}
You can define parameters in your routes using :
prefix:
router.get('/users/:id', (event) {
final userId = event.params['id'];
return 'User with id: $userId'
});
// Matches 'articles/page1' and '/articles/page2' but not 'articles/page1/page2'
router.get('/articles/*', (event) {
final path = event.path;
return 'The tea is teaing!!'
});
// Matches 'articles/foo/bar' and 'articles/foo/bar/xen'
router.get('/articles/**', (event) {
final path = event.path;
return 'The tea is teaing!!'
});
Organize your routes by creating multiple routers with different base paths:
void main() {
var app = createApp(port: 3000);
var mainRouter = createRouter();
var apiRouter = createRouter();
// Mount routers with different base paths
app.use(mainRouter, basePath: '/');
app.use(apiRouter, basePath: '/api');
// Main routes
mainRouter.get('/hello', (event) => {'message': 'Hello World'});
// API routes
apiRouter.get('/users', (event) => ['user1', 'user2']);
apiRouter.post('/auth', (event) async {
var body = await readRequestBody(event);
return body;
});
}
Store request-specific data and handle headers:
void main() {
var app = createApp(
port: 3000,
onRequest: (event) {
// Store data in request context
event.context["userId"] = "123";
// Set CORS headers
handleCors(event, origin: "https://myapp.com", methods: "GET,POST");
},
afterResponse: (event) {
print("Response sent with status: ${event.statusCode}");
}
);
}
Handle file uploads to your server easily:
void main() {
var app = createApp(port: 3000);
var router = createRouter();
app.use(router);
router.post("/upload", (event) async {
var files = await readFiles(
event,
// Formdata field where your files are stored
fieldName: 'file',
// Optional: Set custom directory for uploaded files. (defaults to temp dir)
customFilePath: 'uploads',
// Optional: Hash filenames for security (defaults to false)
// When false, files are prefixed with a naive hash.
hashFileName: true,
// Optional: Set max file size in MB (defaults to 10MB)
maxFileSize: 5
);
// Returned File obj contains: path, size, originalname, mimeType
return {
'message': 'Upload complete',
'files': files
};
});
}
Handle multipart form-data:
void main() {
var app = createApp(port: 3000);
var router = createRouter();
app.use(router);
router.post("/signup", (event) async {
var formData = await readFormData(event);
// Access form fields
var username = formData.get('username');
var password = formData.get('password');
// Get multiple values
var interests = formData.getAll('interests');
return {
'user': username,
'interests': interests
};
});
}
Implement robust error handling:
void main() {
var app = createApp(
port: 3000,
onError: (error, stacktrace, event) {
print("Error occurred: $error");
}
);
var router = createRouter();
app.use(router);
router.get("/risky-operation", (event) async {
try {
// Potentially failing operation
throw Exception("Something went wrong");
} catch (e) {
throw CreateError(
message: "Operation failed: $e",
errorCode: 400
);
}
});
}
The client will recieve a JSON payload -
{
"status": 400,
"message": "Operation failed {error Message}"
}
You can contribute by
-
Improving the test coverage of this library,
-
Adding a helpful utility, for inspiration see here.
-
Sharing benchmarks with other libraries.
In the root directory run
dart test
Be cool and calm.