Infrastructure

GORM Studio (Database Browser)

A database browser that runs inside your API, reached at /studio. Tables, rows, relationships, a query box, import and export. It is mounted in every Grit project, because the moments you most want to look at the data are a staging container and a customer's install, and those are exactly where a desktop database client is not.

What it gives you

It is a database console, reachable over HTTPNearly all of Studio's design is constraints, for that reason. Put authentication in front of it, keep the SQL editor off unless a task needs it, hide the tables that should never be read, and serve it over HTTPS. grit doctor fails when it is mounted with no login or on a default password.

Every feature, in detail

Schema Discovery

Studio reads your schema from two places: the database itself, and your GORM model structs by reflection. Having both is the useful part, because the gap between them is where migration bugs live. A column the struct declares and the database does not have is a migration that never ran; a column the database has and no struct mentions is one a DropColumn forgot.

SQLite, PostgreSQL and MySQL are supported. SQLite goes through a pure-Go driver, so there is no CGO and no build toolchain to install.

Browse and Filter

A paginated grid per table, with column sorting and full-text search across the row. It is a data grid rather than a report builder: the question it answers well is "what does this row actually contain", which is the question you usually have at the point you reach for a database client.

Editing Records

Create, edit and delete through modal forms, and select several rows for a bulk delete. Deletion goes through a styled confirmation rather than the browser's own confirm(), which matters more than it sounds: the native dialog is easy to dismiss by reflex.

Writes here bypass your handlers and therefore your validation, your hooks and your audit log. That is the point when you are repairing data, and a hazard when you are not, which is what the read-only policy below is for.

Relationship Navigation

Foreign keys are shown as links you can follow, across has_one, has_many, belongs_to and many_to_many. In practice this is what makes the tool fast to use: tracing an order to its customer to their addresses is three clicks rather than three queries with ids copied between them.

Raw SQL Editor

A query box, with read and write detected automatically and DDL blocked, so a stray DROP TABLE is refused rather than run. A table listed as hidden is refused here too, not only in the grid.

It cannot be row-scoped, because arbitrary SQL cannot be. If you use the scoping below to keep one tenant out of another's data, turn the SQL editor off with DisableSQL: true, or the scoping is decoration.

Export

Schema as SQL DDL, JSON, YAML, DBML, or an ERD diagram as PNG or PDF. Data as JSON, CSV (zipped, one file per table) or SQL INSERT statements.

CSV export escapes formula injection, so a cell beginning = or + arrives in a spreadsheet as text rather than as something that runs. A field an attacker controls is an attack on whoever opens the export, which is usually not the person who was attacked.

Import

Data from JSON, CSV, SQL or Excel. Schema from SQL, JSON, YAML or DBML, which creates the tables. Useful for seeding an environment from a sample, and the fastest way to ruin one, so it belongs behind the same authentication as everything else here.

Go Code Generation

Two directions. Generate Go structs from an existing schema, with the GORM tags that match, which is how you adopt a database you did not create. Or upload a .go file of struct definitions and have the tables created from it.

In a Grit project you usually want grit generate resource instead, which writes the model plus the service, handler, routes, schemas, types and admin screen. Studio's generator gives you the struct alone.

Access Control

Three controls, and they compose:

studio.Mount(router, db, models, studio.Config{
DisableSQL: true, // the SQL editor cannot be scoped
Scope: func(c *gin.Context, table string, tx *gorm.DB) *gorm.DB {
if table == "orders" {
return tx.Where("tenant_id = ?", c.GetString("tenant_id"))
}
return tx
},
TablePolicy: studio.TablePolicy{
Hidden: []string{"payment_tokens"}, // never exposed
ReadOnly: []string{"audit_log"}, // browsable, not editable
},
AuditLogger: studio.DefaultAuditLogger, // log every mutation
})

Scope narrows what a request may see, per table, from whatever the request carries. Hidden removes a table entirely, including from the SQL editor. ReadOnly lets a table be browsed but not changed, which is the right setting for anything whose value is that nobody edited it: an audit log, an append-only ledger. AuditLogger records every mutation made through Studio, because a change made here leaves no trace in your application's own log.

Authentication

With AuthMiddleware configured, Studio serves a themed login page rather than the browser's native auth popup. The credential token is kept in memory only and never written to browser storage, so a refresh asks again and a shared machine does not keep your database open for the next person.

In a Grit project this is GORM_STUDIO_USERNAME and GORM_STUDIO_PASSWORD. The scaffold adds the middleware only when both are set, which means forgetting one leaves Studio open; grit doctor reports exactly that, and reports a default password as an error in production.

Turning it off

GORM_STUDIO_ENABLED=false does not mount it at all. Worth doing on a deployment where nobody is going to use it: the safest console is the one that is not there.