Skip to content

Commit 5ca7a97

Browse files
authored
Merge pull request #2990 from gofr-dev/docs/cli-store-generator
docs: Add comprehensive documentation for gofr store generator
2 parents a03d4b0 + bedef6f commit 5ca7a97

File tree

1 file changed

+353
-0
lines changed

1 file changed

+353
-0
lines changed

docs/references/gofrcli/page.md

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Managing repetitive tasks and maintaining consistency across large-scale applica
66

77
* All-in-one command-line tool designed specifically for GoFr applications
88
* Simplifies **database migrations** management
9+
* **Store Layer Generator** for type-safe data access code from YAML configurations
910
* Abstracts **tracing**, **metrics** and structured **logging** for GoFr's gRPC server/client
1011
* Enforces standard **GoFr conventions** in new projects
1112

@@ -206,3 +207,355 @@ func main() {
206207
```
207208
For detailed instruction on setting up a gRPC server with GoFr see the [gRPC Client Documentation](https://gofr.dev/docs/advanced-guide/grpc#generating-tracing-enabled-g-rpc-client-using)
208209
For more examples refer [gRPC Examples](https://github.com/gofr-dev/gofr/tree/main/examples/grpc)
210+
211+
---
212+
## 4. ***`store`***
213+
214+
> **Available since:** `gofr-cli` **v0.8.1**
215+
216+
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.
217+
218+
### **Features**
219+
220+
* **YAML-Driven Configuration**: Define your data models and queries in a simple, declarative format.
221+
* **Type-Safe Code Generation**: Generates Go interfaces and implementation boilerplates.
222+
* **GoFr Context Integration**: Generated methods work with `*gofr.Context` for built-in observability.
223+
* **Multiple Stores**: Define all stores in a single YAML file — each gets its own directory.
224+
* **Store Registry**: Centralized factory management of all generated stores via `stores/all.go`.
225+
226+
### **Commands**
227+
228+
#### **Initialize Store Configuration**
229+
230+
Create a new store directory and a `store.yaml` configuration template. **The `-name` flag is required.**
231+
232+
```bash
233+
gofr store init -name=<store-name>
234+
```
235+
236+
**Example:**
237+
```bash
238+
gofr store init -name=user
239+
```
240+
241+
This creates the following structure:
242+
- `stores/store.yaml` — Configuration file template (shared across all stores).
243+
- `stores/all.go` — Store registry factory (auto-generated, DO NOT EDIT).
244+
- `stores/user/interface.go` — Initial interface stub (DO NOT EDIT — regenerated by `generate`).
245+
- `stores/user/user.go` — Initial implementation stub (editable — add your SQL logic here).
246+
247+
#### **Generate Store Code**
248+
249+
Generate or update Go code from your store configuration file.
250+
251+
```bash
252+
gofr store generate
253+
```
254+
255+
> **💡 Note:** By default, this command looks for the configuration at **`stores/store.yaml`**. To use a different path, use the `-config` flag:
256+
> ```bash
257+
> gofr store generate -config=path/to/store.yaml
258+
> ```
259+
260+
---
261+
262+
### **Quick Start Example**
263+
264+
**Step 1: Initialize Configuration**
265+
```bash
266+
gofr store init -name=user
267+
```
268+
269+
**Step 2: Define Your Store in `stores/store.yaml`**
270+
```yaml
271+
version: "1.0"
272+
273+
stores:
274+
- name: "user"
275+
package: "user"
276+
output_dir: "stores/user"
277+
interface: "UserStore"
278+
implementation: "userStore"
279+
queries:
280+
- name: "GetUserByID"
281+
sql: "SELECT id, name, email FROM users WHERE id = ?"
282+
type: "select"
283+
model: "User"
284+
returns: "single"
285+
params:
286+
- name: "id"
287+
type: "int64"
288+
description: "Retrieves a user by their ID"
289+
290+
- name: "GetAllUsers"
291+
sql: "SELECT id, name, email FROM users"
292+
type: "select"
293+
model: "User"
294+
returns: "multiple"
295+
description: "Retrieves all users"
296+
297+
models:
298+
- name: "User"
299+
fields:
300+
- name: "ID"
301+
type: "int64"
302+
tag: 'db:"id" json:"id"'
303+
- name: "Name"
304+
type: "string"
305+
tag: 'db:"name" json:"name"'
306+
- name: "Email"
307+
type: "string"
308+
tag: 'db:"email" json:"email"'
309+
```
310+
311+
**Step 3: Generate Store Code**
312+
```bash
313+
gofr store generate
314+
```
315+
316+
This generates:
317+
```
318+
stores/
319+
├── store.yaml # Central Configuration
320+
├── all.go # Store registry factory (auto-generated)
321+
└── user/
322+
├── interface.go # UserStore interface definition
323+
├── userStore.go # userStore implementation boilerplate
324+
└── user.go # User model struct
325+
```
326+
327+
**Step 4: Use in Your Application**
328+
```go
329+
package main
330+
331+
import (
332+
"gofr.dev/pkg/gofr"
333+
"your-project/stores/user"
334+
)
335+
336+
func main() {
337+
app := gofr.New()
338+
339+
userStore := user.NewUserStore()
340+
341+
app.GET("/users/{id}", func(ctx *gofr.Context) (interface{}, error) {
342+
id, _ := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
343+
return userStore.GetUserByID(ctx, id)
344+
})
345+
346+
app.GET("/users", func(ctx *gofr.Context) (interface{}, error) {
347+
return userStore.GetAllUsers(ctx)
348+
})
349+
350+
app.Run()
351+
}
352+
```
353+
354+
---
355+
356+
### **Multiple Stores in One File**
357+
358+
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.
359+
360+
```yaml
361+
version: "1.0"
362+
363+
stores:
364+
- name: "user"
365+
package: "user"
366+
output_dir: "stores/user"
367+
interface: "UserStore"
368+
implementation: "userStore"
369+
queries: [...]
370+
371+
- name: "product"
372+
package: "product"
373+
output_dir: "stores/product"
374+
interface: "ProductStore"
375+
implementation: "productStore"
376+
queries: [...]
377+
378+
models:
379+
- name: "User"
380+
fields: [...]
381+
- name: "Product"
382+
fields: [...]
383+
```
384+
385+
**Generated structure:**
386+
```
387+
stores/
388+
├── all.go
389+
├── user/
390+
│ ├── interface.go
391+
│ ├── userStore.go
392+
│ └── user.go
393+
└── product/
394+
├── interface.go
395+
├── productStore.go
396+
└── product.go
397+
```
398+
399+
**Using the registry with multiple stores:**
400+
```go
401+
import (
402+
"your-project/stores"
403+
"your-project/stores/user"
404+
"your-project/stores/product"
405+
)
406+
407+
// stores.GetStore returns a factory-created instance
408+
userStore := stores.GetStore("user").(user.UserStore)
409+
productStore := stores.GetStore("product").(product.ProductStore)
410+
```
411+
412+
> **💡 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.
413+
414+
---
415+
416+
### **Configuration Reference**
417+
418+
#### **Store Configuration**
419+
420+
| Field | Description | Required |
421+
|-------|-------------|----------|
422+
| `name` | Store identifier used in the registry key. | **Yes** |
423+
| `package` | Go package name for generated code. | **Yes** |
424+
| `output_dir` | Directory path where files will be generated. | Optional (defaults to `stores/<name>`) |
425+
| `interface` | Interface name — **recommended: `<Name>Store`** (e.g., `UserStore`). | Optional (defaults to `<Name>Store`) |
426+
| `implementation` | Private struct name for the implementation (e.g., `userStore`). | Optional (defaults to `<name>Store`) |
427+
| `queries` | List of database queries. | Optional |
428+
429+
> **⚠️ 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.
430+
431+
#### **Query Types**
432+
433+
* **`select`** — SELECT queries.
434+
* **`insert`** — INSERT queries.
435+
* **`update`** — UPDATE queries.
436+
* **`delete`** — DELETE queries.
437+
438+
#### **Return Types**
439+
440+
* **`single`** — Returns `(Model, error)`.
441+
* **`multiple`** — Returns `([]Model, error)`.
442+
* **`count`** — Returns `(int64, error)`.
443+
* **`custom`** — Returns `(any, error)`.
444+
445+
#### **Query Parameters**
446+
447+
```yaml
448+
params:
449+
- name: "id"
450+
type: "int64"
451+
- name: "email"
452+
type: "string"
453+
```
454+
455+
Supported parameter types include all Go primitive types, `time.Time`, and pointer types (e.g., `*int64`).
456+
457+
---
458+
459+
### **Model Generation**
460+
461+
#### **Generate New Models**
462+
463+
```yaml
464+
models:
465+
- name: "User"
466+
fields:
467+
- name: "ID"
468+
type: "int64"
469+
tag: 'db:"id" json:"id"'
470+
- name: "Name"
471+
type: "string"
472+
tag: 'db:"name" json:"name"'
473+
- name: "CreatedAt"
474+
type: "time.Time"
475+
tag: 'db:"created_at" json:"created_at"'
476+
```
477+
478+
This generates:
479+
```go
480+
type User struct {
481+
ID int64 `db:"id" json:"id"`
482+
Name string `db:"name" json:"name"`
483+
CreatedAt time.Time `db:"created_at" json:"created_at"`
484+
}
485+
486+
func (User) TableName() string {
487+
return "user"
488+
}
489+
```
490+
491+
#### **Reference Existing Models**
492+
493+
If you already have models defined elsewhere:
494+
495+
```yaml
496+
models:
497+
- name: "User"
498+
path: "../models/user.go"
499+
package: "your-project/models"
500+
```
501+
502+
---
503+
504+
### **Generated Code Structure**
505+
506+
#### **Interface (`interface.go`)**
507+
508+
```go
509+
// Code generated by gofr.dev/cli/gofr. DO NOT EDIT.
510+
package user
511+
512+
import "gofr.dev/pkg/gofr"
513+
514+
type UserStore interface {
515+
GetUserByID(ctx *gofr.Context, id int64) (User, error)
516+
GetAllUsers(ctx *gofr.Context) ([]User, error)
517+
}
518+
```
519+
520+
#### **Implementation (`userStore.go`)**
521+
522+
```go
523+
// Code generated by gofr.dev/cli/gofr. DO NOT EDIT.
524+
package user
525+
526+
type userStore struct{}
527+
528+
func NewUserStore() UserStore {
529+
return &userStore{}
530+
}
531+
532+
func (s *userStore) GetUserByID(ctx *gofr.Context, id int64) (User, error) {
533+
// TODO: Implement using ctx.SQL()
534+
var result User
535+
// err := ctx.SQL().QueryRowContext(ctx, sql, id).Scan(&result.ID, ...)
536+
return result, nil
537+
}
538+
539+
func (s *userStore) GetAllUsers(ctx *gofr.Context) ([]User, error) {
540+
// TODO: Implement using ctx.SQL()
541+
return []User{}, nil
542+
}
543+
```
544+
545+
---
546+
547+
### **Best Practices**
548+
549+
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.
550+
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)`.
551+
3. **One YAML, Many Stores**: Define all your stores in a single `store.yaml` to keep your data access layer centrally configured.
552+
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.
553+
5. **Version Control**: Always commit your `store.yaml`. Re-run `gofr store generate` after any configuration change to sync the generated interfaces.
554+
555+
---
556+
557+
### **Complete Example**
558+
559+
For a complete working example of the store generator, see the [store example](https://github.com/gofr-dev/gofr-cli/tree/main/store/example.yaml) in the gofr-cli repository.
560+
561+
For detailed configuration options and advanced usage, refer to the [Store Generator README](https://github.com/gofr-dev/gofr-cli/blob/main/store/README.md).

0 commit comments

Comments
 (0)