kamalshkeir/korm has 185 GitHub stars and 8 forks. Star changes are not measured yet. Last checked 2026-11-09 05:07 UTC. Last push 2026-10-31.
KORM, an elegant and lightning-fast ORM for all your concurrent and async needs. Inspired by the highly popular Django Framework, KORM offers similar functionality with the added bonus of performance
측정된 스타 증가
- in 24h
- 기록 수집 중
- in 7 days
- 기록 수집 중
- in 15 days
- 기록 수집 중
- in 1 month
- 기록 수집 중
서로 겹치는 기간의 순증가량이며 일별 합계가 아닙니다. 측정값이 없으면 비워 둡니다. 스타는 관심도를 나타내며 품질을 보장하지 않습니다.
README.md
Original
Introducing Korm - the elegant, lightning-fast ORM/Framework for all your needs, see benchmarks. Inspired by the highly popular Django Framework, Korm offers similar functionality with the added bonus of performance
It is also composable, allowing for integration with a network websocket PubSub using WithBus when you want to synchronise your data between multiple Korm or WithDashboard to have a complete setup of server bus and Admin Dashboard.
Why settle for less when you can have the best ?
- Django become very hard to work with when you need concurrency and async, you will need django channels and a server like daphne or uvicorn, Go have the perfect implementation
- Django can handle at most 300 request per second, Go handle 44,000 requests per second (benchmarks done on my machine)
- The API is also more user-friendly and less verbose than Django's
- Deploying an executable binary file using Korm , with automatic TLS Let's encrypt, a built-in Admin Dashboard, Interactive Shell, Eventbus to communicate between multiple Korm applications is pretty neat
- Additionally, its caching system uses goroutines and channels to efficiently to clean the cache when rows or tables are created, updated, deleted, or dropped
It Has :
New: When using korm.WithDashboard, now you have access to all logs in realtime (websockets) from admin dashboard when you log using lg pkg. By default only 10 last logs are keeped in memory, you can increase it using lg.SaveLogs(50) for keeping last 50 logs
New: Handle Nested or Embeded structs and slice of structs through joins, like sqlx, but sqlx doesn't handle slice of structs
New: korm.QueryNamed, QueryNamedS, korm.ExecNamed, korm.ExecContextNamed and WhereNamed(query string, args map[string]any) like :Where("email = :email",map[string]any{"email":"[email protected]"})
New: korm.LogsQueries() that log statements and time tooked by sql queries
Built-in MCP Server: Model Context Protocol native server (
korm mcpviakorm.WithShell()or/admin/mcp/*HTTP/SSE) for Cursor, Claude Desktop, Antigravity, and any AI agent to inspect schemas, execute safe read-only queries, compare models, and view cache stats.Built-in Reverse Proxy & Load Balancer: Forward subdomains or paths to other services with
router.ReverseProxy(host, toUrl)and round-robin traffic withrouter.LoadBalancer(pattern, backends...)including Circuit Breaker supportSwagger Documentation with interactive UI and automatic OpenAPI JSON generation
PPROF Go profiling tool and Metrics Prometheus
Admin dashboard with ready offline and installable PWA (using /static/sw.js and /static/manifest.webmanifest). All statics mentionned in
sw.jswill be cached and served by the service worker, you can inspect the Network Tab in the browser to check itShared Network Bus allowing you to send and recv data in realtime using pubsub websockets between your ORMs, so you can decide how you data will be distributed between different databases, see Example
Built-in Authentication using
korm.Auth,korm.Adminorkorm.BasicAuthmiddlewares, whenever Auth and Admin middlewares are used, you get access to the.Usermodel and variable.IsAuthenticatedfrom any template html like this example admin_nav.htmlInteractive Shell, to CRUD in your databases from command line, use
korm.WithShell(), ⚠️ if dashboard used, korm.WithShell should be after WithDashboardAutoMigrate directly from struct
Schema Diff & Dry-Run, compare Go models with physical database and get proposed migration SQL queries without executing anything
Compatible with official database/sql, so you can do your queries yourself using sql.DB `korm.GetConnection()``, and overall a painless integration of your existing codebases using database/sql
Router/Mux accessible from the serverBus after calling
korm.WithBus(...opts)orkorm.WithDashboard(addr, ...opts)Hooks : OnInsert OnSet OnDelete and OnDrop
many to many relationships
GENERATED ALWAYS AS tag added (all dialects)
Concatination and Length support for
Whereand for tags:checkandgenerated(all dialects)Support for foreign keys, indexes , checks,... See all
Kenv load env vars to struct
Python Bus Client
pip install ksbus
All drivers concurrent safe read and write
Supported databases:
- Sqlite
- Mysql
- Maria
- Postgres
- Cockroach
Installation
go get -u github.com/kamalshkeir/korm@latest
Drivers moved outside this package to not get them all in your go.mod file
go get -u github.com/kamalshkeir/sqlitedriver@latest
go get -u github.com/kamalshkeir/pgdriver@latest
go get -u github.com/kamalshkeir/mysqldriver@latest
Global Vars
// Debug when true show extra useful logs for queries executed for migrations and queries statements
Debug = false
// FlushCacheEvery execute korm.FlushCache() every 10 min by default, you should not worry about it, but useful that you can change it
FlushCacheEvery = 10 * time.Minute
// SetCacheMaxMemory set max size of each cache cacheAllS AllM ...
korm.SetCacheMaxMemory(megaByte int) // default maximum of 50 Mb , cannot be lower
// Connection pool
MaxOpenConns = 20
MaxIdleConns = 20
MaxLifetime = 30 * time.Minute
MaxIdleTime = 15 * time.Minute
Connect to a database
// sqlite
// go get github.com/kamalshkeir/sqlitedriver
err := korm.New(korm.SQLITE, "dbName", sqlitedriver.Use()) // Connect
// postgres, cockroach
// go get github.com/kamalshkeir/pgdriver
err := korm.New(korm.POSTGRES,"dbName", pgdriver.Use(), "user:password@localhost:5432") // Connect
// mysql, maria
// go get github.com/kamalshkeir/mysqldriver
err := korm.New(korm.MYSQL,"dbName", mysqldriver.Use(), "user:password@localhost:3306") // Connect
korm.Shutdown(databasesName ...string) error
Hello world example
package main
import (
"fmt"
"time"
"github.com/kamalshkeir/lg"
"github.com/kamalshkeir/korm"
"github.com/kamalshkeir/sqlitedriver"
)
type Class struct {
Id uint `korm:"pk"`
Name string
Students []Student
}
type Student struct {
Id uint `korm:"pk"`
Name string
Class uint `korm:"fk:classes.id:cascade:cascade"`
Classes Class
}
func main() {
err := korm.New(korm.SQLITE, "db", sqlitedriver.Use())
if lg.CheckError(err) {
return
}
defer korm.Shutdown()
server := korm.WithDashboard(":9313")
korm.WithShell() // ⚠️ if dashboard used, korm.WithShell should be after WithDashboard
err = korm.AutoMigrate[Class]("classes")
lg.CheckError(err)
err = korm.AutoMigrate[Student]("students")
lg.CheckError(err)
// go run main.go shell to createsuperuser
// connect to admin and create some data to query
// Stream rows directly to a channel as they arrive:
studentsChan := make(chan Student)
go func() {
for s := range studentsChan {
fmt.Println("chan students:", s)
}
}()
_, err = korm.Select[chan Student]().Channel(studentsChan).Query("select students.*,classes.id as 'classes.id',classes.name as 'classes.name' from students join classes where classes.id = students.class")
lg.CheckError(err)
fmt.Println()
// 🚀 Modern Generic Queries: No pointer passing, no pre-allocated slice variables!
// Named query with joins and nested relations:
query := "select classes.*, students.id as 'students.id',students.name as 'students.name' from classes join students on students.class = classes.id order by :order_here"
classes, err := korm.QueryNamed[Class](query, map[string]any{
"order_here": "classes.id",
})
lg.CheckError(err)
for _, s := range classes {
fmt.Println("class:", s)
}
fmt.Println()
// Queries with joins and nested structs:
students, err := korm.Query[Student]("select students.*,classes.id as 'classes.id',classes.name as 'classes.name' from students join classes where classes.id = students.class")
lg.CheckError(err)
for _, s := range students {
fmt.Println("student:", s)
}
fmt.Println()
// Direct maps slice:
maps, err := korm.Query[map[string]any]("select * from students")
lg.CheckError(err)
fmt.Println("maps =", maps)
fmt.Println()
// Direct primitive slices:
names, err := korm.Query[string]("select name from students")
lg.CheckError(err)
fmt.Println("names =", names)
fmt.Println()
ids, err := korm.Query[int]("select id from students")
lg.CheckError(err)
fmt.Println("ids =", ids)
fmt.Println()
bools, err := korm.Query[bool]("select is_admin from users")
lg.CheckError(err)
fmt.Println("bools =", bools)
fmt.Println()
times, err := korm.Query[time.Time]("select created_at from users")
lg.CheckError(err)
fmt.Println("times =", times)
// Fetch a single record directly (returns ErrNoData if not found):
firstStudent, err := korm.QueryOne[Student]("select * from students where id = ?", 1)
if err == nil {
fmt.Println("first student =", firstStudent)
}
server.Run()
}
// OUTPUT
// chan students: {1 student-1 1 {1 Math []}}
// chan students: {2 student-2 2 {2 French []}}
// chan students: {3 student-3 1 {1 Math []}}
// chan students: {4 student-4 2 {2 French []}}
// class: {1 Math [{1 student-1 0 {0 []}} {3 student-3 0 {0 []}}]}
// class: {2 French [{2 student-2 0 {0 []}} {4 student-4 0 {0 []}}]}
// student: &{1 student-1 1 {1 Math []}}
// student: &{2 student-2 2 {2 French []}}
// student: &{3 student-3 1 {1 Math []}}
// student: &{4 student-4 2 {2 French []}}
// maps = [map[class:1 id:1 name:student-1] map[class:2 id:2 name:student-2] map[class:1 id:3 name:student-3] map[class:2 id:4 name:student-4]]
// names = [student-1 student-2 student-3 student-4]
// ids = [1 2 3 4]
// bools = [true]
// times = [2023-04-30 19:19:32 +0200 CEST]
AutoMigrate
Available Tags (SQL)
SQL:
korm.AutoMigrate[T comparable](tableName string, dbName ...string) error
err := korm.AutoMigrate[User]("users")
err := korm.AutoMigrate[Bookmark ]("bookmarks")
type User struct {
Id int `korm:"pk"` // AUTO Increment ID primary key
Uuid string `korm:"size:40"` // VARCHAR(50)
Email string `korm:"size:50;iunique"` // insensitive unique
Password string `korm:"size:150"` // VARCHAR(150)
IsAdmin bool `korm:"default:false"` // DEFAULT 0
Image string `korm:"size:100;default:''"`
CreatedAt time.Time `korm:"now"` // auto now
Ignored string `korm:"-"`
}
type Bookmark struct {
Id uint `korm:"pk"`
UserId int `korm:"fk:users.id:cascade:setnull"` // options cascade,donothing/noaction, setnull/null, setdefault/default
IsDone bool
ToCheck string `korm:"size:50; notnull; check: len(to_check) > 2 AND len(to_check) < 10; check: is_done=true"` // column type will be VARCHAR(50)
Content string `korm:"text"` // column type will be TEXT not VARCHAR
UpdatedAt time.Time `korm:"update"` // will update when model updated, handled by triggers for sqlite, cockroach and postgres, and on migration for mysql
CreatedAt time.Time `korm:"now"` // now is default to current timestamp and of type TEXT for sqlite
}
all, _ := korm.Model[User]()
.Where("id = ?",id)
.Select("item1","item2")
.OrderBy("created")
.Limit(8)
.Page(2)
.All()
Schema Diff (Dry-Run)
Inspect differences between your Go model structs and the physical database tables at runtime or on-demand, and review proposed migration SQL queries without executing them:
# In terminal directly:
go run main.go shell diff
# Or for a specific table:
go run main.go shell diff users
Or programmatically in Go:
diffs, err := korm.CheckSchema()
if err == nil {
korm.PrintSchemaDiff(diffs)
}
Key Capabilities :
- Safe & Non-Destructive : Performs a complete dry-run inspection of tables, columns, indexes, and constraints without running any
ALTERorDROPqueries. - Dialect-Aware : Proposes tailored SQL for SQLite, PostgreSQL, CockroachDB, and MySQL / MariaDB (e.g. SQLite-safe
ADD COLUMNavoiding illegal inlineUNIQUEconstraints). - Foreign Key & Rename Intelligence : Automatically detects column renames (
old ➔ new) and alerts when dropped or renamed columns have active foreign key dependencies across models or database tables. - KORM Triggers & Constraints : Automatically proposes trigger refreshes (for SQLite/MySQL change tracking triggers and
updated_at) and handles dynamic default conversions.
API
korm.New(dbType Dialect, dbName string, dbDriver driver.Driver, dbDSN ...string) error
korm.LogQueries()
korm.GetConnection(dbName ...string) *sql.DB
// 🚀 Modern, Type-Safe Query API (zero pointer passing required!)
korm.Query[T any](statement string, args ...any) ([]T, error)
korm.QueryOne[T any](statement string, args ...any) (T, error)
korm.QueryNamed[T any](statement string, args map[string]any, unsafe ...bool) ([]T, error)
korm.QueryNamedOne[T any](statement string, args map[string]any, unsafe ...bool) (T, error)
// Fluent Selector for advanced options (Database, NoCache, Debug, Tracing, Context)
korm.Select[T any](nestedSlice ...bool) *Selector[T]
korm.SelectTo[T any](dest *[]T, nestedSlice ...bool) *Selector[T] // scan into an existing slice pointer (ideal for anonymous structs)
(sl *Selector[T]) Database(dbName string) *Selector[T]
(sl *Selector[T]) NoCache() *Selector[T]
(sl *Selector[T]) Debug() *Selector[T]
(sl *Selector[T]) Trace() *Selector[T]
(sl *Selector[T]) Ctx(ct context.Context) *Selector[T]
(sl *Selector[T]) Query(statement string, args ...any) ([]T, error)
(sl *Selector[T]) Named(statement string, args map[string]any, unsafe ...bool) ([]T, error)
(sl *Selector[T]) One(statement string, args ...any) (T, error)
(sl *Selector[T]) NamedOne(statement string, args map[string]any, unsafe ...bool) (T, error)
korm.WithBus(...opts) *ksbus.Server // Usage: WithBus(...opts) or share an existing one
korm.WithDashboard(address, ...opts) *ksbus.Server
korm.WithShell() // ⚠️ if dashboard used, korm.WithShell should be after WithDashboard
korm.WithDocs(generateJsonDocs bool, outJsonDocs string, handlerMiddlewares ...func(handler kmux.Handler) kmux.Handler) *ksbus.Server
korm.WithEmbededDocs(embeded embed.FS, embededDirPath string, handlerMiddlewares ...func(handler kmux.Handler) kmux.Handler) *ksbus.Server
korm.WithMetrics(httpHandler http.Handler) *ksbus.Server
korm.WithPprof(path ...string) *ksbus.Server
korm.Transaction(dbName ...string) (*sql.Tx, error)
korm.Exec(dbName, query string, args ...any) error
korm.ExecContext(ctx context.Context, dbName, query string, args ...any) error
korm.ExecNamed(query string, args map[string]any, dbName ...string) error
korm.ExecContextNamed(ctx context.Context, query string, args map[string]any, dbName ...string) error
korm.BeforeServersData(fn func(data any, conn *ws.Conn))
korm.BeforeDataWS(fn func(data map[string]any, conn *ws.Conn, originalRequest *http.Request) bool)
korm.GetAllTables(dbName ...string) []string
korm.GetAllColumnsTypes(table string, dbName ...string) map[string]string
korm.GetMemoryTable(tbName string, dbName ...string) (TableEntity, error)
korm.GetMemoryTables(dbName ...string) ([]TableEntity, error)
korm.GetMemoryDatabases() []DatabaseEntity
korm.GetMemoryDatabase(dbName string) (*DatabaseEntity, error)
korm.Shutdown(databasesName ...string) error
korm.FlushCache()
korm.DisableCache()
korm.ManyToMany(table1, table2 string, dbName ...string) error // add table relation m2m
Builder Struct:
korm.Exec(dbName, query string, args ...any) error
korm.Transaction(dbName ...string) (*sql.Tx, error)
// Model is a starter for Buider
func Model[T comparable](tableName ...string) *BuilderS[T]
// Database allow to choose database to execute query on
func (b *BuilderS[T]) Database(dbName string) *BuilderS[T]
// Insert insert a row into a table and return inserted PK
func (b *BuilderS[T]) Insert(model *T) (int, error)
// InsertR add row to a table using input struct, and return the inserted row
func (b *BuilderS[T]) InsertR(model *T) (T, error)
// BulkInsert insert many row at the same time in one query
func (b *BuilderS[T]) BulkInsert(models ...*T) ([]int, error)
// AddRelated used for many to many, and after korm.ManyToMany, to add a class to a student or a student to a class, class or student should exist in the database before adding them
func (b *BuilderS[T]) AddRelated(relatedTable string, whereRelatedTable string, whereRelatedArgs ...any) (int, error)
// DeleteRelated delete a relations many to many
func (b *BuilderS[T]) DeleteRelated(relatedTable string, whereRelatedTable string, whereRelatedArgs ...any) (int, error)
// GetRelated used for many to many to get related classes to a student or related students to a class
func (b *BuilderS[T]) GetRelated(relatedTable string, dest any) error
// JoinRelated same as get, but it join data
func (b *BuilderS[T]) JoinRelated(relatedTable string, dest any) error
// Set used to update, Set("email,is_admin","[email protected]",true) or Set("email = ? AND is_admin = ?","[email protected]",true)
func (b *BuilderS[T]) Set(query string, args ...any) (int, error)
// Delete data from database, can be multiple, depending on the where, return affected rows(Not every database or database driver may support affected rows)
func (b *BuilderS[T]) Delete() (int, error)
// Drop drop table from db
func (b *BuilderS[T]) Drop() (int, error)
// Select usage: Select("email","password")
func (b *BuilderS[T]) Select(columns ...string) *BuilderS[T]
// Where can be like : Where("id > ?",1) or Where("id",1) = Where("id = ?",1)
func (b *BuilderS[T]) Where(query string, args ...any) *BuilderS[T]
// Limit set limit
func (b *BuilderS[T]) Limit(limit int) *BuilderS[T]
// Context allow to query or execute using ctx
func (b *BuilderS[T]) Context(ctx context.Context) *BuilderS[T]
// Page return paginated elements using Limit for specific page
func (b *BuilderS[T]) Page(pageNumber int) *BuilderS[T]
// OrderBy can be used like: OrderBy("-id","-email") OrderBy("id","-email") OrderBy("+id","email")
func (b *BuilderS[T]) OrderBy(fields ...string) *BuilderS[T]
// Debug print prepared statement and values for this operation
func (b *BuilderS[T]) Debug() *BuilderS[T]
// All get all data
func (b *BuilderS[T]) All() ([]T, error)
// One get single row
func (b *BuilderS[T]) One() (T, error)
Examples:
korm.Model[models.User]().Select("email","uuid").OrderBy("-id").Limit(PAGINATION_PER).Page(1).All()
// INSERT
uuid,_ := korm.GenerateUUID()
hashedPass,_ := argon.Hash(password)
korm.Model[models.User]().Insert(&models.User{
Uuid: uuid,
Email: "[email protected]",
Password: hashedPass,
IsAdmin: false,
Image: "",
CreatedAt: time.Now(),
})
//if using more than one db
korm.Database[models.User]("dbNameHere").Where("id = ? AND email = ?",1,"[email protected]").All()
// where
korm.Model[models.User]().Where("id = ? AND email = ?",1,"[email protected]").One()
// delete
korm.Model[models.User]().Where("id = ? AND email = ?",1,"[email protected]").Delete()
// drop table
korm.Model[models.User]().Drop()
// update
korm.Model[models.User]().Where("id = ?",1).Set("email = ?","[email protected]")
Builder map[string]any:
// BuilderM is query builder map string any
type BuilderM struct
// Table is a starter for BuiderM
func Table(tableName string) *BuilderM
// Database allow to choose database to execute query on
func (b *BuilderM) Database(dbName string) *BuilderM
// Select select table columns to return
func (b *BuilderM) Select(columns ...string) *BuilderM
// Where can be like: Where("id > ?",1) or Where("id",1) = Where("id = ?",1)
func (b *BuilderM) Where(query string, args ...any) *BuilderM
// Limit set limit
func (b *BuilderM) Limit(limit int) *BuilderM
// Page return paginated elements using Limit for specific page
func (b *BuilderM) Page(pageNumber int) *BuilderM
// OrderBy can be used like: OrderBy("-id","-email") OrderBy("id","-email") OrderBy("+id","email")
func (b *BuilderM) OrderBy(fields ...string) *BuilderM
// Context allow to query or execute using ctx
func (b *BuilderM) Context(ctx context.Context) *BuilderM
// Debug print prepared statement and values for this operation
func (b *BuilderM) Debug() *BuilderM
// All get all data
func (b *BuilderM) All() ([]map[string]any, error)
// One get single row
func (b *BuilderM) One() (map[string]any, error)
// Insert add row to a table using input map, and return PK of the inserted row
func (b *BuilderM) Insert(rowData map[string]any) (int, error)
// InsertR add row to a table using input map, and return the inserted row
func (b *BuilderM) InsertR(rowData map[string]any) (map[string]any, error)
// BulkInsert insert many row at the same time in one query
func (b *BuilderM) BulkInsert(rowsData ...map[string]any) ([]int, error)
// Set used to update, Set("email,is_admin","[email protected]",true) or Set("email = ? AND is_admin = ?","[email protected]",true)
func (b *BuilderM) Set(query string, args ...any) (int, error)
// Delete data from database, can be multiple, depending on the where, return affected rows(Not every database or database driver may support affected rows)
func (b *BuilderM) Delete() (int, error)
// Drop drop table from db
func (b *BuilderM) Drop() (int, error)
// AddRelated used for many to many, and after korm.ManyToMany, to add a class to a student or a student to a class, class or student should exist in the database before adding them
func (b *BuilderM) AddRelated(relatedTable string, whereRelatedTable string, whereRelatedArgs ...any) (int, error)
// GetRelated used for many to many to get related classes to a student or related students to a class
func (b *BuilderM) GetRelated(relatedTable string, dest *[]map[string]any) error
// JoinRelated same as get, but it join data
func (b *BuilderM) JoinRelated(relatedTable string, dest *[]map[string]any) error
// DeleteRelated delete a relations many to many
func (b *BuilderM) DeleteRelated(relatedTable string, whereRelatedTable string, whereRelatedArgs ...any) (int, error)
Examples:
sliceMapStringAny,err := korm.Table("users")
.Select("email","uuid")
.OrderBy("-id")
.Limit(PAGINATION_PER)
.Page(1)
.All()
// INSERT
uuid,_ := korm.GenerateUUID()
hashedPass,_ := argon.Hash("password") // github.com/kamalshkeir/argon
korm.Table("users").Insert(map[string]any{
"uuid":uuid,
"email":"[email protected]",
...
})
//if using more than one db
korm.Database("dbNameHere").Table("tableName").Where("id = ? AND email = ?",1,"[email protected]").All()
// where
Where("id = ? AND email = ?",1,"[email protected]") // this work
Where("id,email",1,"[email protected]") // and this work
korm.Table("tableName").Where("id = ? AND email = ?",1,"[email protected]").One()
// delete
korm.Table("tableName").Where("id = ? AND email = ?",1,"[email protected]").Delete()
// drop table
korm.Table("tableName").Drop()
// update
korm.Table("tableName").Where("id = ?",1).Set("email = ?","[email protected]")
korm.Table("tableName").Where("id",1).Set("email","[email protected]")
Dashboard defaults you can set
korm.PaginationPer = 10
korm.DocsUrl = "docs"
korm.EmbededDashboard = false
korm.MediaDir = "media"
korm.AssetsDir = "assets"
korm.StaticDir = path.Join(AssetsDir, "/", "static")
korm.TemplatesDir = path.Join(AssetsDir, "/", "templates")
korm.RepoUser = "kamalshkeir"
korm.RepoName = "korm-dash"
korm.adminPathNameGroup = "/admin" // korm.SetAdminPath("/another")
// so you can create a custom dashboard, upload it to your repos and change like like above korm.RepoUser and korm.RepoName
Example With Dashboard (you don't need korm.WithBus with it, because WithDashboard already call it and return the server bus for you)
package main
import (
"github.com/kamalshkeir/lg"
"github.com/kamalshkeir/ksmux"
"github.com/kamalshkeir/korm"
"github.com/kamalshkeir/sqlitedriver"
)
func main() {
err := korm.New(korm.SQLITE, "db", sqlitedriver.Use())
lg.CheckError(err)
serverBus := korm.WithDashboard("localhost:9313")
korm.WithShell() // ⚠️ if dashboard used, korm.WithShell should be after WithDashboard
// you can overwrite Admin and Auth middleware used for dashboard (dash_middlewares.go)
//korm.Auth = func(handler ksmux.Handler) ksmux.Handler {}
//korm.Admin = func(handler ksmux.Handler) ksmux.Handler {}
// and also all handlers (dash_views.go)
//korm.LoginView = func(c *ksmux.Context) {
// c.Html("admin/new_admin_login.html", nil)
//}
// add extra static directory if you want
//serverBus.App.LocalStatics("assets/mystatic","myassets") // will be available at /myassets/*
//serverBus.App.LocalTemplates("assets/templates") // will make them available to use with c.Html
// serve HTML
// serverBus.App.Get("/",func(c *ksmux.Context) {
// c.Html("index.html", map[string]any{
// "data": data,
// })
// })
serverBus.Run()
// OR run https if you have certificates
serverBus.RunTLS(cert string, certKey string)
// OR generate certificates let's encrypt for a domain name, check https://github.com/kamalshkeir/ksbus for more infos
serverBus.RunAutoTLS(subDomains ...string)
}
Then create admin user to connect to the dashboard
go run main.go shell
createsuperuser
Then you can visit /admin
Auth middleware example
func main() {
err := korm.New(korm.SQLITE, "db", sqlitedriver.Use())
if lg.CheckError(err) {
return
}
defer korm.Shutdown()
srv := korm.WithDashboard("localhost:9313")
korm.WithShell() // ⚠️ if dashboard used, korm.WithShell should be after WithDashboard
lg.Printfs("mgrunning on http://localhost:9313\n")
app := srv.App
app.Get("/", korm.Auth(func(c *ksmux.Context) { // work with korm.Admin also
// c.IsAuthenticated also return bool
if v, ok := c.User(); ok {
c.Json(map[string]any{
"msg": "Authenticated",
"v": v.(korm.User).Email,
})
} else {
c.Json(map[string]any{
"error": "not auth",
})
}
}))
srv.Run()
}
Example Admin Auth User and IsAuthenticated
{{define "admin_nav"}}
<header id="admin-header">
<nav>
<a href="/">
<h1>KORM</h1>
</a>
<ul>
<li>
<a {{if eq .Request.URL.Path "/" }}class="active"{{end}} href="/">Home</a>
</li>
<li>
<a {{if contains .Request.URL.Path .admin_path }}class="active"{{end}} href="{{.admin_path}}">Admin</a>
</li>
{{if .IsAuthenticated}}
<li>
<a href="{{.admin_path}}/logout">Logout</a>
</li>
{{if .User.Email}}
<li>
<span>Hello {{.User.Email}}</span>
</li>
{{end}}
{{end}}
</ul>
</nav>
</header>
{{end}}
Admin middlewares
// dash_middlewares.go
package korm
import (
"context"
"net/http"
"github.com/kamalshkeir/aes"
"github.com/kamalshkeir/ksmux"
)
var Auth = func(handler ksmux.Handler) ksmux.Handler {
return func(c *ksmux.Context) {
session, err := c.GetCookie("session")
if err != nil || session == "" {
// NOT AUTHENTICATED
c.DeleteCookie("session")
handler(c)
return
}
session, err = aes.Decrypt(session)
if err != nil {
handler(c)
return
}
// Check session
user, err := Model[User]().Where("uuid = ?", session).One()
if err != nil {
// session fail
handler(c)
return
}
// AUTHENTICATED AND FOUND IN DB
c.SetKey("korm-user", user)
handler(c)
}
}
var Admin = func(handler ksmux.Handler) ksmux.Handler {
return func(c *ksmux.Context) {
session, err := c.GetCookie("session")
if err != nil || session == "" {
// NOT AUTHENTICATED
c.DeleteCookie("session")
c.Status(http.StatusTemporaryRedirect).Redirect(adminPathNameGroup + "/login")
return
}
session, err = aes.Decrypt(session)
if err != nil {
c.Status(http.StatusTemporaryRedirect).Redirect(adminPathNameGroup + "/login")
return
}
user, err := Model[User]().Where("uuid = ?", session).One()
if err != nil {
// AUTHENTICATED BUT NOT FOUND IN DB
c.Status(http.StatusTemporaryRedirect).Redirect(adminPathNameGroup + "/login")
return
}
// Not admin
if !user.IsAdmin {
c.Status(403).Text("Middleware : Not allowed to access this page")
return
}
c.SetKey("korm-user", user)
handler(c)
}
}
var BasicAuth = func(handler ksmux.Handler) ksmux.Handler {
return ksmux.BasicAuth(handler, BASIC_AUTH_USER, BASIC_AUTH_PASS)
}
Example With Bus between 2 KORM
KORM 1:
package main
import (
"net/http"
"github.com/kamalshkeir/lg"
"github.com/kamalshkeir/ksmux"
"github.com/kamalshkeir/ksmux/ws"
"github.com/kamalshkeir/korm"
"github.com/kamalshkeir/ksbus"
"github.com/kamalshkeir/sqlitedriver"
)
func main() {
err := korm.New(korm.SQLITE,"db1", sqlitedriver.Use())
if lg.CheckError(err) {return}
korm.WithShell() // ⚠️ if dashboard used, korm.WithShell should be after WithDashboard
serverBus := korm.WithBus(ksbus.ServerOpts{
ID string
Address string
Path string
OnWsClose func(connID string)
OnDataWS func(data map[string]any, conn *ws.Conn, originalRequest *http.Request) error
OnServerData func(data any, conn *ws.Conn)
OnId func(data map[string]any)
OnUpgradeWs func(r *http.Request) bool
WithOtherRouter *ksmux.Router
WithOtherBus *Bus
})
// handler authentication
korm.BeforeDataWS(func(data map[string]any, conn *ws.Conn, originalRequest *http.Request) bool {
lg.Info("handle authentication here")
return true
})
// handler data from other KORM
korm.BeforeServersData(func(data any, conn *ws.Conn) {
lg.Info("recv orm:", "data", data)
})
// built in router to the bus, check it at https://github.com/kamalshkeir/ksbus
serverBus.App.Get("/",func(c *ksmux.Context) {
serverBus.SendToServer("localhost:9314",map[string]any{
"msg":"hello from server 1",
})
c.Text("ok")
})
serverBus.Run("localhost:9313")
// OR run https if you have certificates
serverBus.RunTLS(addr string, cert string, certKey string)
// OR generate certificates let's encrypt for a domain name, check https://github.com/kamalshkeir/ksbus for more details
serverBus.RunAutoTLS(domainName string, subDomains ...string)
}
KORM 2:
package main
import (
"net/http"
"github.com/kamalshkeir/lg"
"github.com/kamalshkeir/ksmux"
"github.com/kamalshkeir/ksmux/ws"
"github.com/kamalshkeir/korm"
"github.com/kamalshkeir/sqlitedriver"
)
func main() {
err := korm.New(korm.SQLITE,"db2",sqlitedriver.Use())
if lg.CheckError(err) {return}
korm.WithShell() // if dashboard used, this line should be after it
serverBus := korm.WithBus(ksbus.ServerOpts{
ID string
Address string
Path string
OnWsClose func(connID string)
OnDataWS func(data map[string]any, conn *ws.Conn, originalRequest *http.Request) error
OnServerData func(data any, conn *ws.Conn)
OnId func(data map[string]any)
OnUpgradeWs func(r *http.Request) bool
WithOtherRouter *ksmux.Router
WithOtherBus *Bus
})
korm.BeforeServersData(func(data any, conn *ws.Conn) {
lg.Info("recv", "data", data)
})
// built in router to the bus, check it at https://github.com/kamalshkeir/ksbus
serverBus.App.GET("/",func(c *ksmux.Context) {
serverBus.SendToServer("localhost:9314",map[string]any{
"msg":"hello from server 2",
})
c.Status(200).Text("ok")
})
// Run Server Bus
serverBus.Run("localhost:9314")
// OR run https if you have certificates
serverBus.RunTLS(addr string, cert string, certKey string)
// OR generate certificates let's encrypt for a domain name, check https://github.com/kamalshkeir/ksbus for more infos
serverBus.RunAutoTLS(domainName string, subDomains ...string)
}
Example generated tag
// generated example using concatination and length
type TestUser struct {
Id *uint `korm:"pk"`
Uuid string `korm:"size:40;iunique"`
Email *string `korm:"size:100;iunique"`
Gen string `korm:"size:250;generated: concat(uuid,'working',len(password))"`
Password string
IsAdmin *bool
CreatedAt time.Time `korm:"now"`
UpdatedAt time.Time `korm:"update"`
}
func TestGeneratedAs(t *testing.T) {
u, err := Model[TestUser]().Limit(3).All()
if err != nil {
t.Error(err)
}
if len(u) != 3 {
t.Error("len not 20")
}
if u[0].Gen != u[0].Uuid+"working"+fmt.Sprintf("%d", len(u[0].Password)) {
t.Error("generated not working:", u[0].Gen)
}
}
Example concat and len from korm_test.go
// Where example
func TestConcatANDLen(t *testing.T) {
groupes, err := Model[Group]().Where("name = concat(?,'min') AND len(name) = ?", "ad", 5).Debug().All()
// translated to select * from groups WHERE name = 'ad' || 'min' AND length(name) = 5 (sqlite)
// translated to select * from groups WHERE name = concat('ad','min') AND char_length(name) = 5 (postgres, mysql)
if err != nil {
t.Error(err)
}
if len(groupes) != 1 || groupes[0].Name != "admin" {
t.Error("len(groupes) != 1 , got: ", groupes)
}
}
Router/Mux
Learn more about Ksmux
func main() {
err := korm.New(korm.SQLITE, "db", sqlitedriver.Use())
if err != nil {
log.Fatal(err)
}
serverBus := korm.WithDashboard("localhost:9313")
korm.WithShell() // ⚠️ if dashboard used, korm.WithShell should be after WithDashboard
mux := serverBus.App
// add global middlewares
mux.Use((midws ...func(http.Handler) http.Handler))
...
}
Pprof
serverBus := korm.WithDashboard("localhost:9313")
// or srv := korm.WithBus()
serverBus.WithPprof(path ...string) // path is 'debug' by default
will enable:
- /debug/pprof
- /debug/profile
- /debug/heap
- /debug/trace
To execute profile cpu: go tool pprof -http=":8000" pprofbin http://localhost:9313/debug/profile?seconds=18
To execute profile memory: go tool pprof -http=":8000" pprofbin http://localhost:9313/debug/heap?seconds=18
To execute generate trace: go to endpoint http://localhost:9313/debug/trace?seconds=18 from browser , this will download the trace of 18 seconds
Then to see the trace : go tool trace path/to/trace
Metrics Prometheus
// or srv := korm.WithBus()
//srv.WithMetrics(httpHandler http.Handler, path ...string) path default to 'metrics'
srv.WithMetrics(promhttp.Handler())
will enable:
- /metrics
Logs middleware
// or srv := korm.WithBus()
//srv.WithMetrics(httpHandler http.Handler, path ...string) path default to 'metrics'
srv.App.Use(ksmux.Logs()) // it take an optional callback executed on each request if you want to add log to a file or send
srv.App.Use(ksmux.Logs(func(method, path, remote string, status int, took time.Duration) {
// save somewhere
}))
will enable:
- /metrics
Hooks
korm.OnInsert(func(database, table string, data map[string]any) error {
fmt.Println("inserting into", database, table, data)
// if error returned, it will not insert
return nil
})
korm.OnSet(func(database, table string, data map[string]any) error {
fmt.Println("set into", database, table, data)
return nil
})
korm.OnDelete(func(database, table, query string, args ...any) error {})
korm.OnDrop(func(database, table string) error {})
Python bus client example
pip install ksbus==1.1.0
# if it doesn't work , execute it again
from ksbus import Bus
# onOpen callback that let you know when connection is ready, it take the bus as param
def OnOpen(bus):
print("connected")
# bus.autorestart=True
# Publish publish to topic
bus.Publish("top", {
"data": "hello from python"
})
# Subscribe, it also return the subscription
bus.Subscribe("python", pythonTopicHandler)
# SendToNamed publish to named topic
bus.SendToNamed("top:srv", {
"data": "hello again from python"
})
# bus.Unsubscribe("python")
print("finish everything")
# pythonTopicHandler handle topic 'python'
def pythonTopicHandler(data, subs):
print("recv on topic python:", data)
# Unsubscribe
#subs.Unsubscribe()
if __name__ == "__main__":
Bus("localhost:9313", onOpen=OnOpen) # blocking
print("prorgram exited")
ManyToMany Relationships Example
type Class struct {
Id uint `korm:"pk"`
Name string `korm:"size:100"`
IsAvailable bool
CreatedAt time.Time `korm:"now"`
}
type Student struct {
Id uint `korm:"pk"`
Name string `korm:"size:100"`
CreatedAt time.Time `korm:"now"`
}
// migrate
func migrate() {
err := korm.AutoMigrate[Class]("classes")
if lg.CheckError(err) {
return
}
err = korm.AutoMigrate[Student]("students")
if lg.CheckError(err) {
return
}
err = korm.ManyToMany("classes", "students")
if lg.CheckError(err) {
return
}
}
// korm.ManyToMany create relation table named m2m_classes_students
// then you can use it like so to get related data
// get related to map to struct
std := []Student{}
err = korm.Model[Class]().Where("name = ?", "Math").Select("name").OrderBy("-name").Limit(1).GetRelated("students", &std)
// get related to map
std := []map[string]any{}
err = korm.Table("classes").Where("name = ?", "Math").Select("name").OrderBy("-name").Limit(1).GetRelated("students", &std)
// join related to map
std := []map[string]any{}
err = korm.Table("classes").Where("name = ?", "Math").JoinRelated("students", &std)
// join related to strcu
cu := []JoinClassUser{}
err = korm.Model[Class]().Where("name = ?", "Math").JoinRelated("students", &cu)
// to add relation
_, err = korm.Model[Class]().AddRelated("students", "name = ?", "hisName")
_, err = korm.Model[Student]().AddRelated("classes", "name = ?", "French")
_, err = korm.Table("students").AddRelated("classes", "name = ?", "French")
// delete relation
_, err = korm.Model[Class]().Where("name = ?", "Math").DeleteRelated("students", "name = ?", "hisName")
_, err = korm.Table("classes").Where("name = ?", "Math").DeleteRelated("students", "name = ?", "hisName")
Swagger documentation

korm.DocsUrl = "docs" // default endpoint '/docs'
korm.BASIC_AUTH_USER = "test"
korm.BASIC_AUTH_PASS = "pass"
korm.WithDocs(generate, dirPath, korm.BasicAuth)
korm.WithDocs(true, "", korm.BasicAuth) // dirPath default to 'assets/static/docs'
korm.WithEmbededDocs(embeded embed.FS, dirPath, korm.BasicAuth)
// dirPath default to 'assets/static/docs' if empty
Interactive shell
Commands :
[databases, use, tables, columns, diff, migrate, createsuperuser, createuser, query, getall, get, drop, delete, clear/cls, q/quit/exit, help/commands]
'databases':
list all connected databases
'use':
use a specific database
'tables':
list all tables in database
'columns':
list all columns of a table
(accept but not required extra param like : 'columns' or 'columns users')
'diff':
show diff between Go models and DB schema and propose SQL queries without executing
(accepts optional table name like : 'diff' or 'diff users')
'migrate':
migrate or execute sql file
'createsuperuser': (only with dashboard)
create a admin user
'createuser': (only with dashboard)
create a regular user
'query':
query data from database
(accept but not required extra param like : 'query' or 'query select * from users where ...')
'getall':
get all rows given a table name
(accept but not required extra param like : 'getall' or 'getall users')
'get':
get single row
(accept but not required extra param like : 'get' or 'get users email like "%anything%"')
'delete':
delete rows where field equal_to
(accept but not required extra param like : 'delete' or 'delete users email="[email protected]"')
'drop':
drop a table given table name
(accept but not required extra param like : 'drop' or 'drop users')
'clear / cls':
clear shell console
'q / quit / exit / q!':
exit shell
'help':
show this help message
Example, not required, Load config from env directly to struct using Kenv
import "github.com/kamalshkeir/kenv"
type EmbedS struct {
Static bool `kenv:"EMBED_STATIC|false"`
Templates bool `kenv:"EMBED_TEMPLATES|false"`
}
type GlobalConfig struct {
Host string `kenv:"HOST|localhost"` // DEFAULT to 'localhost': if HOST not found in env
Port string `kenv:"PORT|9313"`
Embed EmbedS
Db struct {
Name string `kenv:"DB_NAME|db"` // NOT REQUIRED: if DB_NAME not found, defaulted to 'db'
Type string `kenv:"DB_TYPE"` // REEQUIRED: this env var is required, you will have error if empty
DSN string `kenv:"DB_DSN|"` // NOT REQUIRED: if DB_DSN not found it's not required, it's ok to stay empty
}
Smtp struct {
Email string `kenv:"SMTP_EMAIL|"`
Pass string `kenv:"SMTP_PASS|"`
Host string `kenv:"SMTP_HOST|"`
Port string `kenv:"SMTP_PORT|"`
}
Profiler bool `kenv:"PROFILER|false"`
Docs bool `kenv:"DOCS|false"`
Logs bool `kenv:"LOGS|false"`
Monitoring bool `kenv:"MONITORING|false"`
}
kenv.Load(".env") // load env file
// Fill struct from env loaded before:
Config := &GlobalConfig{}
err := kenv.Fill(Config) // fill struct with env vars loaded before
Example nested or embeded structs
package main
import (
"fmt"
"time"
"github.com/kamalshkeir/lg"
"github.com/kamalshkeir/korm"
"github.com/kamalshkeir/sqlitedriver"
)
type Class struct {
Id uint `korm:"pk"`
Name string
Students []Student
}
type Student struct {
Id uint `korm:"pk"`
Name string
Class uint `korm:"fk:classes.id:cascade:cascade"`
Classes Class
}
func main() {
err := korm.New(korm.SQLITE, "db", sqlitedriver.Use())
if lg.CheckError(err) {
return
}
defer korm.Shutdown()
server := korm.WithDashboard("localhost:9313")
korm.WithShell() // ⚠️ if dashboard used, korm.WithShell should be after WithDashboard
err = korm.AutoMigrate[Class]("classes")
lg.CheckError(err)
err = korm.AutoMigrate[Student]("students")
lg.CheckError(err)
// go run main.go shell to createsuperuser
// connect to admin and create some data to query
// nested structs with joins, scan the result to the channel directly after each row
// Stream rows directly to a channel as they arrive:
studentsChan := make(chan Student)
go func() {
for s := range studentsChan {
fmt.Println("chan students:", s)
}
}()
_, err = korm.Select[chan Student]().Channel(studentsChan).Query("select students.*,classes.id as 'classes.id',classes.name as 'classes.name' from students join classes where classes.id = students.class")
lg.CheckError(err)
fmt.Println()
// 🚀 Type-safe queries: Returns typed slice/struct directly!
classes, err := korm.Query[Class]("select classes.*, students.id as 'students.id',students.name as 'students.name' from classes join students on students.class = classes.id order by classes.id")
lg.CheckError(err)
for _, s := range classes {
fmt.Println("class:", s)
}
fmt.Println()
students, err := korm.Query[Student]("select students.*,classes.id as 'classes.id',classes.name as 'classes.name' from students join classes where classes.id = students.class")
lg.CheckError(err)
for _, s := range students {
fmt.Println("student:", s)
}
fmt.Println()
maps, err := korm.Query[map[string]any]("select * from students")
lg.CheckError(err)
fmt.Println("maps =", maps)
fmt.Println()
names, err := korm.Query[string]("select name from students")
lg.CheckError(err)
fmt.Println("names =", names)
fmt.Println()
ids, err := korm.Query[int]("select id from students")
lg.CheckError(err)
fmt.Println("ids =", ids)
fmt.Println()
bools, err := korm.Query[bool]("select is_admin from users")
lg.CheckError(err)
fmt.Println("bools =", bools)
fmt.Println()
times, err := korm.Query[time.Time]("select created_at from users")
lg.CheckError(err)
fmt.Println("times =", times)
server.Run()
}
// OUTPUT
// chan students: {1 student-1 1 {1 Math []}}
// chan students: {2 student-2 2 {2 French []}}
// chan students: {3 student-3 1 {1 Math []}}
// chan students: {4 student-4 2 {2 French []}}
// class: {1 Math [{1 student-1 0 {0 []}} {3 student-3 0 {0 []}}]}
// class: {2 French [{2 student-2 0 {0 []}} {4 student-4 0 {0 []}}]}
// student: &{1 student-1 1 {1 Math []}}
// student: &{2 student-2 2 {2 French []}}
// student: &{3 student-3 1 {1 Math []}}
// student: &{4 student-4 2 {2 French []}}
// maps = [map[class:1 id:1 name:student-1] map[class:2 id:2 name:student-2] map[class:1 id:3 name:student-3] map[class:2 id:4 name:student-4]]
// names = [student-1 student-2 student-3 student-4]
// ids = [1 2 3 4]
// bools = [true]
// times = [2023-04-30 19:19:32 +0200 CEST]
Benchmark vs Tarantool, Pgx, Gorm
https://github.com/kamalshkeir/korm-vs-gorm-vs-tarantool-vs-pgx
Benchmarks vs Gorm
goos: windows
goarch: amd64
pkg: github.com/kamalshkeir/korm/benchmarks
cpu: Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz
To execute these benchmarks on your machine, very easy :
- git clone https://github.com/kamalshkeir/korm.git
- cd korm
- uncomment commented code at benchmarks/bench-test.go and Save
- go mod tidy
- go test -bench ^ .\benchmarks\ -benchmem
type TestTable struct {
Id uint `korm:"pk"`
Email string
Content string
Password string
IsAdmin bool
CreatedAt time.Time `korm:"now"`
UpdatedAt time.Time `korm:"update"`
}
type TestTableGorm struct {
Id uint `gorm:"primarykey"`
Email string
Content string
Password string
IsAdmin bool
CreatedAt time.Time
UpdatedAt time.Time
}
//////////////////////////////////////////// query 7000 rows //////////////////////////////////////////////
BenchmarkGetAllS_GORM-4 19 56049832 ns/op 12163316 B/op 328790 allocs/op
BenchmarkGetAllS-4 2708934 395.3 ns/op 224 B/op 1 allocs/op
BenchmarkGetAllM_GORM-4 18 62989567 ns/op 13212278 B/op 468632 allocs/op
BenchmarkGetAllM-4 4219461 273.5 ns/op 224 B/op 1 allocs/op
BenchmarkGetRowS_GORM-4 12188 96988 ns/op 5930 B/op 142 allocs/op
BenchmarkGetRowS-4 1473164 805.1 ns/op 336 B/op 7 allocs/op
BenchmarkGetRowM_GORM-4 11402 101638 ns/op 7408 B/op 203 allocs/op
BenchmarkGetRowM-4 1752652 671.9 ns/op 336 B/op 7 allocs/op
BenchmarkPagination10_GORM-4 7714 153304 ns/op 19357 B/op 549 allocs/op
BenchmarkPagination10-4 1285722 934.5 ns/op 400 B/op 7 allocs/op
BenchmarkPagination100_GORM-4 1364 738934 ns/op 165423 B/op 4704 allocs/op
BenchmarkPagination100-4 1278724 956.5 ns/op 400 B/op 7 allocs/op
BenchmarkQueryS-4 5781499 207.7 ns/op 4 B/op 1 allocs/op
BenchmarkQueryM-4 4643155 227.2 ns/op 4 B/op 1 allocs/op
BenchmarkGetAllTables-4 47465865 25.48 ns/op 0 B/op 0 allocs/op
BenchmarkGetAllColumns-4 23657019 42.82 ns/op 0 B/op 0 allocs/op
//////////////////////////////////////////// query 5000 rows //////////////////////////////////////////////
BenchmarkGetAllS_GORM-4 24 43247546 ns/op 8796840 B/op 234784 allocs/op
BenchmarkGetAllS-4 2854401 426.8 ns/op 224 B/op 1 allocs/op
BenchmarkGetAllM_GORM-4 24 46329242 ns/op 9433050 B/op 334631 allocs/op
BenchmarkGetAllM-4 4076317 283.4 ns/op 224 B/op 1 allocs/op
BenchmarkGetRowS_GORM-4 11445 101107 ns/op 5962 B/op 142 allocs/op
BenchmarkGetRowS-4 1344831 848.4 ns/op 336 B/op 7 allocs/op
BenchmarkGetRowM_GORM-4 10000 100969 ns/op 7440 B/op 203 allocs/op
BenchmarkGetRowM-4 1721742 688.5 ns/op 336 B/op 7 allocs/op
BenchmarkPagination10_GORM-4 7500 156208 ns/op 19423 B/op 549 allocs/op
BenchmarkPagination10-4 1253757 952.3 ns/op 400 B/op 7 allocs/op
BenchmarkPagination100_GORM-4 1564 749408 ns/op 165766 B/op 4704 allocs/op
BenchmarkPagination100-4 1236270 957.5 ns/op 400 B/op 7 allocs/op
BenchmarkGetAllTables-4 44399386 25.43 ns/op 0 B/op 0 allocs/op
BenchmarkGetAllColumns-4 27906392 41.45 ns/op 0 B/op 0 allocs/op
//////////////////////////////////////////// query 1000 rows //////////////////////////////////////////////
BenchmarkGetAllS_GORM-4 163 6766871 ns/op 1683919 B/op 46735 allocs/op
BenchmarkGetAllS-4 2882660 399.0 ns/op 224 B/op 1 allocs/op
BenchmarkGetAllM_GORM-4 140 8344988 ns/op 1886922 B/op 66626 allocs/op
BenchmarkGetAllM-4 3826730 296.5 ns/op 224 B/op 1 allocs/op
BenchmarkGetRowS_GORM-4 11940 97725 ns/op 5935 B/op 142 allocs/op
BenchmarkGetRowS-4 1333258 903.0 ns/op 336 B/op 7 allocs/op
BenchmarkGetRowM_GORM-4 10000 106079 ns/op 7408 B/op 203 allocs/op
BenchmarkGetRowM-4 1601274 748.2 ns/op 336 B/op 7 allocs/op
BenchmarkPagination10_GORM-4 7534 159991 ns/op 19409 B/op 549 allocs/op
BenchmarkPagination10-4 1153982 1022 ns/op 400 B/op 7 allocs/op
BenchmarkPagination100_GORM-4 1468 766269 ns/op 165876 B/op 4705 allocs/op
BenchmarkPagination100-4 1000000 1016 ns/op 400 B/op 7 allocs/op
BenchmarkGetAllTables-4 56200297 25.36 ns/op 0 B/op 0 allocs/op
BenchmarkGetAllColumns-4 25478679 41.30 ns/op 0 B/op 0 allocs/op
//////////////////////////////////////////// query 300 rows //////////////////////////////////////////////
BenchmarkGetAllS_GORM-4 558 2046830 ns/op 458475 B/op 13823 allocs/op
BenchmarkGetAllS-4 2798872 411.5 ns/op 224 B/op 1 allocs/op
BenchmarkGetAllM_GORM-4 428 2605646 ns/op 567011 B/op 19721 allocs/op
BenchmarkGetAllM-4 4093662 287.9 ns/op 224 B/op 1 allocs/op
BenchmarkGetRowS_GORM-4 12182 97764 ns/op 5966 B/op 142 allocs/op
BenchmarkGetRowS-4 1347084 886.4 ns/op 336 B/op 7 allocs/op
BenchmarkGetRowM_GORM-4 10000 105311 ns/op 7440 B/op 203 allocs/op
BenchmarkGetRowM-4 1390363 780.0 ns/op 336 B/op 7 allocs/op
BenchmarkPagination10_GORM-4 7502 155949 ns/op 19437 B/op 549 allocs/op
BenchmarkPagination10-4 1000000 1046 ns/op 400 B/op 7 allocs/op
BenchmarkPagination100_GORM-4 1479 779700 ns/op 165679 B/op 4705 allocs/op
BenchmarkPagination100-4 1000000 1054 ns/op 400 B/op 7 allocs/op
BenchmarkGetAllTables-4 52255704 26.00 ns/op 0 B/op 0 allocs/op
BenchmarkGetAllColumns-4 29292368 42.09 ns/op 0 B/op 0 allocs/op
//////////////////////////////////////////// MONGO //////////////////////////////////////////////
BenchmarkGetAllS-4 3121384 385.6 ns/op 224 B/op 1 allocs/op
BenchmarkGetAllM-4 4570059 264.2 ns/op 224 B/op 1 allocs/op
BenchmarkGetRowS-4 1404399 866.6 ns/op 336 B/op 7 allocs/op
BenchmarkGetRowM-4 1691026 722.6 ns/op 336 B/op 7 allocs/op
BenchmarkGetAllTables-4 47424489 25.34 ns/op 0 B/op 0 allocs/op
BenchmarkGetAllColumns-4 27039632 42.22 ns/op 0 B/op 0 allocs/op
//////////////////////////////////////////////////////////////////////////////////////////////////////////
Available Tags by struct field type:
String Field:
| Without parameter | With parameter |
|---|---|
|
|
Int, Uint, Int64, Uint64 Fields:
| Without parameter |
|---|
|
| With parameter |
|
Bool : bool is INTEGER NOT NULL checked between 0 and 1 (in order to be consistent accross sql dialects)
| Without parameter | With parameter |
|---|---|
|
|
time.Time :
| Without parameter | With parameter |
|---|---|
|
|
Float64 :
| Without parameter | With parameter |
|---|---|
|
|
JSON
type JsonOption struct {
As string
Dialect string
Database string
Params []any
}
func JSON_EXTRACT(dataJson string, opt ...JsonOption) string
func JSON_REMOVE(dataJson string, opt ...JsonOption) string
func JSON_SET(dataJson string, opt ...JsonOption) string
func JSON_ARRAY(values []any, as string, dialect ...string) string
func JSON_OBJECT(values []any, as string, dialect ...string) string
func JSON_CAST(value string, as string, dialect ...string) string
// create query json
q := korm.JSON_EXTRACT(`{"a": {"c": 3}, "b": 2}`, korm.JsonOption{
As: "data",
Params: []any{"a.c", "b"},
})
fmt.Println("q ==", q) // q == JSON_EXTRACT('{"a": {"c": 3}, "b": 2}','$.a.c','$.b') AS data
data, err := korm.Select[map[string]any]().Query("SELECT " + q)
lg.CheckError(err)
fmt.Println("data=", data) // data= [map[data:[3,2]]]
🤖 MCP Server (Model Context Protocol)
KORM embeds a native Model Context Protocol (MCP) server, enabling AI IDEs and agents (Cursor, Claude Desktop, Antigravity, etc.) to directly inspect, query, and diff your database in real time.
Available MCP Tools
| Tool | Description |
|---|---|
korm_get_schema |
Returns complete schema, columns, primary keys, and foreign key relations graph. |
korm_execute_query |
Executes safe read-only SQL queries (SELECT, EXPLAIN) with automatic LIMIT enforcement. |
korm_get_diff |
Inspects structural differences between declared Go struct models and physical database tables. |
korm_cache_stats |
Inspects in-memory KORM cache metrics, RAM usage, and active entries. |
korm_generate_go_struct |
Reverse-engineers any physical table into clean Go struct code with KORM tags. |
How to use in any KORM project
No separate binary build is required! Simply enable the shell in your app's main.go:
package main
import (
"github.com/kamalshkeir/korm"
"github.com/kamalshkeir/sqlitedriver"
)
func main() {
err := korm.New(korm.SQLITE, "db", sqlitedriver.Use())
if err != nil {
return
}
defer korm.Shutdown()
// Enables CLI commands, interactive shell, and MCP server
korm.WithShell()
// Start your web server or app...
}
Connect Cursor or Claude Desktop (stdio)
Add the following to your AI tool's MCP configuration (e.g. claude_desktop_config.json or .cursor/mcp.json):
{
"mcpServers": {
"my-korm-app": {
"command": "go",
"args": ["run", "main.go", "mcp"],
"cwd": "/path/to/your/korm-project"
}
}
}
HTTP / SSE Transport (Web & Remote)
When using korm.WithDashboard(":9313"), the MCP endpoints are automatically mounted:
- Server-Sent Events (SSE):
GET /admin/mcp/sse - JSON-RPC HTTP POST:
POST /admin/mcp/rpc - SSE Message Bridge:
POST /admin/mcp/message?session_id=...
🔗 Links
Licence
Licence BSD-3
관련 프로젝트 살펴보기
Asabeneh/30-Days-Of-Python
30 Days of Python 프로그래밍 챌린지는 Python 프로그래밍 언어를 30일 동안 배우기 위한 단계별 가이드입니다. 이 챌린지는 100일 이상 걸릴 수 있습니다. 자신의 속도에 맞춰 진행하세요. 다음 동영상도 도움이 될 수 있습니다: https://www.youtube.com/channel/UC7PNRuno1rzYPb1xLa4yktw
78,423 스타grafana/grafana
개방적이고 조합 가능한 옵저버빌리티 및 데이터 시각화 플랫폼입니다. Prometheus, Loki, Elasticsearch, InfluxDB, Postgres 등 여러 소스의 메트릭, 로그, 트레이스를 시각화합니다.
77,763 스타redis/redis
실시간 데이터 기반 애플리케이션을 구축하는 개발자에게 Redis는 가장 선호되고, 가장 빠르며, 기능이 가장 풍부한 캐시, 데이터 구조 서버이자 문서 및 벡터 쿼리 엔진입니다.
77,309 스타apache/superset
Apache Superset는 데이터 시각화 및 데이터 탐색 플랫폼입니다.
75,453 스타