NetLife Guru

Open source Go packages for fast, maintainable web systems. Built with a documentation-first approach.

Product
OverviewGolang packagesNews
Documentation
DocumentationGo LoggerGo RouterGo DB Form
Company
OverviewContactNewsGitHub
Community / Support
Supportinfo@netlife.guru
© 2026 NetLife Guru. All rights reserved.
GitHubinfo@netlife.guru
NetLife GuruNetLife GuruNetLife Guru
NetLife GuruNetLife GuruNetLife Guru
OverviewDocumentationNewsSupportContact

Golang packages

About
ScanStructRowsScanStructSliceScanStructOneScanMapRowsFillFromMap
MappingDynamic RowsEdge Cases
MapperReal Usage

ScanStructOne

Use ScanStructOne when a query is expected to return exactly one database row.

Use ScanStructOne when a query is expected to return exactly one row.

This is useful for lookups where the result should be unique.

Typical use cases include:

  • loading a record by primary key
  • loading a user by email
  • checking a unique token
  • reading one configuration row
  • fetching a single aggregate result
  • validating that a query returns only one result

Basic Idea

ScanStructOne scans one database row into *T.

user, err := mapper.ScanStructOne[User](rows)
if err != nil {
	return err
}

The result is a pointer to the scanned struct.

fmt.Println(user.Name)

If no row is returned, mapper returns mapper.ErrNoRows.

If more than one row is returned, mapper returns mapper.ErrTooManyRows.

Define a Struct

Create a struct that represents the expected result row.

type User struct {
	ID        int64     `db:"id"`
	Name      string    `db:"name"`
	Email     string    `db:"email"`
	Active    bool      `db:"active"`
	CreatedAt time.Time `db:"created_at"`
}

Query One Row

Query rows using your database driver.

Even when expecting one row, use a rows-based query because mapper works with mapper.Rows.

rows, err := db.Query(`
	SELECT *
	FROM users
	WHERE id = ?
	LIMIT 1
`, id)

if err != nil {
	return nil, err
}

defer rows.Close()

return mapper.ScanStructOne[User](rows)

Complete Query Example

This example defines the model and a small repository-style function.

package main

import (
	"database/sql"
	"time"

	"github.com/netlifeguru/mapper"
)

type User struct {
	ID        int64     `db:"id"`
	Name      string    `db:"name"`
	Email     string    `db:"email"`
	Active    bool      `db:"active"`
	CreatedAt time.Time `db:"created_at"`
}

func getUserByID(db *sql.DB, id int) (*User, error) {
	rows, err := db.Query(`
		SELECT *
		FROM users
		WHERE id = ?
		LIMIT 1
	`, id)

	if err != nil {
		return nil, err
	}

	defer rows.Close()

	return mapper.ScanStructOne[User](rows)
}

Complete Usage Example

This example loads one user and handles the mapper.ErrNoRows case separately.

package main

import (
	"errors"
	"fmt"
	"log"

	"github.com/joho/godotenv"
	_ "github.com/go-sql-driver/mysql"
	"github.com/netlifeguru/mapper"
)

func main() {
	err := godotenv.Load()
	if err != nil {
		log.Println(".env file not found, I'm using system env variables")
	}

	db, err := connectDB()
	if err != nil {
		log.Fatal(err)
	}

	defer db.Close()

	user, err := getUserByID(db, 1)
	if err != nil {
		if errors.Is(err, mapper.ErrNoRows) {
			log.Println("user not found")
			return
		}

		log.Fatal(err)
	}

	fmt.Printf(
		"ID: %d | Name: %s | Email: %s | Active: %t | Created: %s\n",
		user.ID,
		user.Name,
		user.Email,
		user.Active,
		user.CreatedAt.Format("2006-01-02 15:04:05"),
	)
}

Handling No Rows

Use errors.Is to check for mapper.ErrNoRows.

user, err := mapper.ScanStructOne[User](rows)
if errors.Is(err, mapper.ErrNoRows) {
	return nil
}

if err != nil {
	return err
}

fmt.Println(user.Name)

This is useful when “not found” is not a system error.

For example, an application can log the missing row and return normally.

if errors.Is(err, mapper.ErrNoRows) {
	log.Println("user not found")
	return
}

Handling Too Many Rows

Use errors.Is to check for mapper.ErrTooManyRows.

user, err := mapper.ScanStructOne[User](rows)
if errors.Is(err, mapper.ErrTooManyRows) {
	return errors.New("expected one user, got multiple")
}

if err != nil {
	return err
}

This usually means the query is not unique enough or the database contains unexpected duplicate data.

For unique lookups, prefer a unique column or primary key.

SELECT id, name, email
FROM users
WHERE email = ?

If the query should only return one row, adding LIMIT 1 can protect the query shape, but it also hides duplicate data.

Use LIMIT 1 when you only need the first match.

Avoid LIMIT 1 when duplicates should be detected.

Required Lookup

Sometimes a missing row should be treated as an error.

func requireUserByID(db *sql.DB, id int) (*User, error) {
	user, err := getUserByID(db, id)
	if errors.Is(err, mapper.ErrNoRows) {
		return nil, errors.New("user not found")
	}

	if err != nil {
		return nil, err
	}

	return user, nil
}

Optional Lookup

For optional lookups, return nil when the row is not found.

func optionalUserByID(db *sql.DB, id int) (*User, error) {
	user, err := getUserByID(db, id)
	if errors.Is(err, mapper.ErrNoRows) {
		return nil, nil
	}

	if err != nil {
		return nil, err
	}

	return user, nil
}

When to Use ScanStructOne

Use ScanStructOne when:

  • the query should return exactly one row
  • zero rows should be handled explicitly
  • multiple rows should be treated as an error
  • the caller expects *T
  • you are loading by a unique identifier

Good examples:

SELECT id, name, email FROM users WHERE id = ?
SELECT id, name, email FROM users WHERE email = ?
SELECT COUNT(*) AS total FROM users

When Not to Use It

Do not use ScanStructOne for normal list queries.

Use ScanStructSlice instead.

users, err := mapper.ScanStructSlice[User](rows)

Do not use ScanStructOne if you intentionally want only the first row and do not care about duplicates.

In that case, make the query explicit with LIMIT 1, or handle the behavior at the SQL level.

Related Example

A standalone example is available in the examples repository:

ScanStructOne example

ScanStructSlice

Use ScanStructSlice to load database rows into a typed Go slice.

ScanMapRows

Use ScanMapRows to process database rows as dynamic map values.

On this page

Basic IdeaDefine a StructQuery One RowComplete Query ExampleComplete Usage ExampleHandling No RowsHandling Too Many RowsRequired LookupOptional LookupWhen to Use ScanStructOneWhen Not to Use ItRelated Example