huma icon indicating copy to clipboard operation
huma copied to clipboard

Request body not set in DELETE handlers via huma OpenAPI UI

Open DoGab opened this issue 5 months ago • 2 comments

First of all thank you a lot for this awesome framework! Love it! ❤️

I guess i found a little issue in the OpenAPI documentation UI.

Problem

When issuing a request to a delete handler, that takes a request body, via Huma OpenAPI UI, the request fails with the following error. The curl statement shows that the request will not include the request body.

{
  "$schema": "http://localhost:8888/schemas/ErrorModel.json",
  "title": "Bad Request",
  "status": 400,
  "detail": "request body is required"
}
Image

When defining a handler with http.MethodDelete i want to be able to pass a request body to the handler via OpenAPI UI.

Expected Behavior

Whenever a request body passes the validation i want the handler to be able to read the request body and process it. Whenever the validation of the request body fails i want to receive a 422.

Reproduce

This minimal example demonstrates the how to reproduce the error. It was tested with versions v2.26.0, v2.28.0, v2.30.0, v2.32.0, v2.33.0, v2.34.1.

package main

import (
	"context"
	"log/slog"
	"net/http"

	"github.com/danielgtaylor/huma/v2"
	"github.com/danielgtaylor/huma/v2/adapters/humachi"
	"github.com/go-chi/chi/v5"
)

type UserDeleteInput struct {
	Body UserDeleteInputBody `body:""`
}

type UserDeleteInputBody struct {
	UserIDs []string `json:"user_ids" doc:"The IDs of the users to delete" minItems:"1"`
}

type DeleteResponse struct {
	Success bool `json:"success"`
}

func main() {
	router := gin.Default()
	api := humagin.New(router, huma.DefaultConfig("My API", "1.0.0"))

	huma.Register(api, huma.Operation{
		Method: http.MethodDelete,
		Path:   "/api/companies/users",
		OperationID: "delete-users-v1",
		Summary:     "Deletes a list of users (v1)",
		Description: "This endpoint deletes a list of users. It is authenticated and requires a valid access token.",
		Tags:        []string{"users"},
	}, UserDeleteHandler)

	http.ListenAndServe(":8888", router)
}

func UserDeleteHandler(ctx context.Context, input *UserDeleteInput) (*DeleteResponse, error) {
	users := input.Body.UserIDs
	slog.Info("Users received for deletion", "users", users)

	out := &DeleteResponse{
		Success: true,
	}
	slog.Info("success")
	return out, nil
}

Run the server.

go run main.go

Open a browser and head to http://localhost:8888/docs#/operations/delete-users-v1. Send a request via the OpenAPI UI. The request will fail with the error:

{
  "$schema": "http://localhost:8888/schemas/ErrorModel.json",
  "title": "Bad Request",
  "status": 400,
  "detail": "request body is required"
}

Send requests to the server via curl. The request succeeds.

curl -X DELETE http://localhost:8888/api/companies/users -H "Content-Type: application/json" -d '{"user_ids": ["123-456"]}' -v
* Host localhost:8888 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8888...
* Connected to localhost (::1) port 8888
> DELETE /api/companies/users HTTP/1.1
> Host: localhost:8888
> User-Agent: curl/8.7.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 25
> 
* upload completely sent off: 25 bytes
< HTTP/1.1 204 No Content
< Success: true
< Date: Wed, 27 Aug 2025 09:35:26 GMT
< 
* Connection #0 to host localhost left intact

DoGab avatar Aug 27 '25 09:08 DoGab

I'd be interested to have a first look at this.

+1 for this framework. It has really shaped the way we communicate our APIs in the organization. Thank you ❤️

els0r avatar Aug 27 '25 09:08 els0r

Huma doesn't have any UI natively. Under the hood, it uses Stoplight Elements and i have created a merge request there already

sjayach avatar Sep 18 '25 20:09 sjayach

Same issue, is there a workaround until @sjayach MR is merged and a new version is released ?

MathieuCesbron avatar Dec 15 '25 15:12 MathieuCesbron

@MathieuCesbron Yes. It's possible

Clone or Download https://github.com/sjayach/elements/blob/add-delete-body and run below commands

yarn install 
yarn run build

Copy below files from elements-add-delete-body/packages/elements/dist to your golang codebase under stoplight directory

index.esm.js
index.js
index.mjs
styles.min.css
web-components.min.js

And static directory hosting as part of your own router. ( below code uses mux router)

	staticDir := "stoplight/" // or wherever your static files are located
	r.PathPrefix("/stoplight/").Handler(http.StripPrefix("/stoplight/", http.FileServer(http.Dir(staticDir)))).Methods(http.MethodGet)

	r.HandleFunc("/docs", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/html")
		w.Write([]byte(`<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="referrer" content="same-origin" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>Docs Example reference</title>
    <!-- Embed elements Elements via Web Component -->
    <link href="https://unpkg.com/@stoplight/[email protected]/styles.min.css" rel="stylesheet" />
    <script src="stoplight/web-components.min.js"
            crossorigin="anonymous"></script>
  </head>
  <body style="height: 100vh;">
    <elements-api
      apiDescriptionUrl="/openapi.yaml"
      router="hash"
      layout="sidebar"
      tryItCredentialsPolicy="same-origin"
    />
  </body>
</html>`))
	}).Methods(http.MethodGet)

this will use local stoplight with above MR changes.

sjayach avatar Dec 19 '25 17:12 sjayach