Caddy config based on OpenAPI spec

1. The problem I’m having:

I am using Caddy as an API Gateway (reverse proxy). I want to define my API endpoints using an OpenAPI spec without having to also configure each endpoint in the caddy config. All of my endpoints have the same configuration for auth, circuit breaking, and rate limiting, CORS etc.
Could I define my endpoints with wildcards eg: /users/* and then use an OpenAPI validator to make sure that the request is valid? Is this safe?

2. Error messages and/or full log output:

N/A

3. Caddy version:

v2.10.0

4. How I installed and ran Caddy:

Installed using Chocolety on Windows. Running using the CLI from Powershell

a. System environment:

Windows 11

@chukmunnlee - this could be a novel use of your OAS validator plugin.

Hi Jonathan

Thanks for the idea. So capture it with RE and match it with OpenAPI.

The OpenAPI spec does not support RE; so it would be quite difficult to add this feature as you would have to extend a top-level OAS specification. AFAIK, there are no extensions supported directly in the path specification, only definitions within a path.

A logical way to achieve this is to use, a host module, which matches these RE routes, then forward those requests to the OAS plugin.

Hope this helps.

Regards
Chuk

2 Likes

Yes, you can safely use wildcard routes like /users/* in your Caddy config to avoid duplicating endpoint definitions, and then validate requests against your OpenAPI spec using a validator plugin (like @chukmunnlee’s OAS validator). This approach centralizes your route logic in Caddy while offloading endpoint validation to the OpenAPI spec, keeping config minimal and consistent—as long as your validator reliably enforces method, path, and schema checks before passing the request downstream.

FYI, this worked really well. Here is the config I used. You effectively have a way to manage to define routes in your OAS spec without having to duplicate them in your CaddyFile. Its a good “API first” approach.

FYI: @chukmunnlee and @jhonnmick

CaddyFile

{
	order oas_validator before reverse_proxy
	order request_id before reverse_proxy
	order circuit_breaker before reverse_proxy
	metrics
	debug
	log {
		output stdout
		format json
	}
}

:8080 {
	# HTTPBin API endpoints
	handle /api/* {
		request_id
		oas_validator {
			spec /etc/caddy/openapi/httpbin-api.json
			{$DEBUG:+fall_through}
			{$DEBUG:+log_error}
			{$DEBUG:+multi_error}
		}
		uri strip_prefix /api
		reverse_proxy httpbin:80
	}

	# Echo API endpoints
	handle /echo* {
		request_id
		oas_validator {
			spec /etc/caddy/openapi/echo-api.json
			{$DEBUG:+log_error}
		}
		reverse_proxy echo:80
	}

	# Catch-all for unmatched paths
	handle {
		respond "Not Found" 404
	}
}

:{$MONITORING_PORT:5555} {
	handle {$HEALTH_PATH:/health} {
		respond "OK" 200
	}

	handle {$METRICS_PATH:/metrics} {
		metrics {$METRICS_PATH:/metrics}
	}
}

OAS for echo-api

{
  "openapi": "3.0.0",
  "info": {
    "title": "Echo API",
    "version": "1.0.0",
    "description": "Echo server endpoints"
  },
  "servers": [
    {
      "url": "http://localhost:8080"
    }
  ],
  "paths": {
    "/echo": {
      "get": {
        "operationId": "echoGet",
        "summary": "Echo GET request",
        "description": "Echoes back request information",
        "parameters": [
          {
            "name": "request-id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "method": "GET",
                  "headers": {
                    "request-id": "123e4567-e89b-12d3-a456-426614174000"
                  },
                  "query": {},
                  "body": null
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "post": {
        "operationId": "echoPost",
        "summary": "Echo POST request",
        "description": "Echoes back POST request data",
        "parameters": [
          {
            "name": "request-id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "message": "Hello World"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "method": "POST",
                  "headers": {
                    "content-type": "application/json",
                    "request-id": "123e4567-e89b-12d3-a456-426614174000"
                  },
                  "body": {
                    "message": "Hello World"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      },
      "put": {
        "operationId": "echoPut",
        "summary": "Echo PUT request",
        "description": "Echoes back PUT request data",
        "parameters": [
          {
            "name": "request-id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "id": 1,
                "data": "Updated content"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "method": "PUT",
                  "headers": {
                    "content-type": "application/json",
                    "request-id": "123e4567-e89b-12d3-a456-426614174000"
                  },
                  "body": {
                    "id": 1,
                    "data": "Updated content"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/echo/users/{userId}": {
      "get": {
        "operationId": "getUser",
        "summary": "Get user by ID",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "User data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/echo/users": {
      "get": {
        "operationId": "getUsers",
        "summary": "Get users list",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Users list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}