gofr store
Available since:
gofr-cliv0.8.1
The gofr store command is a code generator that creates type-safe data access layers from YAML configuration files. It eliminates boilerplate code while maintaining GoFr's best practices for observability and context management.
Features
- YAML-Driven Configuration: Define your data models and queries in a simple, declarative format.
- Type-Safe Code Generation: Generates Go interfaces and implementation boilerplates.
- GoFr Context Integration: Generated methods work with
*gofr.Contextfor built-in observability. - Multiple Stores: Define all stores in a single YAML file — each gets its own directory.
- Store Registry: Centralized factory management of all generated stores via
stores/all.go.
Commands
Initialize Store Configuration
Create a new store directory and a store.yaml configuration template. The -name flag is required.
gofr store init -name=<store-name>
Example:
gofr store init -name=user
This creates the following structure:
stores/store.yaml— Configuration file template (shared across all stores).stores/all.go— Store registry factory (auto-generated, DO NOT EDIT).stores/user/interface.go— Initial interface stub (DO NOT EDIT — regenerated bygenerate).stores/user/user.go— Initial implementation stub (editable — add your SQL logic here).
Generate Store Code
Generate or update Go code from your store configuration file.
gofr store generate
💡 Note: By default, this command looks for the configuration at
stores/store.yaml. To use a different path, use the-configflag:Bashgofr store generate -config=path/to/store.yaml
Quick Start Example
Step 1: Initialize Configuration
gofr store init -name=user
Step 2: Define Your Store in stores/store.yaml
version: "1.0"
stores:
- name: "user"
package: "user"
output_dir: "stores/user"
interface: "UserStore"
implementation: "userStore"
queries:
- name: "GetUserByID"
sql: "SELECT id, name, email FROM users WHERE id = ?"
type: "select"
model: "User"
returns: "single"
params:
- name: "id"
type: "int64"
description: "Retrieves a user by their ID"
- name: "GetAllUsers"
sql: "SELECT id, name, email FROM users"
type: "select"
model: "User"
returns: "multiple"
description: "Retrieves all users"
models:
- name: "User"
fields:
- name: "ID"
type: "int64"
tag: 'db:"id" json:"id"'
- name: "Name"
type: "string"
tag: 'db:"name" json:"name"'
- name: "Email"
type: "string"
tag: 'db:"email" json:"email"'
Step 3: Generate Store Code
gofr store generate
This generates:
stores/
├── store.yaml # Central Configuration
├── all.go # Store registry factory (auto-generated)
└── user/
├── interface.go # UserStore interface definition
├── userStore.go # userStore implementation boilerplate
└── user.go # User model struct
Step 4: Use in Your Application
package main
import (
"gofr.dev/pkg/gofr"
"your-project/stores/user"
)
func main() {
app := gofr.New()
userStore := user.NewUserStore()
app.GET("/users/{id}", func(ctx *gofr.Context) (interface{}, error) {
id, _ := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
return userStore.GetUserByID(ctx, id)
})
app.GET("/users", func(ctx *gofr.Context) (interface{}, error) {
return userStore.GetAllUsers(ctx)
})
app.Run()
}
Multiple Stores in One File
You can define all stores in a single YAML file. Each store gets its own output directory and all are registered into the same stores/all.go registry.
version: "1.0"
stores:
- name: "user"
package: "user"
output_dir: "stores/user"
interface: "UserStore"
implementation: "userStore"
queries: [...]
- name: "product"
package: "product"
output_dir: "stores/product"
interface: "ProductStore"
implementation: "productStore"
queries: [...]
models:
- name: "User"
fields: [...]
- name: "Product"
fields: [...]
Generated structure:
stores/
├── all.go
├── user/
│ ├── interface.go
│ ├── userStore.go
│ └── user.go
└── product/
├── interface.go
├── productStore.go
└── product.go
Using the registry with multiple stores:
import (
"your-project/stores"
"your-project/stores/user"
"your-project/stores/product"
)
// stores.GetStore returns a factory-created instance
userStore := stores.GetStore("user").(user.UserStore)
productStore := stores.GetStore("product").(product.ProductStore)
💡 Note:
stores.All()returns amap[string]func() any— a map of factory functions, not active instances.stores.GetStore(name)calls the factory for you and returns the instance.
Configuration Reference
Store Configuration
| Field | Description | Required |
|---|---|---|
name | Store identifier used in the registry key. | Yes |
package | Go package name for generated code. | Yes |
output_dir | Directory path where files will be generated. | Optional (defaults to stores/<name>) |
interface | Interface name — recommended: <Name>Store (e.g., UserStore). | Optional (defaults to <Name>Store) |
implementation | Private struct name for the implementation (e.g., userStore). | Optional (defaults to <name>Store) |
queries | List of database queries. | Optional |
⚠️ Naming Convention: The registry (
stores/all.go) uses a hardcoded<Name>Storepattern when generating constructor calls (e.g.,NewUserStore()). Always name your interface as<Name>Storeto avoid compilation errors.
Query Types
select— SELECT queries.insert— INSERT queries.update— UPDATE queries.delete— DELETE queries.
Return Types
single— Returns(Model, error).multiple— Returns([]Model, error).count— Returns(int64, error).custom— Returns(any, error).
Query Parameters
params:
- name: "id"
type: "int64"
- name: "email"
type: "string"
Supported parameter types include all Go primitive types, time.Time, and pointer types (e.g., *int64).
Model Generation
Generate New Models
models:
- name: "User"
fields:
- name: "ID"
type: "int64"
tag: 'db:"id" json:"id"'
- name: "Name"
type: "string"
tag: 'db:"name" json:"name"'
- name: "CreatedAt"
type: "time.Time"
tag: 'db:"created_at" json:"created_at"'
This generates:
type User struct {
ID int64 `db:"id" json:"id"`
Name string `db:"name" json:"name"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
}
func (User) TableName() string {
return "user"
}
Reference Existing Models
If you already have models defined elsewhere:
models:
- name: "User"
path: "../models/user.go"
package: "your-project/models"
Generated Code Structure
Interface (interface.go)
// Code generated by gofr.dev/cli/gofr. DO NOT EDIT.
package user
import "gofr.dev/pkg/gofr"
type UserStore interface {
GetUserByID(ctx *gofr.Context, id int64) (User, error)
GetAllUsers(ctx *gofr.Context) ([]User, error)
}
Implementation (userStore.go)
// Code generated by gofr.dev/cli/gofr. DO NOT EDIT.
package user
type userStore struct{}
func NewUserStore() UserStore {
return &userStore{}
}
func (s *userStore) GetUserByID(ctx *gofr.Context, id int64) (User, error) {
// TODO: Implement using ctx.SQL()
var result User
// err := ctx.SQL().QueryRowContext(ctx, sql, id).Scan(&result.ID, ...)
return result, nil
}
func (s *userStore) GetAllUsers(ctx *gofr.Context) ([]User, error) {
// TODO: Implement using ctx.SQL()
return []User{}, nil
}
Best Practices
- Implement the TODOs: The generator creates method signatures and boilerplate only. You must fill in the
// TODO: Implementsections with actual SQL execution usingctx.SQL()methods. - Use
<Name>StoreInterface Names: The registry assumes this convention. E.g.,interface: "UserStore"results in the constructorNewUserStore()and type assertion.(user.UserStore). - One YAML, Many Stores: Define all your stores in a single
store.yamlto keep your data access layer centrally configured. - Know Which Files Are Auto-Generated: Only
interface.goandall.goare markedDO NOT EDITand are overwritten on everygofr store generate. The implementation stub (<name>.go) created bygofr store initis editable — this is where you add your SQL logic. TheuserStore.gogenerated bygofr store generateis also editable boilerplate. - Version Control: Always commit your
store.yaml. Re-rungofr store generateafter any configuration change to sync the generated interfaces.
Complete Example
For a complete working example of the store generator, see the store example in the gofr-cli repository.
For detailed configuration options and advanced usage, refer to the Store Generator README.