/gormpher

Generate restful APIs by defining struct model, based on Gin and Gorm, with a optional web interface for administrator.

Primary LanguageGoBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

Gormpher - Generate restful APIs by defining struct model, based on Gin and Gorm

Quick Start

go get github.com/restsend/gormpher

reference to example

package main

import (
	"errors"
	"flag"
	"math/rand"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/restsend/gormpher"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

type User struct {
	ID        uint       `json:"id" gorm:"primarykey"`
	CreatedAt time.Time  `json:"createdAt"`
	UpdatedAt time.Time  `json:"updatedAt"`
	Name      string     `json:"name"`
	Age       int        `json:"age"`
	Enabled   bool       `json:"enabled"`
	LastLogin *time.Time `json:"lastLogin"`
}

type Product struct {
	UUID      string    `json:"id" gorm:"primarykey"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
	GroupID   int       `json:"-"`
	Group     Group     `json:"group" gorm:"foreignKey:GroupID;references:ID"` // association
	Name      string    `json:"name"`
	Enabled   bool      `json:"enabled"`
}

type Group struct {
	ID   uint   `json:"id" gorm:"primarykey"`
	Name string `json:"name"`
}

func main() {
	var dsn string
	var addr string

	flag.StringVar(&dsn, "n", "", "DB DSN")
	flag.StringVar(&addr, "a", ":8890", "Api Server Addr")
	flag.Parse()

	db, _ := gorm.Open(sqlite.Open(dsn), nil)
	db.AutoMigrate(Product{}, User{})

	r := gin.Default()

	objs := GetWebObjects(db)
	// visit API: http://localhost:8890/api
	gormpher.RegisterObjects(r, objs)
	// visit Admin: http://localhost:8890/admin
	gormpher.RegisterObjectsWithAdmin(r.Group("admin"), objs)

	r.Run(addr)
}

func GetWebObjects(db *gorm.DB) []gormpher.WebObject {
	return []gormpher.WebObject{
		// Basic Demo
		// Check API File: user.http
		// PUT 		http://localhost:8890/user
		// GET 		http://localhost:8890/user/:key
		// PATCH	http://localhost:8890/user/:key
		// POST 	http://localhost:8890/user
		// DELETE http://localhost:8890/user/:key
		// DELETE http://localhost:8890/user
		{
			Name:         "user",
			Model:        &User{},
			SearchFields: []string{"Name", "Enabled"},
			EditFields:   []string{"Name", "Age", "Enabled", "LastLogin"},
			FilterFields: []string{"Name", "CreatedAt", "Age", "Enabled"},
			OrderFields:  []string{"CreatedAt", "Age", "Enabled"},
			GetDB:        func(ctx *gin.Context, isCreate bool) *gorm.DB { return db },
		},
		// Advanced Demo
		// Check API File: product.http
		// PUT 		http://localhost:8890/product
		// GET 		http://localhost:8890/product/:key
		// PATCH	http://localhost:8890/product/:key
		// POST 	http://localhost:8890/product
		// DELETE http://localhost:8890/product/:key
		// DELETE http://localhost:8890/product
		{
			Name:         "product",
			Model:        &Product{},
			SearchFields: []string{"Name"},
			EditFields:   []string{"Name", "Enabled", "Model"},
			FilterFields: []string{"Name", "CreatedAt", "Enabled"},
			OrderFields:  []string{"CreatedAt"},
			GetDB:        func(c *gin.Context, isCreate bool) *gorm.DB { return db },
			BeforeCreate: func(ctx *gin.Context, vptr any, vals map[string]any) error {
				p := (vptr).(*Product)
				p.UUID = MockUUID(8)

				// create group
				group := Group{Name: "group" + MockUUID(4)}
				if err := db.Create(&group).Error; err != nil {
					return err
				}

				p.GroupID = int(group.ID)
				return nil
			},
			BeforeDelete: func(ctx *gin.Context, vptr any) error {
				p := (vptr).(*Product)
				if p.Enabled {
					return errors.New("product is enabled, can not delete")
				}
				return nil
			},
			// Custom Query View
			// GET http://localhost:8890/product/all_enabled
			Views: []gormpher.QueryView{
				{
					Name:   "all_enabled",
					Method: "GET",
					Prepare: func(db *gorm.DB, c *gin.Context, pagination bool) (*gorm.DB, *gormpher.QueryForm, error) {
						// SELECT (id, name) FROM products WHERE enabled = true
						queryForm := &gormpher.QueryForm{
							Limit: -1,
							Filters: []gormpher.Filter{
								{Name: "enabled", Op: "=", Value: true}, // JSON format
							},
							ViewFields: []string{"UUID", "Name"},
						}
						return db, queryForm, nil
					},
				},
			},
		},
	}
}

func MockUUID(n int) string {
	source := []rune("0123456789abcdefghijklmnopqrstuvwxyz")
	b := make([]rune, n)
	for i := range b {
		b[i] = source[rand.Intn(len(source))]
	}
	return string(b)
}

Run the project and visit http://localhost:8890/admin , you can see a web interface for administrator.

image

Features

Generate CRUD Restful API

Optional Admin Web UI

Generic Gorm Function