api-endpoint-creator
新しいAPIエンドポイントを追加する際に使用。Huma API、Echo、Handler/Service/Repositoryの3層構成、エンドポイント登録パターンを適用。REST API、新規エンドポイント、CRUD APIを追加する場合に使用。
When & Why to Use This Skill
This Claude skill automates the creation of RESTful API endpoints following a structured 3-layer architecture (Handler, Service, Repository). It leverages the Echo and Huma v2 frameworks to ensure OpenAPI compliance and consistent code patterns, significantly reducing boilerplate and speeding up backend development in Go.
Use Cases
- Scaffolding new CRUD API endpoints for database entities while maintaining architectural consistency.
- Implementing Huma v2 input/output type definitions to ensure automatic and accurate OpenAPI documentation generation.
- Generating the 3-layer code structure (Handler, Service, Repository) for new business logic features.
- Registering new routes and handlers within an existing Echo/Huma router configuration following established project patterns.
| name | api-endpoint-creator |
|---|---|
| description | 新しいAPIエンドポイントを追加する際に使用。Huma API、Echo、Handler/Service/Repositoryの3層構成、エンドポイント登録パターンを適用。REST API、新規エンドポイント、CRUD APIを追加する場合に使用。 |
| allowed-tools | Read, Glob |
API エンドポイント作成パターン
このプロジェクトのAPI実装パターンを定義します。
アーキテクチャ
3層構成:
Handler (API層) → Service (ビジネスロジック) → Repository (データアクセス)
使用フレームワーク
- Echo: HTTPルーター
- Huma v2: OpenAPI対応のAPIフレームワーク
- humaecho: EchoとHumaのアダプター
ディレクトリ構成
server/internal/
├── api/
│ ├── handler/
│ │ ├── user_handler.go
│ │ └── post_handler.go
│ ├── huma/
│ │ └── types.go # Huma用の入出力型定義
│ └── router/
│ └── router.go # ルーター設定
├── service/
│ ├── user_service.go
│ └── post_service.go
└── repository/
├── user_repository.go
└── post_repository.go
参照ファイル
Handler:
server/internal/api/handler/user_handler.goserver/internal/api/handler/post_handler.go
Huma型定義:
server/internal/api/huma/types.go
ルーター:
server/internal/api/router/router.go
Service:
server/internal/service/user_service.go
Handler パターン
構造体定義
package handler
import (
"context"
"net/http"
"github.com/danielgtaylor/huma/v2"
humaapi "github.com/example/go-webdb-template/internal/api/huma"
"github.com/example/go-webdb-template/internal/model"
"github.com/example/go-webdb-template/internal/service"
)
// EntityHandler はエンティティAPIのハンドラー
type EntityHandler struct {
entityService *service.EntityService
}
// NewEntityHandler は新しいEntityHandlerを作成
func NewEntityHandler(entityService *service.EntityService) *EntityHandler {
return &EntityHandler{
entityService: entityService,
}
}
エンドポイント登録
// RegisterEntityEndpoints はHuma APIにエンティティエンドポイントを登録
func RegisterEntityEndpoints(api huma.API, h *EntityHandler) {
// POST /api/entities - エンティティ作成
huma.Register(api, huma.Operation{
OperationID: "create-entity",
Method: http.MethodPost,
Path: "/api/entities",
Summary: "エンティティを作成",
Tags: []string{"entities"},
DefaultStatus: http.StatusCreated,
}, func(ctx context.Context, input *humaapi.CreateEntityInput) (*humaapi.EntityOutput, error) {
req := &model.CreateEntityRequest{
Name: input.Body.Name,
}
entity, err := h.entityService.CreateEntity(ctx, req)
if err != nil {
return nil, huma.Error500InternalServerError(err.Error())
}
resp := &humaapi.EntityOutput{}
resp.Body = *entity
return resp, nil
})
// GET /api/entities/{id} - エンティティ取得
huma.Register(api, huma.Operation{
OperationID: "get-entity",
Method: http.MethodGet,
Path: "/api/entities/{id}",
Summary: "エンティティを取得",
Tags: []string{"entities"},
}, func(ctx context.Context, input *humaapi.GetEntityInput) (*humaapi.EntityOutput, error) {
entity, err := h.entityService.GetEntity(ctx, input.ID)
if err != nil {
return nil, huma.Error404NotFound(err.Error())
}
resp := &humaapi.EntityOutput{}
resp.Body = *entity
return resp, nil
})
// GET /api/entities - エンティティ一覧取得
huma.Register(api, huma.Operation{
OperationID: "list-entities",
Method: http.MethodGet,
Path: "/api/entities",
Summary: "エンティティ一覧を取得",
Tags: []string{"entities"},
}, func(ctx context.Context, input *humaapi.ListEntitiesInput) (*humaapi.EntitiesOutput, error) {
entities, err := h.entityService.ListEntities(ctx, input.Limit, input.Offset)
if err != nil {
return nil, huma.Error500InternalServerError(err.Error())
}
resp := &humaapi.EntitiesOutput{}
resp.Body = entities
return resp, nil
})
// PUT /api/entities/{id} - エンティティ更新
huma.Register(api, huma.Operation{
OperationID: "update-entity",
Method: http.MethodPut,
Path: "/api/entities/{id}",
Summary: "エンティティを更新",
Tags: []string{"entities"},
}, func(ctx context.Context, input *humaapi.UpdateEntityInput) (*humaapi.EntityOutput, error) {
req := &model.UpdateEntityRequest{
Name: input.Body.Name,
}
entity, err := h.entityService.UpdateEntity(ctx, input.ID, req)
if err != nil {
return nil, huma.Error500InternalServerError(err.Error())
}
resp := &humaapi.EntityOutput{}
resp.Body = *entity
return resp, nil
})
// DELETE /api/entities/{id} - エンティティ削除
huma.Register(api, huma.Operation{
OperationID: "delete-entity",
Method: http.MethodDelete,
Path: "/api/entities/{id}",
Summary: "エンティティを削除",
Tags: []string{"entities"},
DefaultStatus: http.StatusNoContent,
}, func(ctx context.Context, input *humaapi.DeleteEntityInput) (*struct{}, error) {
err := h.entityService.DeleteEntity(ctx, input.ID)
if err != nil {
return nil, huma.Error500InternalServerError(err.Error())
}
return nil, nil
})
}
Huma 入出力型パターン
// server/internal/api/huma/types.go
// 入力型
type CreateEntityInput struct {
Body struct {
Name string `json:"name" required:"true"`
Email string `json:"email" required:"true"`
}
}
type GetEntityInput struct {
ID int64 `path:"id"`
}
type ListEntitiesInput struct {
Limit int `query:"limit" default:"10"`
Offset int `query:"offset" default:"0"`
}
type UpdateEntityInput struct {
ID int64 `path:"id"`
Body struct {
Name string `json:"name"`
Email string `json:"email"`
}
}
type DeleteEntityInput struct {
ID int64 `path:"id"`
}
// 出力型
type EntityOutput struct {
Body model.Entity
}
type EntitiesOutput struct {
Body []*model.Entity
}
Service パターン
package service
import (
"context"
"github.com/example/go-webdb-template/internal/model"
"github.com/example/go-webdb-template/internal/repository"
)
type EntityService struct {
entityRepo *repository.EntityRepository
}
func NewEntityService(entityRepo *repository.EntityRepository) *EntityService {
return &EntityService{
entityRepo: entityRepo,
}
}
func (s *EntityService) CreateEntity(ctx context.Context, req *model.CreateEntityRequest) (*model.Entity, error) {
return s.entityRepo.Create(ctx, req)
}
func (s *EntityService) GetEntity(ctx context.Context, id int64) (*model.Entity, error) {
return s.entityRepo.GetByID(ctx, id)
}
// ListEntities, UpdateEntity, DeleteEntity も同様...
ルーターへの登録
router.go でエンドポイントを登録:
// Humaエンドポイントの登録
handler.RegisterUserEndpoints(humaAPI, userHandler)
handler.RegisterPostEndpoints(humaAPI, postHandler)
handler.RegisterEntityEndpoints(humaAPI, entityHandler) // 新規追加
エラーレスポンス
// 404 Not Found
return nil, huma.Error404NotFound(err.Error())
// 500 Internal Server Error
return nil, huma.Error500InternalServerError(err.Error())
// 400 Bad Request
return nil, huma.Error400BadRequest("Invalid input")