Loading...
Loading...
Generate Chi HTTP handlers following GO modular architechture conventions (request/response DTOs, use case orchestration, error handling, swagger annotations, Fx DI). Use when creating HTTP endpoint handlers in internal/modules/<module>/http/chi/handler/ for REST operations (List, Create, Update, Delete, Get) that need to decode requests, call use cases, map responses, and handle errors with proper logging and tracing.
npx skill4agent add cristiano-pacheco/ai-rules go-create-chi-handlerinternal/modules/<module>/http/chi/handler/<resource>_handler.gopackage handler
import (
"net/http"
"strconv"
"github.com/cristiano-pacheco/bricks/pkg/http/request"
"github.com/cristiano-pacheco/bricks/pkg/http/response"
"github.com/cristiano-pacheco/bricks/pkg/logger"
"github.com/cristiano-pacheco/pingo/internal/modules/<module>/http/dto"
"github.com/cristiano-pacheco/pingo/internal/modules/<module>/usecase"
"github.com/go-chi/chi/v5"
)
type ResourceHandler struct {
resourceCreateUseCase *usecase.ResourceCreateUseCase
resourceListUseCase *usecase.ResourceListUseCase
resourceUpdateUseCase *usecase.ResourceUpdateUseCase
resourceDeleteUseCase *usecase.ResourceDeleteUseCase
errorHandler response.ErrorHandler
logger logger.Logger
}
func NewResourceHandler(
resourceCreateUseCase *usecase.ResourceCreateUseCase,
resourceListUseCase *usecase.ResourceListUseCase,
resourceUpdateUseCase *usecase.ResourceUpdateUseCase,
resourceDeleteUseCase *usecase.ResourceDeleteUseCase,
errorHandler response.ErrorHandler,
logger logger.Logger,
) *ResourceHandler {
return &ResourceHandler{
resourceCreateUseCase: resourceCreateUseCase,
resourceListUseCase: resourceListUseCase,
resourceUpdateUseCase: resourceUpdateUseCase,
resourceDeleteUseCase: resourceDeleteUseCase,
errorHandler: errorHandler,
logger: logger,
}
}internal/modules/<module>/http/dto/<resource>_dto.gopackage dto
type CreateResourceRequest struct {
Field1 string `json:"field1"`
Field2 int `json:"field2"`
}
type CreateResourceResponse struct {
ID uint64 `json:"id"`
Field1 string `json:"field1"`
Field2 int `json:"field2"`
}
type UpdateResourceRequest struct {
Field1 string `json:"field1"`
Field2 int `json:"field2"`
}
type ResourceResponse struct {
ID uint64 `json:"id"`
Field1 string `json:"field1"`
Field2 int `json:"field2"`
}// @Summary List resources
// @Description Retrieves all resources
// @Tags Resources
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} response.Envelope[[]dto.ResourceResponse] "Successfully retrieved resources"
// @Failure 401 {object} errs.Error "Invalid credentials"
// @Failure 500 {object} errs.Error "Internal server error"
// @Router /api/v1/resources [get]
func (h *ResourceHandler) ListResources(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
output, err := h.resourceListUseCase.Execute(ctx)
if err != nil {
h.logger.Error("failed to list resources", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
resources := make([]dto.ResourceResponse, len(output.Resources))
for i, resource := range output.Resources {
resources[i] = dto.ResourceResponse{
ID: resource.ID,
Name: resource.Name,
// ... map other fields
}
}
err = response.JSON(w, http.StatusOK, resources, http.Header{})
if err != nil {
h.logger.Error("failed to write response", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
}// @Summary Create resource
// @Description Creates a new resource
// @Tags Resources
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body dto.CreateResourceRequest true "Resource data"
// @Success 201 {object} response.Envelope[dto.CreateResourceResponse] "Successfully created resource"
// @Failure 422 {object} errs.Error "Invalid request format or validation error"
// @Failure 401 {object} errs.Error "Invalid credentials"
// @Failure 500 {object} errs.Error "Internal server error"
// @Router /api/v1/resources [post]
func (h *ResourceHandler) CreateResource(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var createRequest dto.CreateResourceRequest
err := request.ReadJSON(w, r, &createRequest)
if err != nil {
h.logger.Error("failed to parse request body", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
input := usecase.ResourceCreateInput{
Name: createRequest.Name,
// ... map other fields
}
output, err := h.resourceCreateUseCase.Execute(ctx, input)
if err != nil {
h.logger.Error("failed to create resource", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
createResponse := dto.CreateResourceResponse{
ID: output.ID,
Name: output.Name,
// ... map other fields
}
err = response.JSON(w, http.StatusCreated, createResponse, http.Header{})
if err != nil {
h.logger.Error("failed to write response", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
}// @Summary Update resource
// @Description Updates an existing resource
// @Tags Resources
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Resource ID"
// @Param request body dto.UpdateResourceRequest true "Resource data"
// @Success 204 "Successfully updated resource"
// @Failure 422 {object} errs.Error "Invalid request format or validation error"
// @Failure 401 {object} errs.Error "Invalid credentials"
// @Failure 404 {object} errs.Error "Resource not found"
// @Failure 500 {object} errs.Error "Internal server error"
// @Router /api/v1/resources/{id} [put]
func (h *ResourceHandler) UpdateResource(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var updateRequest dto.UpdateResourceRequest
err := request.ReadJSON(w, r, &updateRequest)
if err != nil {
h.logger.Error("failed to parse request body", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
h.logger.Error("invalid resource ID", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
input := usecase.ResourceUpdateInput{
ID: id,
Name: updateRequest.Name,
// ... map other fields
}
err = h.resourceUpdateUseCase.Execute(ctx, input)
if err != nil {
h.logger.Error("failed to update resource", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
response.NoContent(w)
}// @Summary Delete resource
// @Description Deletes an existing resource
// @Tags Resources
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Resource ID"
// @Success 204 "Successfully deleted resource"
// @Failure 401 {object} errs.Error "Invalid credentials"
// @Failure 404 {object} errs.Error "Resource not found"
// @Failure 500 {object} errs.Error "Internal server error"
// @Router /api/v1/resources/{id} [delete]
func (h *ResourceHandler) DeleteResource(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
h.logger.Error("invalid resource ID", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
input := usecase.ResourceDeleteInput{
ID: id,
}
err = h.resourceDeleteUseCase.Execute(ctx, input)
if err != nil {
h.logger.Error("failed to delete resource", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
response.NoContent(w)
}// @Summary Get resource
// @Description Retrieves a resource by ID
// @Tags Resources
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Resource ID"
// @Success 200 {object} response.Envelope[dto.ResourceResponse] "Successfully retrieved resource"
// @Failure 401 {object} errs.Error "Invalid credentials"
// @Failure 404 {object} errs.Error "Resource not found"
// @Failure 500 {object} errs.Error "Internal server error"
// @Router /api/v1/resources/{id} [get]
func (h *ResourceHandler) GetResource(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
idStr := chi.URLParam(r, "id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
h.logger.Error("invalid resource ID", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
input := usecase.ResourceGetInput{
ID: id,
}
output, err := h.resourceGetUseCase.Execute(ctx, input)
if err != nil {
h.logger.Error("failed to get resource", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
resourceResponse := dto.ResourceResponse{
ID: output.ID,
Name: output.Name,
// ... map other fields
}
err = response.JSON(w, http.StatusOK, resourceResponse, http.Header{})
if err != nil {
h.logger.Error("failed to write response", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
}jsonjsonBearerAuth@Param id path int true "Resource ID"@Param request body dto.CreateResourceRequest true "Resource data"{object} response.Envelope[dto.ResourceResponse]{object} response.Envelope[dto.CreateResourceResponse]{object} errs.Error/api/v1/resources/{id} [method]internal/modules/<module>/http/dto// Decode request
var req dto.CreateResourceRequest
err := request.ReadJSON(w, r, &req)
if err != nil {
h.logger.Error("failed to parse request body", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
// Map to use case input
input := usecase.ResourceCreateInput{
Field1: req.Field1,
Field2: req.Field2,
}// Execute use case
output, err := h.resourceCreateUseCase.Execute(ctx, input)
if err != nil {
h.logger.Error("failed to create resource", logger.Error(err))
h.errorHandler.Error(w, err)
return
}
// Map to response DTO
response := dto.CreateResourceResponse{
ID: output.ID,
Field1: output.Field1,
Field2: output.Field2,
}if err != nil {
h.logger.Error("descriptive error message", logger.Error(err))
h.errorHandler.Error(w, err)
return
}internal/modules/<module>/fx.gofx.Provide(handler.NewResourceHandler),response.ErrorHandlerlogger.Logger*ResourceHandlerctx := r.Context()request.ReadJSON(w, r, &dto)chi.URLParam(r, "paramName")strconv.ParseUinth.errorHandler.Error(w, err)response.JSON(w, http.StatusOK, data, http.Header{})response.JSON(w, http.StatusCreated, data, http.Header{})response.NoContent(w)make lintmake update-swaggerNewResourceHandlerfx.gomake lintmake nilawaymake update-swagger