Connecting to a Database
Connecting to a database is a fundamental task in many Go applications. Go's standard library and ecosystem provide robust tools for working with databases efficiently and securely.
database/sql
The database/sql
package in Go provides a generic interface for interacting with SQL databases. Combined with database drivers (e.g., pq
for PostgreSQL, mysql
for MySQL),
it supports multiple databases through drivers, manages connections with a built-in connection pool and provides a consistent API across databases.
Installing a Database Driver
To use database/sql
, you need a database driver. Install the driver for your database using go get
.
PostgreSQL:
go get github.com/lib/pq
MySQL:
go get github.com/go-sql-driver/mysql
SQLite:
go get modernc.org/sqlite
Connecting to the Database
Use the sql.Open
function to create a database connection. This function does not establish a connection immediately but validates the driver and data source name (DSN).
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq" // PostgreSQL driver
)
func main() {
connStr := "user=postgres password=mysecretpassword dbname=mydb sslmode=disable"
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatalf("Failed to open database: %v", err)
}
defer db.Close()
// Test the connection
if err := db.Ping(); err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
fmt.Println("Connected to the database!")
}
Executing Queries
The database/sql
package supports executing SQL queries with methods like Query
, QueryRow
, and Exec
.
Running a Query
rows, err := db.Query("SELECT id, name FROM users")
if err != nil {
log.Fatalf("Query failed: %v", err)
}
defer rows.Close()
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
log.Fatalf("Row scan failed: %v", err)
}
fmt.Printf("ID: %d, Name: %s\n", id, name)
}
Executing an Update:
result, err := db.Exec("UPDATE users SET name = $1 WHERE id = $2", "John Doe", 1)
if err != nil {
log.Fatalf("Update failed: %v", err)
}
affected, _ := result.RowsAffected()
fmt.Printf("Rows affected: %d\n", affected)
Using Prepared Statements
Prepared statements improve performance and security by precompiling SQL and preventing SQL injection.
stmt, err := db.Prepare("INSERT INTO users(name, email) VALUES($1, $2)")
if err != nil {
log.Fatalf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
_, err = stmt.Exec("Alice", "alice@example.com")
if err != nil {
log.Fatalf("Insert failed: %v", err)
}
fmt.Println("User added successfully!")
Using Transactions
Transactions allow multiple queries to be executed as a single unit of work.
tx, err := db.Begin()
if err != nil {
log.Fatalf("Failed to begin transaction: %v", err)
}
_, err = tx.Exec("UPDATE accounts SET balance = balance - $1 WHERE id = $2", 100, 1)
if err != nil {
tx.Rollback()
log.Fatalf("Failed to debit account: %v", err)
}
_, err = tx.Exec("UPDATE accounts SET balance = balance + $1 WHERE id = $2", 100, 2)
if err != nil {
tx.Rollback()
log.Fatalf("Failed to credit account: %v", err)
}
if err := tx.Commit(); err != nil {
log.Fatalf("Transaction commit failed: %v", err)
}
fmt.Println("Transaction completed successfully!")
SQL Builder
goqu Go SQL builder is my favourite package to dynamically build querries.