NativeLambdaRouter 1.0.2
See the version list below for details.
dotnet add package NativeLambdaRouter --version 1.0.2
NuGet\Install-Package NativeLambdaRouter -Version 1.0.2
<PackageReference Include="NativeLambdaRouter" Version="1.0.2" />
<PackageVersion Include="NativeLambdaRouter" Version="1.0.2" />
<PackageReference Include="NativeLambdaRouter" />
paket add NativeLambdaRouter --version 1.0.2
#r "nuget: NativeLambdaRouter, 1.0.2"
#:package NativeLambdaRouter@1.0.2
#addin nuget:?package=NativeLambdaRouter&version=1.0.2
#tool nuget:?package=NativeLambdaRouter&version=1.0.2
NativeLambdaRouter
A high-performance API Gateway routing library for AWS Lambda, optimized for Native AOT. Provides declarative route mapping to mediator commands with path parameter extraction, health checks, and automatic error handling.
Features
- 🚀 Native AOT Compatible - Full support for .NET Native AOT compilation
- 🛣️ Declarative Routing - Map HTTP endpoints to mediator commands with fluent API
- 📦 Path Parameters - Extract parameters from URLs like
/items/{id} - ❤️ Health Checks - Built-in
/healthand/healthzendpoints - ⚠️ Error Handling - Automatic HTTP status codes for common exceptions
- 🔐 Authorization - Fluent authorization with policies, roles, and claims
- 🎫 JWT Claims - Access authenticated user claims from route context
- 🎯 NativeMediator Integration - Seamless integration with CQRS pattern
Installation
dotnet add package NativeLambdaRouter
Quick Start
1. Create your Lambda Function
using NativeLambdaRouter;
using NativeMediator;
public class Function : RoutedApiGatewayFunction
{
public Function(IMediator mediator)
: base(mediator, JsonSerializerContext.Default.Options)
{
}
protected override void ConfigureRoutes(IRouteBuilder routes)
{
// GET /items - List all items
routes.MapGet<GetItemsCommand, GetItemsResponse>(
"/items",
ctx => new GetItemsCommand());
// GET /items/{id} - Get item by ID
routes.MapGet<GetItemByIdCommand, GetItemByIdResponse>(
"/items/{id}",
ctx => new GetItemByIdCommand(ctx.PathParameters["id"]));
// POST /items - Create new item
routes.MapPost<CreateItemCommand, CreateItemResponse>(
"/items",
ctx => JsonSerializer.Deserialize<CreateItemCommand>(ctx.Body!)!);
// PUT /items/{id} - Update item
routes.MapPut<UpdateItemCommand, UpdateItemResponse>(
"/items/{id}",
ctx => new UpdateItemCommand(
ctx.PathParameters["id"],
JsonSerializer.Deserialize<UpdateItemRequest>(ctx.Body!)!));
// DELETE /items/{id} - Delete item
routes.MapDelete<DeleteItemCommand, DeleteItemResponse>(
"/items/{id}",
ctx => new DeleteItemCommand(ctx.PathParameters["id"]));
}
protected override async Task<object> ExecuteCommandAsync(
RouteMatch match,
RouteContext context)
{
var command = match.Route.CommandFactory(context);
return command switch
{
GetItemsCommand cmd => await Mediator.Send(cmd),
GetItemByIdCommand cmd => await Mediator.Send(cmd),
CreateItemCommand cmd => await Mediator.Send(cmd),
UpdateItemCommand cmd => await Mediator.Send(cmd),
DeleteItemCommand cmd => await Mediator.Send(cmd),
_ => throw new InvalidOperationException($"Unknown command: {command.GetType().Name}")
};
}
}
Architecture
flowchart LR
subgraph Lambda["Lambda Function"]
Router["RoutedApiGatewayFunction"]
Routes["ConfigureRoutes()"]
Matcher["RouteMatcher"]
Executor["ExecuteCommandAsync()"]
end
HTTP["API Gateway Request"] --> Router
Router --> Matcher
Matcher --> |"Match Found"| Routes
Routes --> Executor
Executor --> Mediator["NativeMediator"]
Mediator --> Handler["Command Handler"]
Handler --> Response["JSON Response"]
Matcher --> |"No Match"| NotFound["404 Not Found"]
Route Context
The RouteContext provides access to request information:
| Property | Type | Description |
|---|---|---|
Body |
string? |
Raw request body |
PathParameters |
Dictionary<string, string> |
URL path parameters ({id} → "123") |
QueryParameters |
Dictionary<string, string> |
Query string parameters |
Headers |
Dictionary<string, string> |
Request headers |
Claims |
Dictionary<string, string> |
JWT claims (when authenticated) |
HTTP Methods
routes.MapGet<TCommand, TResponse>(path, commandFactory);
routes.MapPost<TCommand, TResponse>(path, commandFactory);
routes.MapPut<TCommand, TResponse>(path, commandFactory);
routes.MapDelete<TCommand, TResponse>(path, commandFactory);
routes.MapPatch<TCommand, TResponse>(path, commandFactory);
// Custom method
routes.Map<TCommand, TResponse>(method, path, commandFactory, requiresAuth: true);
Authorization
NativeLambdaRouter provides a fluent authorization API inspired by ASP.NET Core Minimal APIs.
Defining Policies
Override ConfigureAuthorization to define named policies:
protected override void ConfigureAuthorization(AuthorizationBuilder auth)
{
auth.AddPolicy("admin_greetings", policy =>
policy
.RequireRole("admin")
.RequireClaim("scope", "greetings_api"));
auth.AddPolicy("api_access", policy =>
policy
.RequireClaim("scope", "api:read", "api:write")
.RequireAssertion(ctx => ctx.Headers.ContainsKey("X-Api-Key")));
}
Applying Authorization to Routes
Use fluent methods to apply authorization requirements:
protected override void ConfigureRoutes(IRouteBuilder routes)
{
// Require a named policy
routes.MapPut<UpdateItemCommand, UpdateItemResponse>(
"/items/{id}",
ctx => new UpdateItemCommand(
ctx.PathParameters["id"],
JsonSerializer.Deserialize<UpdateItemRequest>(ctx.Body!)!))
.RequireAuthorization("admin_greetings");
// Require specific roles
routes.MapDelete<DeleteItemCommand, DeleteItemResponse>(
"/items/{id}",
ctx => new DeleteItemCommand(ctx.PathParameters["id"]))
.RequireRole("admin", "superuser");
// Require specific claims
routes.MapPost<CreateItemCommand, CreateItemResponse>(
"/items",
ctx => JsonSerializer.Deserialize<CreateItemCommand>(ctx.Body!)!)
.RequireClaim("scope", "items:write");
// Allow anonymous access (bypass authorization)
routes.MapGet<GetPublicDataCommand, GetPublicDataResponse>(
"/public",
ctx => new GetPublicDataCommand())
.AllowAnonymous();
// Combine multiple requirements
routes.MapPatch<PatchItemCommand, PatchItemResponse>(
"/items/{id}",
ctx => new PatchItemCommand(ctx.PathParameters["id"]))
.RequireAuthorization("api_access")
.RequireRole("editor")
.RequireClaim("department", "engineering");
}
Authorization Methods
| Method | Description |
|---|---|
.RequireAuthorization(policies...) |
Requires authentication and optionally specific policies |
.RequireRole(roles...) |
Requires the user to have at least one of the specified roles |
.RequireClaim(type, values...) |
Requires a claim with one of the specified values |
.AllowAnonymous() |
Bypasses all authorization checks |
Policy Builder Methods
| Method | Description |
|---|---|
.RequireRole(roles...) |
User must have at least one of these roles |
.RequireClaim(type) |
User must have this claim (any value) |
.RequireClaim(type, values...) |
User must have this claim with one of these values |
.RequireAssertion(func) |
Custom authorization logic via delegate |
Role Claim Support
The authorization system automatically checks these claim types for roles:
rolerolescognito:groups(AWS Cognito)groups
Roles can be in various formats:
- Single value:
"admin" - Comma-separated:
"admin,user" - JSON array:
["admin","user"]
Error Handling
Built-in exception handling with automatic HTTP status codes:
| Exception | HTTP Status | Response |
|---|---|---|
ValidationException |
400 Bad Request | { "error": "Validation failed", "details": "..." } |
NotFoundException |
404 Not Found | { "error": "Resource not found", "details": "..." } |
UnauthorizedException |
401 Unauthorized | { "error": "Unauthorized", "details": "..." } |
ForbiddenException |
403 Forbidden | { "error": "Forbidden", "details": "..." } |
ConflictException |
409 Conflict | { "error": "Conflict", "details": "..." } |
| Other exceptions | 500 Internal Server Error | { "error": "Internal server error", "details": "..." } |
Using Exceptions
public class GetItemHandler : IRequestHandler<GetItemCommand, GetItemResponse>
{
public async ValueTask<GetItemResponse> Handle(GetItemCommand request, CancellationToken ct)
{
var item = await _repository.GetByIdAsync(request.Id);
if (item == null)
throw new NotFoundException($"Item {request.Id} not found");
if (!item.IsValid)
throw new ValidationException("Item is not valid");
return new GetItemResponse(item);
}
}
Health Checks
Built-in health check endpoints respond to /health and /healthz:
{
"status": "healthy",
"function": "MyFunction",
"timestamp": "2026-01-23T10:30:00.000Z",
"environment": "production"
}
Customize the health check response:
protected override object GetHealthCheckResponse()
{
return new
{
Status = "healthy",
Version = "1.0.0",
Dependencies = new { Database = "ok", Cache = "ok" }
};
}
Native AOT Serialization
For Native AOT compatibility, override SerializeResponse:
protected override string SerializeResponse(object response)
{
return response switch
{
GetItemsResponse r => JsonSerializer.Serialize(r, JsonSerializerContext.Default.GetItemsResponse),
GetItemByIdResponse r => JsonSerializer.Serialize(r, JsonSerializerContext.Default.GetItemByIdResponse),
_ => JsonSerializer.Serialize(response, JsonSerializerContext.Default.Object)
};
}
Requirements
- .NET 10.0 or later
- AWS Lambda with
provided.al2023runtime - NativeMediator for CQRS pattern
License
MIT License - see LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Amazon.Lambda.APIGatewayEvents (>= 2.7.3)
- Amazon.Lambda.Core (>= 2.8.0)
- NativeMediator (>= 1.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.