SQL-first Bun is a SQL-first Golang ORM for PostgreSQL, MySQL/MariaDB, MSSQL, and SQLite. SQL-first means you can write SQL queries in Go, for example, this Bun query: var num int err := db.NewSelect(). TableExpr("generate_series(1, 3)"). Where("generate_series = ?", 3). Limit(10). Scan(ctx, &num) Generates the following SQL: SELECT * FROM generate_series(1, 3) WHERE generate_series = 123 LIMIT 10 SQL is still there, but Bun helps you generate long queries while protecting against SQL injections thanks to placeholders: ? Where("id = ?", 123) // WHERE id = 123 Where("id >= ?", 123) // WHERE id >= 123 Where("id = ?", "hello") // WHERE id = 'hello' Where("id IN (?)", bun.In([]int{1, 2, 3})) // WHERE id IN (1, 2, 3) Where("? = ?", bun.Ident("column"), "value") // WHERE "column" = 'value' Using Bun, you can write really complex queries, for example, the following Bun query: regionalSales := db.NewSelect(). ColumnExpr("region"). ColumnExpr("SUM(amount) AS total_sales"). TableExpr("orders"). GroupExpr("region") topRegions := db.NewSelect(). ColumnExpr("region"). TableExpr("regional_sales"). Where("total_sales > (SELECT SUM(total_sales) / 10 FROM regional_sales)") var []items map[string]interface{} err := db.NewSelect(). With("regional_sales", regionalSales). With("top_regions", topRegions). ColumnExpr("region"). ColumnExpr("product"). ColumnExpr("SUM(quantity) AS product_units"). ColumnExpr("SUM(amount) AS product_sales"). TableExpr("orders"). Where("region IN (SELECT region FROM top_regions)"). GroupExpr("region"). GroupExpr("product"). Scan(ctx, &items) Generates the following SQL: WITH regional_sales AS ( SELECT region, SUM(amount) AS total_sales FROM orders GROUP BY region ), top_regions AS ( SELECT region FROM regional_sales WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales) ) SELECT region, product, SUM(quantity) AS product_units, SUM(amount) AS product_sales FROM orders WHERE region IN (SELECT region FROM top_regions) GROUP BY region, product Structs and tables Bun allows you to map Go structs to database tables using struct-based models, for example, the following code: type Model struct { ID int64 `bun:",pk,autoincrement"` Name string `bun:",notnull"` CreatedAt time.Time `bun:",nullzero,default:now()"` } err := db.ResetModel(ctx, &Model{}) Generates the following table: CREATE TABLE "models" ( "id" BIGSERIAL NOT NULL, "name" VARCHAR NOT NULL, "created_at" TIMESTAMPTZ DEFAULT now(), PRIMARY KEY ("id"), ) You can then select/insert/update/delete rows using Go structs: model := new(Model) err := db.NewSelect().Model().Where("id = ?", 123).Scan(ctx) model.ID = 0 res, err := db.NewInsert().Model(model).Exec(ctx) res, err := db.NewUpdate(). Model(model). Set("name = ?", "updated name"). WherePK(). Exec(ctx) res, err := db.NewDelete().Model(model).WherePK().Exec(ctx) See Bun documentation for details. Golang ORM So what about the Golang ORM part? Bun allows you to define common table relations using Go structs, for example, here is how you can define belongs to relation: Author Book type Book struct { ID int64 AuthorID int64 Author Author `bun:"rel:belongs-to,join:author_id=id"` } type Author struct { ID int64 } And then use method to join tables: Relation err := db.NewSelect(). Model(book). Relation("Author"). Where("id = ?", 123). Scan(ctx) SELECT "book"."id", "book"."title", "book"."text", "author"."id" AS "author__id", "author"."name" AS "author__name" FROM "books" LEFT JOIN "users" AS "author" ON "author"."id" = "book"."author_id" WHERE id = 1 See ORM: Table relationships for details. Connecting to a database Bun works on top of database/SQL and supports PostgreSQL, MySQL/MariaDB, MSSQL, and SQLite. To connect to a PostgreSQL database: import ( "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/pgdialect" "github.com/uptrace/bun/driver/pgdriver" ) dsn := "postgres://postgres:@localhost:5432/test?sslmode=disable" sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn))) db := bun.NewDB(sqldb, pgdialect.New()) To connect to a database: MySQL import ( "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/mysqldialect" _ "github.com/go-sql-driver/mysql" ) sqldb, err := sql.Open("mysql", "root:pass@/test") if err != nil { panic(err) } db := bun.NewDB(sqldb, mysqldialect.New()) To log all executed queries, you can install bundebug plugin: import "github.com/uptrace/bun/extra/bundebug" db.AddQueryHook(bundebug.NewQueryHook( bundebug.WithVerbose(true), // log everything )) Executing queries Once you have a model, you can start executing queries: // Select a user by a primary key. user := new(User) err := db.NewSelect().Model(user).Where("id = ?", 1).Scan(ctx) // Select first 10 users. var users []User err := db.NewSelect().Model(&users).OrderExpr("id ASC").Limit(10).Scan(ctx) When it comes to scanning query results, Bun is very flexible and allows scanning into structs: user := new(User) err := db.NewSelect().Model(user).Limit(1).Scan(ctx) Into scalars: var id int64 var name string err := db.NewSelect().Model((*User)(nil)).Column("id", "name").Limit(1).Scan(ctx, &id, &name) Into a : map[string]interface{} var m map[string]interface{} err := db.NewSelect().Model((*User)(nil)).Limit(1).Scan(ctx, &m) And into slices of the types above: var users []User err := db.NewSelect().Model(&users).Limit(1).Scan(ctx) var ids []int64 var names []string err := db.NewSelect().Model((*User)(nil)).Column("id", "name").Limit(1).Scan(ctx, &ids, &names) var ms []map[string]interface{} err := db.NewSelect().Model((*User)(nil)).Scan(ctx, &ms) You can also return results from insert/update/delete queries and scan them too: var ids []int64 res, err := db.NewDelete().Model((*User)(nil)).Returning("id").Exec(ctx, &ids) What's next? To get started, see the documentation and run examples. Bun comes with many plugins including OpenTelemetry instrumentation that enables distributed tracing and metrics. Using tracing, you can monitor performance using one of the open-source tracing tools that work with OpenTelemetry. Many DataDog competitors also support OpenTelemetry. Besides, you can export metrics to Prometheus and visualize them using Grafana or a popular alternative.