Flash ORM - Go Usage Guide
A comprehensive guide to using Flash ORM with Go projects, featuring type-safe code generation and high performance.
Table of Contents
- Installation
- Quick Start
- Configuration
- Schema Definition
- Migrations
- Code Generation
- Working with Generated Code
- Advanced Usage
- Best Practices
Installation
Install Flash ORM CLI
bash
go install github.com/Lumos-Labs-HQ/flash@latestVerify installation:
bash
flash --versionQuick Start
1. Create a New Go Project
bash
mkdir myproject && cd myproject
go mod init myproject2. Initialize Flash ORM
bash
# For PostgreSQL
flash init --postgresql
# For MySQL
flash init --mysql
# For SQLite
flash init --sqlite3. Set Database URL
Create a .env file:
env
# PostgreSQL
DATABASE_URL=postgres://user:password@localhost:5432/mydb
# MySQL
DATABASE_URL=user:password@tcp(localhost:3306)/mydb
# SQLite
DATABASE_URL=sqlite://./data.db4. Define Your Schema
Edit db/schema/schema.sql:
sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
title VARCHAR(255) NOT NULL,
content TEXT,
published BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);5. Create and Apply Migration
bash
flash migrate "initial schema"
flash apply6. Generate Go Code
bash
flash genConfiguration
flash.config.json
json
{
"version": "2",
"schema_dir": "db/schema",
"queries": "db/queries",
"migrations_path": "db/migrations",
"export_path": "db/export",
"database": {
"provider": "postgresql",
"url_env": "DATABASE_URL"
},
"gen": {
"go": {
"enabled": true
}
}
}Environment Variables
env
DATABASE_URL=postgres://user:password@localhost:5432/mydbSchema Definition
Basic Schema
sql
-- db/schema/users.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- db/schema/posts.sql
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
content TEXT,
published BOOLEAN DEFAULT FALSE,
tags TEXT[],
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);Advanced Schema Features
sql
-- Enums
CREATE TYPE user_role AS ENUM ('admin', 'user', 'moderator');
-- Tables with enums
CREATE TABLE user_profiles (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
role user_role DEFAULT 'user',
bio TEXT,
avatar_url VARCHAR(500)
);
-- Indexes
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE UNIQUE INDEX idx_user_profiles_user_id ON user_profiles(user_id);
-- Constraints
ALTER TABLE users ADD CONSTRAINT chk_name_length CHECK (length(name) >= 2);
ALTER TABLE posts ADD CONSTRAINT chk_title_length CHECK (length(title) >= 1);Migrations
Creating Migrations
bash
# Interactive migration creation
flash migrate
# Named migration
flash migrate "add user profiles table"
# Auto-generate migration from schema changes
flash migrate "update schema" --autoMigration Files
Migrations are stored in db/migrations/ with timestamp prefixes:
db/migrations/
├── 20240101120000_initial_schema.up.sql
├── 20240101120000_initial_schema.down.sql
├── 20240102130000_add_user_profiles.up.sql
└── 20240102130000_add_user_profiles.down.sqlApplying Migrations
bash
# Apply all pending migrations
flash apply
# Apply specific number of migrations
flash apply --count 1
# Dry run (show what would be executed)
flash apply --dry-runMigration Status
bash
# Check migration status
flash status
# Output:
# Migration Status:
# ✅ 20240101120000 - initial schema
# ✅ 20240102130000 - add user profiles
# ⏳ 20240103140000 - add comments table (pending)Code Generation
Generated Files
After running flash gen, you'll get:
flash_gen/
├── db.go # Database connection and query interface
├── models.go # Generated models and types
├── users.go # User-related queries
├── posts.go # Post-related queries
└── ...Generated Code Structure
go
// flash_gen/db.go
package flash_gen
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}
type Queries struct {
db DBTX
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}Working with Generated Code
Database Connection
go
// db/database.go
package db
import (
"database/sql"
"log"
"os"
"yourproject/flash_gen"
_ "github.com/lib/pq" // PostgreSQL driver
)
var DB *sql.DB
var Queries *flash_gen.Queries
func ConnectDatabase() {
dbURL := os.Getenv("DATABASE_URL")
if dbURL == "" {
log.Fatal("DATABASE_URL environment variable is not set")
}
var err error
DB, err = sql.Open("postgres", dbURL)
if err != nil {
log.Fatal("Failed to open database:", err)
}
if err = DB.Ping(); err != nil {
log.Fatal("Failed to ping database:", err)
}
Queries = flash_gen.New(DB)
log.Println("Database connected successfully")
}
func CloseDatabase() {
if DB != nil {
DB.Close()
log.Println("Database connection closed")
}
}Using Generated Queries
go
// main.go
package main
import (
"context"
"log"
"time"
"yourproject/db"
)
func main() {
db.ConnectDatabase()
defer db.CloseDatabase()
ctx := context.Background()
// Create a user
userID, err := db.Queries.CreateUser(ctx, flash_gen.CreateUserParams{
Name: "John Doe",
Email: "john@example.com",
})
if err != nil {
log.Fatal("Failed to create user:", err)
}
log.Printf("Created user with ID: %d", userID)
// Get user by ID
user, err := db.Queries.GetUserByID(ctx, userID)
if err != nil {
log.Fatal("Failed to get user:", err)
}
log.Printf("User: %+v", user)
// Create a post
postID, err := db.Queries.CreatePost(ctx, flash_gen.CreatePostParams{
UserID: userID,
Title: "My First Post",
Content: "This is the content of my first post.",
})
if err != nil {
log.Fatal("Failed to create post:", err)
}
// Get posts by user
posts, err := db.Queries.GetPostsByUserID(ctx, userID)
if err != nil {
log.Fatal("Failed to get posts:", err)
}
for _, post := range posts {
log.Printf("Post: %s - %s", post.Title, post.Content)
}
}Query Files
Create query files in db/queries/:
sql
-- db/queries/users.sql
-- name: GetUserByID :one
SELECT id, name, email, is_active, created_at, updated_at
FROM users WHERE id = $1;
-- name: GetUserByEmail :one
SELECT id, name, email, is_active, created_at, updated_at
FROM users WHERE email = $1;
-- name: CreateUser :one
INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id;
-- name: UpdateUser :exec
UPDATE users SET name = $2, email = $3, updated_at = NOW()
WHERE id = $1;
-- name: DeleteUser :exec
DELETE FROM users WHERE id = $1;
-- name: ListUsers :many
SELECT id, name, email, is_active, created_at, updated_at
FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2;sql
-- db/queries/posts.sql
-- name: CreatePost :one
INSERT INTO posts (user_id, title, content) VALUES ($1, $2, $3) RETURNING id;
-- name: GetPostByID :one
SELECT p.id, p.user_id, p.title, p.content, p.published, p.created_at,
u.name as author_name, u.email as author_email
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.id = $1;
-- name: GetPostsByUserID :many
SELECT id, user_id, title, content, published, created_at
FROM posts WHERE user_id = $1 ORDER BY created_at DESC;
-- name: UpdatePost :exec
UPDATE posts SET title = $2, content = $3, published = $4
WHERE id = $1;
-- name: DeletePost :exec
DELETE FROM posts WHERE id = $1;Advanced Usage
Transactions
go
func createUserWithProfile(ctx context.Context, name, email, bio string) error {
tx, err := db.DB.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
queries := db.Queries.WithTx(tx)
// Create user
userID, err := queries.CreateUser(ctx, flash_gen.CreateUserParams{
Name: name,
Email: email,
})
if err != nil {
return err
}
// Create profile
_, err = queries.CreateUserProfile(ctx, flash_gen.CreateUserProfileParams{
UserID: userID,
Bio: bio,
})
if err != nil {
return err
}
return tx.Commit()
}Prepared Statements
Flash ORM automatically caches prepared statements for performance:
go
// The generated code includes prepared statement caching
type Queries struct {
db DBTX
stmts map[string]*sql.Stmt // Statement cache
}Context Support
All generated methods accept context for cancellation and timeouts:
go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
user, err := db.Queries.GetUserByID(ctx, userID)Best Practices
Project Structure
myproject/
├── db/
│ ├── database.go # Database connection
│ ├── schema/ # Schema files
│ │ ├── users.sql
│ │ └── posts.sql
│ ├── queries/ # Query files
│ │ ├── users.sql
│ │ └── posts.sql
│ └── migrations/ # Migration files
├── flash_gen/ # Generated code (don't edit)
├── handlers/ # HTTP handlers
├── models/ # Business logic models
├── main.go
├── go.mod
└── flash.config.jsonError Handling
go
func getUser(ctx context.Context, userID int64) (*flash_gen.User, error) {
user, err := db.Queries.GetUserByID(ctx, userID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("user not found: %w", err)
}
return nil, fmt.Errorf("failed to get user: %w", err)
}
return &user, nil
}Connection Pooling
go
// db/database.go
func initDB() *sql.DB {
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatal(err)
}
// Configure connection pool
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
return db
}Migration Strategy
- Keep migrations small and focused
- Test migrations on staging before production
- Use descriptive migration names
- Never modify existing migrations
- Create new migrations for schema changes
Performance Tips
- Use prepared statements (automatic in generated code)
- Implement proper indexing
- Use connection pooling
- Batch operations when possible
- Use context for timeouts
Troubleshooting
Common Issues
Migration fails with "table already exists"
- Check if migration was already applied:
flash status - Reset if needed:
flash reset
Generated code has compilation errors
- Ensure schema files are valid SQL
- Check query syntax in
.sqlfiles - Regenerate code:
flash gen
Database connection fails
- Verify
DATABASE_URLenvironment variable - Check database server is running
- Ensure user has proper permissions