database/sql & net Package Family Overview
Go splits networking and SQL into layered packages that share context and io patterns.
Search across all documentation pages
Go splits networking and SQL into layered packages that share context and io patterns.
database/sql manages connection pools and transactions.
The net family (net, net/http, net/url, net/netip) handles addresses, HTTP, and modern IP types.
Import a SQL driver anonymously, open a *sql.DB, and configure pool limits for your database SLA.
HTTP servers and clients built on net/http reuse the same timeout and context practices as SQL queries.
Use net/netip for typed IPs and prefixes instead of parsing strings ad hoc.
Quick-reference recipe card - copy-paste ready.
import (
"context"
"database/sql"
_ "github.com/jackc/pgx/v5/stdlib"
"net/http"
"time"
)
db, _ := sql.Open("pgx", dsn)
db.SetMaxOpenConns(25)
db.SetConnMaxLifetime(30 * time.Minute)
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
row := db.QueryRowContext(ctx, `SELECT name FROM users WHERE id=$1`, id)
})When to reach for this:
net.Conn.package main
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"net"
"net/http"
"time"
_ "modernc.org/sqlite"
)
func main() {
db, err := sql.Open("sqlite", "file:users.db?cache=shared&mode=rwc")
if err != nil {
panic(err)
}
defer db.Close()
db.SetMaxOpenConns(1) // sqlite single-writer
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)`); err != nil {
panic(err)
}
if _, err := db.Exec(`INSERT OR IGNORE INTO users (id, name) VALUES (1, 'Ada')`); err != nil {
panic(err)
}
mux := http.NewServeMux()
mux.HandleFunc("/users/", func(w http.ResponseWriter, r *http.Request) {
handleUser(db, w, r)
})
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
fmt.Println("listening", srv.Addr)
panic(srv.ListenAndServe())
}
func handleUser(db *sql.DB, w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
http.Error(w, "bad remote addr", http.StatusInternalServerError)
return
}
var name string
err = db.QueryRowContext(ctx, `SELECT name FROM users WHERE id=1`).Scan(&name)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{
"name": name,
"peer": host,
})
}What this demonstrates:
sql.Open with driver import and pool tuning.QueryRowContext honoring request deadlines.net.SplitHostPort for peer metadata.http.Server with ReadHeaderTimeout.sql.Open creates a pool handle; first real connection opens on first query.database/sql/driver interfaces; pgx, mysql, and sqlite ship as modules.net.Dialer and net.Listen manage TCP/UDP sockets; http.Server layers HTTP parsing above.context cancellation closes in-flight queries and HTTP handler work when deadlines hit.| Package | Responsibility | Typical pairing |
|---|---|---|
database/sql | Pool, Tx, Stmt | Driver module (pgx, go-sql-driver/mysql) |
net | IPs, dial, listen | Custom protocols, gRPC over TCP |
net/http | HTTP server/client | REST gateways, webhooks |
net/url | URL parse/build | Redirects, query strings |
net/netip | Typed Addr, Prefix | ACL checks, CIDR routing |
// Always ping after Open to catch bad DSN early:
if err := db.PingContext(ctx); err != nil { return err }
// Prepared statements for hot queries:
stmt, err := db.PrepareContext(ctx, `SELECT ...`)
// Prefer netip over net.ParseIP for comparisons:
addr, _ := netip.ParseAddr("203.0.113.1")sql.Open without Ping - Misconfigured DSN fails on first request. Fix: PingContext during startup health checks.SetMaxOpenConns - Exhausts database connections under spike load. Fix: set max open and idle conns from DB capacity formulas.http.DefaultClient in servers - No timeouts on outbound calls. Fix: dedicated http.Client per upstream with Transport tuning.rows.Err() after iteration - Silent partial results. Fix: check err after for rows.Next() loop.*sql.Tx on structs - Leaks transactions across requests. Fix: keep transactions scoped to handler or service method.net/netip.ParseAddr or net.SplitHostPort.| Alternative | Use When | Don't Use When |
|---|---|---|
| ORM (gorm, ent) | Complex models and migrations | Simple SQL with few tables |
pgx pool directly | PostgreSQL-only advanced features | Need database/sql portability |
gRPC (google.golang.org/grpc) | Internal RPC contracts | Browser-facing JSON APIs |
database/sql + sqlc | Type-safe query codegen | Ad-hoc dynamic SQL only |
Drivers register themselves with database/sql in init.
The blank import links the driver without referencing its package directly.
sql.DB is a connection pool shared across goroutines.
sql.Tx binds one transactional session; do not share across requests.
Pass r.Context() into QueryContext.
When the server read/write deadline fires, context cancels and the driver should stop the query.
Whenever comparing, parsing, or storing IP addresses and CIDR prefixes.
It avoids mutability and parsing bugs in net.IP slice form.
Not automatically.
Implement retry at the application layer for transient errors, with backoff and idempotency guards.
HTTP servers accept connections via net.Listener, then parse HTTP on top.
Custom protocols can use net.Dial and net.Conn without HTTP.
The example uses modernc.org/sqlite with a file: URI.
Driver-specific DSN strings differ; consult driver docs for PostgreSQL and MySQL.
Always prefer QueryContext in servers so cancellation propagates.
Bare Query uses context.Background() internally.
Inject a configured client from main into handlers or a service struct.
Tune Transport.MaxIdleConnsPerHost per upstream dependency.
Each sql.Open call uses one driver name.
Different databases mean different *sql.DB handles, usually different services.
Parse request URLs, build redirect targets, and encode query parameters without manual string concat.
Expose /health that runs db.PingContext with a short timeout.
Fail readiness when the pool cannot reach the primary database.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 16, 2026