Claude Code Plugins

Community-maintained marketplace

Feedback

api-endpoint-creator

@taku-o/go-webdb-template
0
0

新しいAPIエンドポイントを追加する際に使用。Huma API、Echo、Handler/Service/Repositoryの3層構成、エンドポイント登録パターンを適用。REST API、新規エンドポイント、CRUD APIを追加する場合に使用。

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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.go
  • server/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")