Skip to main content

gofr store

Available since: gofr-cli v0.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.Context for 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.

Bash
gofr store init -name=<store-name>

Example:

Bash
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 by generate).
  • 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.

Bash
gofr store generate

💡 Note: By default, this command looks for the configuration at stores/store.yaml. To use a different path, use the -config flag:

Bash
gofr store generate -config=path/to/store.yaml

Quick Start Example

Step 1: Initialize Configuration

Bash
gofr store init -name=user

Step 2: Define Your Store in stores/store.yaml

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

Bash
gofr store generate

This generates:

text
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

Go
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.

YAML
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:

text
stores/
├── all.go
├── user/
│   ├── interface.go
│   ├── userStore.go
│   └── user.go
└── product/
    ├── interface.go
    ├── productStore.go
    └── product.go

Using the registry with multiple stores:

Go
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 a map[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

FieldDescriptionRequired
nameStore identifier used in the registry key.Yes
packageGo package name for generated code.Yes
output_dirDirectory path where files will be generated.Optional (defaults to stores/<name>)
interfaceInterface name — recommended: <Name>Store (e.g., UserStore).Optional (defaults to <Name>Store)
implementationPrivate struct name for the implementation (e.g., userStore).Optional (defaults to <name>Store)
queriesList of database queries.Optional

⚠️ Naming Convention: The registry (stores/all.go) uses a hardcoded <Name>Store pattern when generating constructor calls (e.g., NewUserStore()). Always name your interface as <Name>Store to 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

YAML
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

YAML
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:

Go
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:

YAML
models:
  - name: "User"
    path: "../models/user.go"
    package: "your-project/models"

Generated Code Structure

Interface (interface.go)

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)

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

  1. Implement the TODOs: The generator creates method signatures and boilerplate only. You must fill in the // TODO: Implement sections with actual SQL execution using ctx.SQL() methods.
  2. Use <Name>Store Interface Names: The registry assumes this convention. E.g., interface: "UserStore" results in the constructor NewUserStore() and type assertion .(user.UserStore).
  3. One YAML, Many Stores: Define all your stores in a single store.yaml to keep your data access layer centrally configured.
  4. Know Which Files Are Auto-Generated: Only interface.go and all.go are marked DO NOT EDIT and are overwritten on every gofr store generate. The implementation stub (<name>.go) created by gofr store init is editable — this is where you add your SQL logic. The userStore.go generated by gofr store generate is also editable boilerplate.
  5. Version Control: Always commit your store.yaml. Re-run gofr store generate after 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.


See also