Generate OpenAPI Documentation with LambdaOpenApi
OpenAPI (formerly Swagger) specifications are the industry standard for documenting REST APIs. With LambdaOpenApi, you can automatically generate OpenAPI documentation from your Lambda Annotations functions at build time—no manual YAML editing required.
OpenAPI specifications enable automatic client SDK generation, interactive API documentation (Swagger UI), and API testing tools. After completing the Lambda Annotations tutorial, adding OpenAPI documentation is the natural next step for production-ready APIs.
Prerequisites
- Completed the Lambda Annotations tutorial (recommended)
- .NET 6 SDK or later
- An existing Lambda Annotations project
What LambdaOpenApi Provides
LambdaOpenApi is a source generator that extracts API metadata from your Lambda Annotations functions:
| Manual Approach | LambdaOpenApi |
|---|---|
| Hand-written YAML/JSON specs | Auto-generated from code |
| Specs drift from implementation | Always in sync with code |
| Manual schema definitions | Schemas from C# types |
| No compile-time validation | Build-time spec generation |
| Runtime reflection overhead | Zero runtime cost |
Project Setup
Install LambdaOpenApi
Add the LambdaOpenApi package to your existing Lambda Annotations project:
dotnet add package Oproto.Lambda.OpenApi
That's it! The source generator integrates automatically with your build process.
Code Example
Here's the product API from the Lambda Annotations tutorial, now enhanced with LambdaOpenApi attributes:
Assembly-Level API Configuration
Create or update an AssemblyInfo.cs file (or add to any .cs file):
using Oproto.Lambda.OpenApi.Attributes;
// API metadata
[assembly: OpenApiInfo("Products API", "1.0.0",
Description = "API for managing products in the catalog")]
// Optional: Define server URLs
[assembly: OpenApiServer("https://api.example.com/v1", Description = "Production")]
// Optional: Define tags with descriptions
[assembly: OpenApiTagDefinition("Products", Description = "Product management operations")]
The Product Model with Schema Attributes
using Oproto.Lambda.OpenApi.Attributes;
namespace MyLambdaApi.Models;
public class Product
{
[OpenApiSchema(Description = "Unique product identifier", Format = "uuid")]
public string Id { get; set; } = string.Empty;
[OpenApiSchema(Description = "Product name", MinLength = 1, MaxLength = 200)]
public string Name { get; set; } = string.Empty;
[OpenApiSchema(Description = "Price in USD", Minimum = 0)]
public decimal Price { get; set; }
[OpenApiSchema(Description = "Product description")]
public string Description { get; set; } = string.Empty;
}
The Lambda Functions with OpenAPI Attributes
using Amazon.Lambda.Core;
using Amazon.Lambda.Annotations;
using Amazon.Lambda.Annotations.APIGateway;
using Oproto.Lambda.OpenApi.Attributes;
using MyLambdaApi.Models;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace MyLambdaApi;
[OpenApiTag("Products")]
public class Functions
{
/// <summary>
/// Get a product by ID
/// </summary>
[LambdaFunction]
[HttpApi(LambdaHttpMethod.Get, "/products/{id}")]
[OpenApiOperation(Summary = "Get product by ID",
Description = "Retrieves a single product by its unique identifier")]
public Product GetProduct(
[FromRoute] string id,
ILambdaContext context)
{
context.Logger.LogInformation($"Getting product with ID: {id}");
return new Product
{
Id = id,
Name = "Sample Product",
Price = 29.99m,
Description = "A sample product for demonstration"
};
}
/// <summary>
/// Create a new product
/// </summary>
[LambdaFunction]
[HttpApi(LambdaHttpMethod.Post, "/products")]
[OpenApiOperation(Summary = "Create a new product",
Description = "Creates a new product and returns the created resource")]
[OpenApiResponseType(typeof(Product), 201, Description = "Product created successfully")]
public IHttpResult CreateProduct(
[FromBody] Product product,
ILambdaContext context)
{
product.Id = Guid.NewGuid().ToString();
context.Logger.LogInformation($"Created product with ID: {product.Id}");
return HttpResults.Created($"/products/{product.Id}", product);
}
/// <summary>
/// List all products
/// </summary>
[LambdaFunction]
[HttpApi(LambdaHttpMethod.Get, "/products")]
[OpenApiOperation(Summary = "List all products",
Description = "Returns a paginated list of products")]
[OpenApiResponseHeader("X-Total-Count", Description = "Total number of products", Type = typeof(int))]
public IEnumerable<Product> GetProducts(
[FromQuery] int limit = 10,
ILambdaContext context = null!)
{
context?.Logger.LogInformation($"Getting products with limit: {limit}");
return new List<Product>
{
new Product { Id = "1", Name = "Widget", Price = 19.99m, Description = "A useful widget" },
new Product { Id = "2", Name = "Gadget", Price = 29.99m, Description = "A handy gadget" }
};
}
}
Build and Generate
When you build your project, LambdaOpenApi automatically generates the OpenAPI specification:
dotnet build
This creates an openapi.json file in your project directory with the complete API specification.
Generated OpenAPI Specification
Here's a sample of what gets generated:
{
"openapi": "3.0.1",
"info": {
"title": "Products API",
"description": "API for managing products in the catalog",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.example.com/v1",
"description": "Production"
}
],
"paths": {
"/products/{id}": {
"get": {
"tags": ["Products"],
"summary": "Get product by ID",
"description": "Retrieves a single product by its unique identifier",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Product" }
}
}
}
}
}
}
},
"components": {
"schemas": {
"Product": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique product identifier",
"format": "uuid"
},
"name": {
"type": "string",
"description": "Product name",
"minLength": 1,
"maxLength": 200
},
"price": {
"type": "number",
"description": "Price in USD",
"minimum": 0
},
"description": {
"type": "string",
"description": "Product description"
}
}
}
}
}
}
Key LambdaOpenApi Attributes
Assembly-Level Attributes
| Attribute | Purpose |
|---|---|
[OpenApiInfo] | API title, version, and description |
[OpenApiServer] | Server URLs (production, staging) |
[OpenApiTagDefinition] | Tag descriptions for grouping |
[OpenApiSecurityScheme] | Security definitions (API Key, OAuth2) |
Method-Level Attributes
| Attribute | Purpose |
|---|---|
[OpenApiOperation] | Summary and description for operations |
[OpenApiTag] | Group operations by tag |
[OpenApiResponseType] | Document response types for IHttpResult |
[OpenApiResponseHeader] | Document response headers |
[OpenApiExample] | Provide request/response examples |
Property-Level Attributes
| Attribute | Purpose |
|---|---|
[OpenApiSchema] | Customize schema with validation rules |
[OpenApiIgnore] | Exclude properties from documentation |
Handling IHttpResult Returns
When your Lambda functions return IHttpResult, the generator can't infer the response type. Use [OpenApiResponseType] to document the actual responses:
[LambdaFunction]
[HttpApi(LambdaHttpMethod.Get, "/products/{id}")]
[OpenApiResponseType(typeof(Product), 200, Description = "Returns the product")]
[OpenApiResponseType(typeof(ErrorResponse), 404, Description = "Product not found")]
public IHttpResult GetProduct([FromRoute] string id)
{
var product = FindProduct(id);
if (product == null)
return HttpResults.NotFound(new ErrorResponse { Message = "Not found" });
return HttpResults.Ok(product);
}
Adding Security Schemes
Document API authentication with security scheme attributes:
// API Key authentication
[assembly: OpenApiSecurityScheme("apiKey",
Type = OpenApiSecuritySchemeType.ApiKey,
ApiKeyName = "x-api-key",
ApiKeyLocation = ApiKeyLocation.Header,
Description = "API key authentication")]
// Or OAuth2
[assembly: OpenApiSecurityScheme("oauth2",
Type = OpenApiSecuritySchemeType.OAuth2,
AuthorizationUrl = "https://auth.example.com/authorize",
TokenUrl = "https://auth.example.com/token",
Scopes = "read:Read access,write:Write access")]
AOT Compatibility
LambdaOpenApi fully supports Native AOT builds. The specification is generated at build time using MetadataLoadContext, so there's no runtime reflection:
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
Next Steps
Now that your API has OpenAPI documentation, the next step is adding data persistence. The DynamoDB Integration tutorial shows how to integrate DynamoDB with FluentDynamoDB for a complete serverless application.
Continue to: DynamoDB Integration Tutorial →
Additional Resources
- LambdaOpenApi Getting Started — Installation and basic usage
- LambdaOpenApi Attribute Reference — Complete attribute documentation
- Lambda Annotations Tutorial (Previous)
- OpenAPI Specification — Official OpenAPI documentation
- Swagger UI — Interactive API documentation viewer