PostgreSQL is one of the most powerful open-source relational databases, and it pairs exceptionally well with Go for backend development. This post covers PostgreSQL fundamentals and demonstrates how to connect and interact with Postgres from Go using the pgx driver - the most performant PostgreSQL driver for Go.
Why PostgreSQL?
PostgreSQL offers features that make it ideal for production applications:
flowchart TD
subgraph Features["PostgreSQL Strengths"]
A[ACID Compliance] --> R[Reliability]
J[JSON Support] --> F[Flexibility]
E[Extensibility] --> C[Custom Types]
P[Performance] --> S[Scalability]
end
subgraph UseCases["Use Cases"]
R --> W[Web Applications]
F --> API[APIs]
C --> D[Data Analytics]
S --> M[Microservices]
end
classDef blueClass fill:#4A90E2,stroke:#333,stroke-width:2px,color:#fff
classDef greenClass fill:#27AE60,stroke:#333,stroke-width:2px,color:#fff
class Features blueClass
class UseCases greenClass
Key advantages:
ACID compliance: Guaranteed data integrity
JSON/JSONB support: Flexible document storage
Advanced indexing: B-tree, Hash, GiST, GIN
Full-text search: Built-in text search capabilities
Extensibility: Custom functions, types, and operators
-- Not Null ALTER TABLE users ALTERCOLUMN username SETNOT NULL;
Indexes
Indexes improve query performance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
-- B-tree index (default) - good for equality and range queries CREATE INDEX idx_users_email ON users(email);
-- Composite index CREATE INDEX idx_products_category_price ON products(category_id, price);
-- Partial index - index only active users CREATE INDEX idx_active_users ON users(username) WHERE is_active =true;
-- GIN index for JSONB CREATE INDEX idx_products_metadata ON products USING GIN (metadata);
-- Index for array columns CREATE INDEX idx_users_tags ON users USING GIN (tags);
flowchart TB
subgraph With["With Index"]
direction LR
Q2[Query] --> IS[Index Lookup]
IS --> R2["O#40;log n#41; - Fast"]
end
subgraph Without["Without Index"]
direction LR
Q1[Query] --> FS[Full Table Scan]
FS --> R1["O#40;n#41; - Slow"]
end
classDef pinkClass fill:#E74C3C,stroke:#333,stroke-width:2px,color:#fff
classDef greenClass fill:#27AE60,stroke:#333,stroke-width:2px,color:#fff
class Without pinkClass
class With greenClass
Connecting Go to PostgreSQL with pgx
Why pgx?
pgx is the recommended PostgreSQL driver for Go:
Native PostgreSQL protocol implementation
Better performance than database/sql
Full PostgreSQL feature support
Connection pooling built-in
Installation
1 2
go get github.com/jackc/pgx/v5 go get github.com/jackc/pgx/v5/pgxpool
// Create connection pool pool, err := pgxpool.New(context.Background(), connStr) if err != nil { log.Fatalf("Unable to connect to database: %v\n", err) } defer pool.Close()
// Test connection err = pool.Ping(context.Background()) if err != nil { log.Fatalf("Unable to ping database: %v\n", err) }
fmt.Println("Successfully connected to PostgreSQL!") }
type User struct { ID int`json:"id"` Username string`json:"username"` Email string`json:"email"` PasswordHash string`json:"-"` Profile []byte`json:"profile"` IsActive bool`json:"is_active"` CreatedAt time.Time `json:"created_at"` }
// Get single user funcGetUserByID(ctx context.Context, pool *pgxpool.Pool, id int) (*User, error) { query := ` SELECT id, username, email, password_hash, profile, is_active, created_at FROM users WHERE id = $1 `
// Deduct from sender _, err = tx.Exec(ctx, `UPDATE accounts SET balance = balance - $1 WHERE id = $2 AND balance >= $1`, amount, fromID, ) if err != nil { return err }
// Add to receiver _, err = tx.Exec(ctx, `UPDATE accounts SET balance = balance + $1 WHERE id = $2`, amount, toID, ) if err != nil { return err }
// Commit transaction return tx.Commit(ctx) }
sequenceDiagram
participant App as Go Application
participant DB as PostgreSQL
App->>DB: BEGIN TRANSACTION
App->>DB: UPDATE accounts (deduct)
alt Success
App->>DB: UPDATE accounts (add)
alt Success
App->>DB: COMMIT
DB->>App: Success
else Failure
App->>DB: ROLLBACK
DB->>App: Rolled back
end
else Failure
App->>DB: ROLLBACK
DB->>App: Rolled back
end
funcPrepareStatements(ctx context.Context, pool *pgxpool.Pool)error { // pgx automatically prepares and caches statements // This happens transparently when using QueryRow/Query/Exec
// For manual preparation (rarely needed): conn, err := pool.Acquire(ctx) if err != nil { return err } defer conn.Release()
_, err = conn.Conn().Prepare(ctx, "get_user", "SELECT * FROM users WHERE id = $1") return err }
Best Practices
Connection Pool Sizing
flowchart LR
subgraph Pool["Connection Pool"]
C1[Conn 1]
C2[Conn 2]
C3[Conn 3]
Cn[Conn N]
end
R1[Request 1] --> Pool
R2[Request 2] --> Pool
R3[Request 3] --> Pool
Pool --> DB[(PostgreSQL)]
classDef blueClass fill:#4A90E2,stroke:#333,stroke-width:2px,color:#fff
class Pool blueClass
Comments