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

AboutMulti-DriverSQL Files
ConfigPools
DBConnections

Pools

Understand connection pools, identifiers, shared pools, Fork, and Close behavior across DB drivers.

Each NetLifeGuru database driver creates and manages its own connection pool.

The shared db package defines the common connection interface, while the concrete driver package creates the real pool for MySQL, PostgreSQL, or Scylla.

A typical flow is:

  1. create a driver connection
  2. call CreatePool
  3. return a forked db.Conn
  4. use the shared db helpers in application code
  5. close the original driver connection when the application shuts down

Basic Flow

conn := mysql.New()

err := conn.CreatePool(db.Config{
	Identifier: "default",
	Host:       "127.0.0.1",
	Port:       3306,
	Database:   "app",
	Username:   "root",
	Password:   "secret",
})

if err != nil {
	return nil, err
}

return conn.Fork(), nil

The returned value implements db.Conn.

func connectDB() (db.Conn, error) {
	// create pool
	// return conn.Fork()
}

CreatePool

CreatePool creates or reuses a connection pool for the selected driver.

if err := conn.CreatePool(cfg); err != nil {
	return nil, err
}

The config contains connection settings such as:

  • identifier
  • host
  • port
  • database or keyspace
  • username
  • password
  • pool limits
  • timeouts
  • driver-specific options

Each driver validates and normalizes the config before opening the connection.

Identifier

Every pool has an identifier.

Identifier: "default"

The identifier is a logical name for the pool inside the driver.

Common examples:

Identifier: "default"
Identifier: "analytics"
Identifier: "tenant-a"
Identifier: "tenant-b"

Use identifiers when your application needs multiple pools.

Identifier Conflicts

The same identifier cannot be reused with different connection settings.

For example, this is valid:

cfg := db.Config{
	Identifier: "default",
	Host:       "127.0.0.1",
	Database:   "app",
}

Calling CreatePool again with the same identifier and the same effective configuration reuses the pool.

But this is a conflict:

cfg := db.Config{
	Identifier: "default",
	Host:       "127.0.0.1",
	Database:   "analytics",
}

If the identifier already exists with different configuration, the driver returns an error.

This protects applications from accidentally reusing the same logical pool name for different databases.

Shared Pool Reuse

Drivers can reuse an existing physical pool when the effective connection configuration is the same.

For example, two connection objects with the same host, port, credentials, database, and relevant driver settings can share the same underlying pool.

This is useful when different parts of an application create equivalent connections.

The driver tracks references internally and only closes the underlying pool when the last reference is closed.

Fork

Fork returns a connection handle that implements db.Conn.

appConn := conn.Fork()

Use the forked connection in application code:

users, err := db.List[User](ctx, appConn, `
	SELECT * FROM users
`)

A forked connection is useful because application code only needs the shared db.Conn interface.

It does not need to know whether the connection came from MySQL, PostgreSQL, or Scylla.

Why Return Fork

Connection setup usually happens in infrastructure code.

func connectDB() (db.Conn, error) {
	conn := mysql.New()

	if err := conn.CreatePool(cfg); err != nil {
		return nil, err
	}

	return conn.Fork(), nil
}

Application and repository code can then depend only on db.Conn.

func ListUsers(ctx context.Context, conn db.Conn) ([]User, error) {
	return db.List[User](ctx, conn, `
		SELECT * FROM users
	`)
}

This keeps query code independent from the selected driver.

Close

Close the connection when the application shuts down.

conn.Close()

For MySQL and PostgreSQL, Close closes the underlying SQL or pool connection when the last reference is released.

For Scylla, Close closes the underlying Scylla session when the last reference is released.

A common application pattern is:

conn := mysql.New()

if err := conn.CreatePool(cfg); err != nil {
	return err
}

defer conn.Close()

If your setup function returns only conn.Fork(), keep a reference to the original driver connection when you also need to close it explicitly at shutdown.

Multiple Pools

You can create multiple pools by using different identifiers.

primary := mysql.New()

err := primary.CreatePool(db.Config{
	Identifier: "primary",
	Host:       "127.0.0.1",
	Database:   "app",
})
if err != nil {
	return err
}

analytics := mysql.New()

err = analytics.CreatePool(db.Config{
	Identifier: "analytics",
	Host:       "127.0.0.1",
	Database:   "analytics",
})
if err != nil {
	return err
}

Each pool can then be used through Fork.

primaryConn := primary.Fork()
analyticsConn := analytics.Fork()

Multi-Tenant Pools

Identifiers are useful for tenant-based applications.

func connectTenant(identifier string, database string) (db.Conn, error) {
	conn := mysql.New()

	err := conn.CreatePool(db.Config{
		Identifier: identifier,
		Host:       "127.0.0.1",
		Database:   database,
		Username:   "app",
		Password:   "secret",
	})

	if err != nil {
		return nil, err
	}

	return conn.Fork(), nil
}

Pool Identifier

Every pool has an identifier.

The identifier is not a database name and it is not necessarily the database host. It is an application-level name used to register, reuse, and protect connection pools.

Identifier: "default"

Use different identifiers when your application has multiple logical connections:

Identifier: "primary"
Identifier: "analytics"
Identifier: "tenant-a"

For multi-tenant applications, the identifier can be based on your tenant routing model.

Examples:

Identifier: "example.com"
Identifier: "customer-a"
Identifier: "tenant-42"

A domain name can be a good identifier when each tenant is selected by request host.

A tenant slug or customer ID is usually better when tenants are selected from application data, authentication claims, or internal routing.

Avoid using only the database host as the identifier unless the host is truly the unique logical connection name in your application.

The same identifier can be reused only with the same effective connection configuration.

If the same identifier is used with different host, database, credentials, or driver settings, the driver returns a pool identifier conflict error.

Example identifiers:

tenant-a
tenant-b
tenant-c

Each tenant can point to a separate database, keyspace, or cluster depending on the driver and architecture.

Same API Across Drivers

Pool creation is driver-specific, but the returned application connection uses the shared API.

conn, err := connectDB()
if err != nil {
	return err
}

users, err := db.List[User](ctx, conn, `
	SELECT * FROM users
`)

The query helpers do not need to know which driver created the pool.

Driver Differences

The pool implementation depends on the selected driver.

DriverPool implementation
MySQLdatabase/sql pool
Postgrespgxpool
Scyllagocql.Session

The shared db.Conn interface hides these details from application code.

However, connection behavior, transaction support, insert behavior, and SQL/CQL syntax still depend on the driver.

Recommended Pattern

Use a small connection function per driver.

func connectDB() (db.Conn, func() error, error) {
	conn := mysql.New()

	if err := conn.CreatePool(cfg); err != nil {
		return nil, nil, err
	}

	cleanup := func() error {
		conn.Close()
		return nil
	}

	return conn.Fork(), cleanup, nil
}

Then use it from main.

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

defer cleanup()

This keeps both values available:

  • db.Conn for application code
  • cleanup function for shutdown

Notes

Use CreatePool once during application startup when possible.

Use Fork to pass a shared db.Conn into repositories, services, handlers, jobs, or other application layers.

Use clear identifiers when managing more than one connection pool.

Avoid reusing the same identifier for different databases or tenants.

Close the driver connection during application shutdown.

Config

Configure database connections using the shared db.Config structure.

Select Overview

Learn the high-level select helpers for reading lists, single rows, scalar values, and map results.

On this page

Basic FlowCreatePoolIdentifierIdentifier ConflictsShared Pool ReuseForkWhy Return ForkCloseMultiple PoolsMulti-Tenant PoolsPool IdentifierSame API Across DriversDriver DifferencesRecommended PatternNotes