A Flask-RESTful inspired library for building RESTful APIs with Gin. Implement only the HTTP methods you need — routes are registered automatically.
- Interface Segregation — Six independent interfaces (
Lister,Getter,Poster,Putter,Patcher,Deleter). Only implemented methods become routes. - Type-Safe Binding — Generic
Bind[T]andMustBind[T]helpers for compile-time safe request body parsing. - Structured Error Handling —
Abort()helper andHTTPErrortype for clean error responses, inspired by Flask-RESTful'sabort(). - Gin Compatible — Works with
gin.Engine,gin.RouterGroup, middleware, and all standard Gin features.
go get github.com/hwangseonu/gin-restfulpackage main
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
"github.com/hwangseonu/gin-restful"
)
type CreateTodoReq struct {
Title string `json:"title" binding:"required"`
}
type Todo struct {
ID string `json:"id"`
Title string `json:"title"`
}
type TodoResource struct {
todos map[string]Todo
}
// GET /todos — implement Lister
func (r *TodoResource) List(c *gin.Context) (any, int, error) {
items := make([]Todo, 0)
for _, t := range r.todos {
items = append(items, t)
}
return items, http.StatusOK, nil
}
// GET /todos/:id — implement Getter
func (r *TodoResource) Get(id string, c *gin.Context) (any, int, error) {
todo, ok := r.todos[id]
if !ok {
return nil, 0, restful.Abort(http.StatusNotFound, "todo not found")
}
return todo, http.StatusOK, nil
}
// POST /todos — implement Poster
func (r *TodoResource) Post(c *gin.Context) (any, int, error) {
body, err := restful.Bind[CreateTodoReq](c)
if err != nil {
return nil, 0, restful.Abort(http.StatusBadRequest, err.Error())
}
todo := Todo{ID: "1", Title: body.Title}
r.todos[todo.ID] = todo
return todo, http.StatusCreated, nil
}
// DELETE /todos/:id — implement Deleter
func (r *TodoResource) Delete(id string, c *gin.Context) (any, int, error) {
delete(r.todos, id)
return nil, http.StatusNoContent, nil
}
// PUT and PATCH are not implemented — those routes won't be registered.
func main() {
engine := gin.Default()
api := restful.NewAPI(engine, "/api/v1")
api.AddResource("/todos", &TodoResource{todos: make(map[string]Todo)})
log.Fatal(engine.Run(":8080"))
}AddResource("/todos", resource) registers only the routes for implemented interfaces:
| Method | Endpoint | Interface |
|---|---|---|
GET |
/api/v1/todos |
Lister |
GET |
/api/v1/todos/:id |
Getter |
POST |
/api/v1/todos |
Poster |
DELETE |
/api/v1/todos/:id |
Deleter |
PUT and PATCH are not registered because the resource doesn't implement Putter or Patcher.
Each HTTP method has its own interface. Implement only what you need:
type Lister interface { List(c *gin.Context) (any, int, error) } // GET /path
type Getter interface { Get(id string, c *gin.Context) (any, int, error) } // GET /path/:id
type Poster interface { Post(c *gin.Context) (any, int, error) } // POST /path
type Putter interface { Put(id string, c *gin.Context) (any, int, error) } // PUT /path/:id
type Patcher interface { Patch(id string, c *gin.Context) (any, int, error) } // PATCH /path/:id
type Deleter interface { Delete(id string, c *gin.Context) (any, int, error) } // DELETE /path/:idAll methods return (any, int, error) — the response body, HTTP status code, and an optional error.
Returns the parsed body and an error for custom handling:
body, err := restful.Bind[CreateTodoReq](c)
if err != nil {
return nil, 0, restful.Abort(http.StatusBadRequest, err.Error())
}Automatically responds with 400 Bad Request and aborts on failure:
body := restful.MustBind[CreateTodoReq](c)
if body == nil {
return nil, 0, nil // already aborted
}Both use Gin's ShouldBind, so the binding method is determined by the Content-Type header (JSON, form, XML, etc.).
Use Abort to return structured error responses:
return nil, 0, restful.Abort(http.StatusNotFound, "resource not found")
// Response: 404 {"message": "resource not found"}*HTTPErrorerrors produce their status code and message as JSON.- Other errors produce
500 {"message": "internal server error"}— internal details are never leaked to clients.
NewAPI accepts gin.IRouter, so it works with route groups and middleware:
engine := gin.Default()
// Public routes — regular Gin handlers
engine.GET("/health", healthHandler)
// Protected routes — restful resources with auth middleware
authorized := engine.Group("/", authMiddleware())
api := restful.NewAPI(authorized, "/api/v1")
api.AddResource("/tasks", &TaskResource{})See example/hybrid/ for a complete example.
| Example | Description |
|---|---|
example/basic/ |
Simple CRUD with List, Get, Post, Put, Delete |
example/complex/ |
Multiple resources, search, pagination, PUT/PATCH separation, relation validation |
example/hybrid/ |
RESTful resources + regular Gin handlers, auth middleware, route groups |
This project is licensed under the MIT License. See the LICENSE file for details.