resource
is a Go package that simplifies the creation of REST APIs for your database models. It automatically
generates RESTful endpoints with CRUD operations for your GORM models, with support for multiple router frameworks,
access control, and frontend integration. It also automatically generates your docs via OpenAPI
- Automatic CRUD endpoints for your GORM models
- Multiple router integrations:
- Flexible access control:
- Role-Based Access Control (RBAC)
- Access Control Lists (ACL)
- Field-level control for showing/hiding fields based on user roles
- Request validation with customizable validation rules
- Custom query operations for filtering, sorting, and pagination
- Lifecycle hooks for customizing behavior at different stages
- Frontend integration with JavaScript/React hooks
- OpenAPI integration API documentation is auto generated from your model definitions
go get github.com/restk/resource
If you don't need Role-Based Access Control, you can omit the RBAC struct.
package main
import (
"context"
"log"
"net/http"
"github.com/gin-gonic/gin"
"github.com/restk/openapi"
"github.com/restk/resource"
"github.com/restk/resource/access"
ginRouter "github.com/restk/resource/router/gin"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// RBAC should implement the access.RBAC interface.
type RBAC struct{}
func (r *RBAC) HasPermission(ctx context.Context, resource string, permission access.Permission) bool {
panic("not implemented")
}
func (r *RBAC) HasRole(ctx context.Context, role string) bool {
panic("not implemented")
}
type User struct {
gorm.Model
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password,omitempty"`
}
func main() {
// Setup your database connection.
db, err := gorm.Open(postgres.Open("host=127.0.0.1 user=db password=db dbname=db port=5433 sslmode=disable"))
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
// Create a Gin router.
mux := gin.Default()
group := mux.Group("/")
router := ginRouter.NewRouter(group)
// Initialize OpenAPI for docs.
oapi := openapi.New("Your Project", "1.0.0")
// Create a resource definition for the User model.
users := resource.NewResource[User]("user", "id")
// Specify RBAC to control access to the resource.
rbac := &RBAC{}
users.EnableRBAC(rbac)
// Generate REST API handlers and documentation for this resource.
if err = users.GenerateRestAPI(router, db, oapi); err != nil {
log.Fatalf("Failed to generate rest API for user resource: %v", err)
}
// Start the server.
http.ListenAndServe(":8080", mux)
}
This example sets up a REST API for a User model with the following endpoints:
GET /users
- List all usersGET /users/:id
- Get a specific userPOST /users
- Create a new userPUT /users/:id
- Update a userPATCH /users/:id
- Patch a userDELETE /users/:id
- Delete a user
A resource represents a database model with RESTful operations. Create a resource using resource.New[T]()
:
users := resource.NewResource[User]("user", "id")
This will use the users
table, with id
as the primary key (utilizing the struct's JSON tags).
The package supports multiple router frameworks through adapters.
package main
import (
"github.com/gin-gonic/gin"
ginRouter "github.com/restk/resource/router/gin"
)
func main() {
mux := gin.Default()
group := mux.Group("/")
ginRouter.NewRouter(group)
}
package main
import (
"github.com/go-chi/chi/v5"
chiRouter "github.com/restk/resource/router/chi"
)
func main() {
mux := chi.NewRouter()
chiRouter.NewRouter(mux, "")
}
Customize behavior at different lifecycle stages:
package main
import (
"github.com/restk/resource"
"github.com/restk/resource/access"
resourcerouter "github.com/restk/resource/router"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password,omitempty"`
}
func main() {
// Create a resource definition for the User model.
users := resource.NewResource[User]("user", "id")
users.BeforeSave(access.PermissionCreate, func(ctx resourcerouter.Context, user *User) error {
// Hash password before creating user.
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
user.Password = string(hashedPassword)
return nil
})
}
Define relationships between resources:
package main
import (
"github.com/globalcyberalliance/aide/pkg/model"
"github.com/restk/resource"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password,omitempty"`
}
type UserAPIKey struct {
gorm.Model
Key string `json:"key"`
UserID uint `json:"userID"`
}
func main() {
// Create a resource definition for the User model.
users := resource.NewResource[User]("user", "id")
userAPIKeys := resource.NewResource[model.UserAPIKey]("key", "id")
userAPIKeys.BelongsTo(users, "UserID")
}
When generating the REST endpoints for userAPIKeys
, they'll be routed under /users/:userID/keys/:id
.
Clients can request pages using ?page=2
or ?limit=20&offset=40
.
The list endpoint such as GET /users
has query params for filtering, you can use the JSON tag name or the field name such as GET /users?name=tom&age=21
.
For any query param, you can add an operation suffix to change the operator, for example, you could do GET /users?nameLike=%tom%&ageGte=21
you can also do ranges such as /users?createdAtGte=?&createdAtLte=?
Suffix | SQL Operator | Description |
---|---|---|
Gt |
> |
Greater than |
Gte |
>= |
Greater than or equal to |
Lt |
< |
Less than |
Lte |
<= |
Less than or equal to |
Ne |
!= |
Not equal to |
Like |
LIKE |
Pattern match |
The package includes a React hook for easy frontend integration:
// From the javascript/useResource.js file
import {useResource} from '@restk/resource';
function UserList() {
let {
list,
get,
create,
update,
patch,
remove,
} = useResource("users", { pageSize: 10 });
let users = list.data || [];
useEffect(() => {
list.fetch({
ageGte: 21,
nameLike: "%a%"
})
}, [list.page]);
if (list.loading) {
return (<div>Loading...</div>)
}
if (list.error) {
return (<div>Error: {list.error.message}</div>)
}
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
MIT
Contributions are welcome! Please feel free to submit a Pull Request.