Go/Gin MVC generation skill for WorkHub with strict Controller -> Service -> DAO conventions. Use when creating/modifying endpoints, services, dao, JWT auth, models, or fixing/refactoring Go backend code.
Generate Go backend code that is consistent, maintainable, and predictable.
This skill enforces:
backend/
├── main.go # Entry point (db connect + app start)
├── app/
│ ├── config.go # Logging setup
│ ├── router.go # URL mappings
│ └── app.go # Router + middleware setup
├── config/ # .env configuration
├── controllers/ # HTTP handlers (thin)
│ ├── auth/
│ ├── spaces/
│ └── bookings/
├── services/ # Business logic (thick)
├── dao/ # Database operations
├── models/ # GORM structs
├── dto/
│ ├── request/
│ └── response/
├── middleware/ # JWT, role guards, cross-cutting middleware
└── db/ # DB connection and setup
Use this convention for startup files so every backend keeps the same initialization flow.
package app
import (
"os"
log "github.com/sirupsen/logrus"
)
func init() {
log.SetOutput(os.Stdout)
// log.SetFormatter(&log.JSONFormatter{}) // enable in production if needed
log.SetLevel(log.DebugLevel)
log.Info("Starting logger system")
}
package app
import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
var (
router *gin.Engine
)
func init() {
router = gin.Default()
router.Use(cors.Default())
router.Static("/static", "./static") // optional static files
}
func StartRoute() {
mapUrls()
log.Info("Starting server")
router.Run(":8090")
}
package app
import (
entityController "project/controllers/entity"
log "github.com/sirupsen/logrus"
)
func mapUrls() {
// Entity mapping
router.GET("/entity/:id", entityController.GetEntityById)
router.GET("/entities", entityController.GetEntities)
router.POST("/entity", entityController.EntityInsert)
router.DELETE("/entity/:id", entityController.DeleteEntityById)
log.Info("Finishing mappings configurations")
}
Bootstrap rules:
app_config.go.router.go.url_mappings.go.init logger -> init router -> map urls -> run server.user, hotel, booking, amenitie, etc.) and keep naming consistent.Use generic signatures only to show shape and flow. These are templates, not project-specific implementations.
func GetEntityById(c *gin.Context) {
log.Debug("Entity id to load: " + c.Param("id"))
id, _ := strconv.Atoi(c.Param("id")) // id is expected numeric
var entityDto dto.EntityDto
entityDto, err := service.EntityService.GetEntityById(id)
if err != nil {
c.JSON(err.Status(), err)
return
}
c.JSON(http.StatusOK, entityDto)
}
func GetEntityById(id int) model.Entity {
var entity model.Entity
Db.Where("id = ?", id).First(&entity)
log.Debug("Entity: ", entity)
return entity
}
func (a *entityService) GetEntityById(id int) (dto.EntityDto, e.ApiError) {
var entity model.Entity = entityDAO.GetEntityById(id)
var entityDto dto.EntityDto
if entity.Id == 0 {
return entityDto, e.NewBadRequestApiError("resource not found")
}
entityDto.Id = entity.Id
entityDto.Name = entity.Name
entityDto.Description = entity.Description
return entityDto, nil
}
Use this same structure for any domain entity and preserve function flow order.
Use DTOs to define API contracts, not database schema.
type CreateEntityRequest struct {
Name string `json:"name" binding:"required,min=3,max=80"`
Description string `json:"description" binding:"max=255"`
Capacity int `json:"capacity" binding:"required,gte=1"`
HourlyRate float64 `json:"hourly_rate" binding:"required,gte=0"`
}
type EntityDto struct {
Id int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Capacity int `json:"capacity"`
HourlyRate float64 `json:"hourly_rate"`
}
Rules for DTOs:
dto/request.dto/response (or shared dto package if project uses that convention).Use models to represent persistence and relations.
type Entity struct {
Id int `gorm:"primaryKey;autoIncrement"`
Name string `gorm:"size:80;not null"`
Description string `gorm:"size:255"`
Capacity int `gorm:"not null"`
HourlyRate float64 `gorm:"not null"`
CreatedAt time.Time
UpdatedAt time.Time
}
Rules for Models:
models/.e.ApiError when business flow fails.When creating a new endpoint, execute in this order:
dto/request).dto/response or entity dto).app/router.go with correct middleware group.go test ./....Default behavior for generated code:
(result, e.ApiError).c.JSON(err.Status(), err).entity.Id == 0 in service.Prefer these statuses:
400: invalid input / business precondition failed.401: missing or invalid token.403: authenticated but forbidden.404: resource not found (if project uses this semantic).500: unexpected infrastructure failure.Use golang-jwt/jwt/v5 and keep auth logic in dedicated auth service/helpers.
Checklist:
Always check and handle errors immediately after each operation:
| Layer | Pattern | Example |
|---|---|---|
| Model | <Entity> | User, Space |
| DAO | <entity>DAO | spaceDAO |
| Service | <entity>Service | spaceService |
| Controller | <entity>Controller | spaceController |
| DTO Request | <Action><Entity>Request | CreateSpaceRequest |
| DTO Response | <Entity>Dto | EntityDto |
New endpoint? → controller + route
Business logic? → service
DB operation? → dao
Validation? → dto/request + binding
Response format? → dto/response
New model? → models/
Use three route groups in router mapping:
AuthMiddleware().AuthMiddleware() + AdminOnly().go mod init workhub
go get github.com/gin-gonic/gin
go get gorm.io/gorm gorm.io/driver/mysql
go get github.com/golang-jwt/jwt/v5
go get github.com/joho/godotenv
go run main.go
go test ./...
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /auth/register | - | Register |
| POST | /auth/login | - | Login |
| GET | /spaces | - | List |
| GET | /spaces/:id | - | Detail |
| POST | /bookings | JWT | Create |
| GET | /my-bookings | JWT | List |
| POST | /admin/spaces | JWT+Admin | Create |
| PUT | /admin/spaces/:id | JWT+Admin | Update |
| DELETE | /admin/spaces/:id | JWT+Admin | Delete |