While the standard database/sql
package is powerful, it can be verbose and tedious to use. Enter sqlx
- a library that extends database/sql
with additional convenience features while maintaining its core simplicity. In this guide, we'll explore how to use sqlx to write cleaner, more maintainable database code in Go.
Why Choose sqlx?
sqlx offers several advantages over the standard library:
- Automatic scanning into structs and slices
- Named parameter support
- Transaction management helpers
- Convenient result handling
- Compatible with existing
database/sql
code
Getting Started
First, let's set up a new project:
mkdir go-sqlx-demo
cd go-sqlx-demo
go mod init go-sqlx-demo
go get github.com/jmoiron/sqlx
go get github.com/lib/pq # for PostgreSQL
Basic Setup
Here's how to connect to a database using sqlx:
package main
import (
"log"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
type Book struct {
ID int `db:"id"`
Title string `db:"title"`
AuthorID int `db:"author_id"`
PublishedYear int `db:"published_year"`
Price float64 `db:"price"`
}
type Author struct {
ID int `db:"id"`
Name string `db:"name"`
Bio *string `db:"bio"`
CreatedAt string `db:"created_at"`
}
func main() {
// Connect to database
db, err := sqlx.Connect("postgres", "postgresql://postgres:postgres@localhost:5432/bookstore?sslmode=disable")
if err != nil {
log.Fatalln(err)
}
defer db.Close()
// Verify connection
if err := db.Ping(); err != nil {
log.Fatalln(err)
}
log.Println("Connected to database!")
}
Key Features and Usage Examples
1. Querying Single Rows
func GetBook(db *sqlx.DB, id int) (Book, error) {
var book Book
err := db.Get(&book, "SELECT * FROM books WHERE id = $1", id)
return book, err
}
func GetAuthorWithNamedParams(db *sqlx.DB, params map[string]interface{}) (Author, error) {
var author Author
query := "SELECT * FROM authors WHERE name = :name AND created_at > :date"
err := db.Get(&author, db.Rebind(query), params)
return author, err
}
2. Querying Multiple Rows
func GetAllBooks(db *sqlx.DB) ([]Book, error) {
var books []Book
err := db.Select(&books, "SELECT * FROM books ORDER BY published_year DESC")
return books, err
}
func GetBooksByAuthor(db *sqlx.DB, authorID int) ([]Book, error) {
var books []Book
err := db.Select(&books, `
SELECT b.*
FROM books b
JOIN authors a ON a.id = b.author_id
WHERE a.id = $1
ORDER BY b.published_year DESC`,
authorID)
return books, err
}
3. Using Named Parameters
func CreateAuthor(db *sqlx.DB, author Author) (Author, error) {
query := `
INSERT INTO authors (name, bio)
VALUES (:name, :bio)
RETURNING *`
rows, err := db.NamedQuery(query, author)
if err != nil {
return Author{}, err
}
defer rows.Close()
var newAuthor Author
if rows.Next() {
err = rows.StructScan(&newAuthor)
}
return newAuthor, err
}
4. Working with Transactions
func TransferBooks(db *sqlx.DB, fromAuthorID, toAuthorID int) error {
tx, err := db.Beginx()
if err != nil {
return err
}
defer tx.Rollback()
// Update all books from one author to another
_, err = tx.Exec(`
UPDATE books
SET author_id = $1
WHERE author_id = $2`,
toAuthorID, fromAuthorID)
if err != nil {
return err
}
// Update author statistics
_, err = tx.Exec(`
UPDATE author_stats
SET book_count = (
SELECT COUNT(*)
FROM books
WHERE author_id = $1
)
WHERE author_id IN ($1, $2)`,
toAuthorID, fromAuthorID)
if err != nil {
return err
}
return tx.Commit()
}
5. Using Prepared Statements
func BulkInsertBooks(db *sqlx.DB, books []Book) error {
stmt, err := db.Preparex(`
INSERT INTO books (title, author_id, published_year, price)
VALUES ($1, $2, $3, $4)`)
if err != nil {
return err
}
defer stmt.Close()
for _, book := range books {
_, err = stmt.Exec(
book.Title,
book.AuthorID,
book.PublishedYear,
book.Price,
)
if err != nil {
return err
}
}
return nil
}
6. Advanced Queries with Joins
type BookWithAuthor struct {
BookID int `db:"book_id"`
Title string `db:"title"`
AuthorID int `db:"author_id"`
AuthorName string `db:"author_name"`
PublishedYear int `db:"published_year"`
Price float64 `db:"price"`
}
func GetBooksWithAuthors(db *sqlx.DB) ([]BookWithAuthor, error) {
var booksWithAuthors []BookWithAuthor
err := db.Select(&booksWithAuthors, `
SELECT
b.id as book_id,
b.title,
b.author_id,
a.name as author_name,
b.published_year,
b.price
FROM books b
JOIN authors a ON a.id = b.author_id
ORDER BY b.published_year DESC`)
return booksWithAuthors, err
}
Best Practices
-
Use Struct Tags: Always define
db
struct tags to map database columns to struct fields:
type User struct {
ID int `db:"id"`
Email string `db:"email"`
CreatedAt time.Time `db:"created_at"`
}
- Handle NULL Values: Use pointer types or sql.Null* types for nullable columns:
type Profile struct {
ID int `db:"id"`
Bio *string `db:"bio"`
LastLoginAt sql.NullTime `db:"last_login_at"`
Score sql.NullInt64 `db:"score"`
}
- Connection Management: Set appropriate connection pool settings:
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
- Error Handling: Always check for sql.ErrNoRows:
if err == sql.ErrNoRows {
return nil, ErrNotFound
}
Performance Tips
- Batch Operations: Use transactions for batch operations:
tx, err := db.Beginx()
if err != nil {
return err
}
defer tx.Rollback()
for _, item := range items {
_, err = tx.NamedExec(`
INSERT INTO items (name, value)
VALUES (:name, :value)`,
item)
if err != nil {
return err
}
}
return tx.Commit()
- Use Prepared Statements for repeated queries
- Select Only Needed Columns instead of using SELECT *
- Index Properly based on your query patterns
Common Gotchas and Solutions
- NULL Handling: Always use appropriate types for nullable columns
- Connection Leaks: Always close rows and statements
- Transaction Management: Use defer tx.Rollback() pattern
- Parameter Binding: Be aware of different database placeholder styles
Integration with Other Tools
sqlx works well with other database tools:
- Combine with
sqlc
for type-safe query generation - Use with
migrate
for database migrations - Integrate with
pgx
for PostgreSQL-specific features
Conclusion
sqlx provides a perfect balance between the simplicity of database/sql
and the convenience of an ORM. It's an excellent choice for projects that need:
- Clean, maintainable database code
- Good performance
- SQL flexibility
- Minimal learning curve
Resources
Remember to give sqlx a star on GitHub if you find it useful! Happy coding! 🚀
Top comments (0)