Forge

Backends

The Store interface, PostgreSQL, MongoDB, and DynamoDB.

GitHub

Every backend implements one shared interface — dbswitch.Store — so the same CRUD code runs against PostgreSQL, MongoDB, or DynamoDB.

The Store interface

type Store interface {
	CreateTable(ctx context.Context, t Table) error
	Insert(ctx context.Context, table string, data map[string]any) error
	FindOne(ctx context.Context, table string, where map[string]any) (map[string]any, error)
	Find(ctx context.Context, table string, where map[string]any) ([]map[string]any, error)
	List(ctx context.Context, table string, opts ListOptions) ([]map[string]any, error)
	Count(ctx context.Context, table string, filter map[string]any) (int64, error)
	Update(ctx context.Context, table string, set, where map[string]any) (int64, error)
	Delete(ctx context.Context, table string, where map[string]any) (int64, error)
	Close()
}

*postgres.DB, *mongo.Store, and *dynamodb.Store all satisfy it (enforced at compile time with var _ dbswitch.Store = (*DB)(nil)), so you can write backend-agnostic code:

// Works with *postgres.DB, *mongo.Store, or *dynamodb.Store.
func seed(ctx context.Context, db dbswitch.Store) error {
	return db.Insert(ctx, "users", map[string]any{"email": "a@b.com"})
}

PostgreSQL

db, err := postgres.Open(ctx, os.Getenv("DATABASE_URL"))
defer db.Close()

The SQL backend: CreateTable runs CREATE TABLE IF NOT EXISTS, conditions compile to parameterized WHERE col = $n AND …, and driver errors map to ErrNotFound / ErrDuplicate (the constraint name comes from pgconn.PgError). See Schema and CRUD.

MongoDB

Mongo needs the database name separately from the connection URI (a collection is reached as db(name).Collection(table)):

db, err := mongo.Open(ctx, os.Getenv("MONGO_URI"), "myapp")
defer db.Close()

Mongo is schemaless, so the abstractions map like this:

dbswitch conceptMongoDB
TableCollection
CreateTableNo DDL — creates a unique index per Unique column (collections are created on first write)
PrimaryKey columnMapped to Mongo's _id (already uniquely indexed)
"id" field on insert/readMapped to/from _id automatically
Duplicate key*dbswitch.DuplicateErrorConstraint is the violated index name (e.g. "email_1")
Not founddbswitch.ErrNotFound

Column types and defaults (TypeText, DefaultCurrentTime, …) are SQL concepts — Mongo ignores them since documents are schemaless. Only Unique (→ unique index) and the primary key (→ _id) affect the Mongo backend.

DynamoDB

db, err := dynamodb.Open(ctx) // uses the default AWS config chain (env vars, IAM role, etc.)
defer db.Close()

For DynamoDB Local or any custom endpoint, pass a client option:

db, err := dynamodb.Open(ctx, func(o *dynamodbsdk.Options) {
	o.BaseEndpoint = aws.String("http://localhost:8000")
})

DynamoDB is schemaless like Mongo, but has no query planner or secondary index by default, which changes the abstractions more than Mongo does:

dbswitch conceptDynamoDB
TableTable (billed PAY_PER_REQUEST — no throughput to tune)
PrimaryKey columnMust be named "id" — becomes the table's partition key
CreateTableReal CreateTable DDL (unlike Mongo's implicit collections); idempotent
Unique on "id"Enforced via a conditional PutItem
Unique on other columnsNot supportedCreateTable returns an error instead of ignoring it
FindOne / Find / List / Count by {"id": v}Direct GetItem — cheap
Same, by any other fieldFull-table Scan + FilterExpression — correct, not free at scale
Update / Delete by {"id": v}Direct UpdateItem / DeleteItem
Same, by any other fieldScan for matches, then one call per match
ListOptions.SortBy / AfterEmulated in memory after a Scan — no sort key/GSI support yet
Duplicate key*dbswitch.DuplicateErrorConstraint is "<table>.id"
Not founddbswitch.ErrNotFound

Composite (partition + sort key) tables aren't supported yet — only a single "id" partition key. For filters or sorting on hot paths, add a Global Secondary Index and query it with the AWS SDK directly; the Scan-based fallback here is for convenience and small-to-medium tables, not a replacement for indexed access patterns.

Because errors are unified, errors.Is(err, dbswitch.ErrDuplicate) and errors.Is(err, dbswitch.ErrNotFound) work identically across all three backends — only the Constraint string differs by driver. See Errors.

On this page