Skip to main content

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.

Why OpenAPI Documentation?

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 ApproachLambdaOpenApi
Hand-written YAML/JSON specsAuto-generated from code
Specs drift from implementationAlways in sync with code
Manual schema definitionsSchemas from C# types
No compile-time validationBuild-time spec generation
Runtime reflection overheadZero 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

AttributePurpose
[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

AttributePurpose
[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

AttributePurpose
[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