Go is built for simplicity, performance, and concurrency. Your database should be too. Too often, Go developers are forced to choose between the complexity of a heavyweight Object Relational Mapper (ORM), the tedious boilerplate of manual SQL mapping, or the awkward, untyped nature of raw NoSQL drivers. This adds friction to development and creates a drag on your team's velocity.
StatelyDB offers a new approach that feels native to the Go ecosystem. By combining a schema-first workflow with powerful code generation, we eliminate the boilerplate and give you back what you value most: the ability to work with clean, type-safe Go structs. Stop fighting your database and start building.
If you are a Go developer, this probably sounds familiar. You want to store a simple struct in a database. You end up:
You pull in a large, complex ORM library with its own domain specific language, performance overhead, and limitations, adding a heavy layer of abstraction between your code and your data.
You spend hours writing repetitive sql. Scan code or meticulously managing db struct tags for every field, a process that is both tedious and highly error-prone.
You work with NoSQL databases and are forced to handle map[string]interface{}, losing all the benefits of Go's strong type system and opening the door to runtime errors from unexpected data shapes.
You need to change a data model, and now you face the operational risk of writing and executing a complex schema migration, hoping you do not break production.
These challenges create a tax on development. They slow you down, introduce bugs, and make your application more complex than it needs to be.
StatelyDB was designed to solve these problems by putting type safety and developer productivity first. Our workflow is simple, powerful, and removes the friction between your application and your data.
Your data model is the single source of truth. You define it once using our simple, TypeScript-based Elastic Schema™. This schema is not just for validation; it is the blueprint for your entire data layer.
// schema/schema.ts
import { itemType, string, uuid } from "@stately-cloud/schema";
/** A user of our new application. */
itemType("User", {
keyPath: "/user-:id",
fields: {
id: { type: uuid, initialValue: "uuid" },
name: { type: string },
email: { type: string },
},
});
Run a single command to generate a Go package with native structs for every item in your schema. There is no ORM, no magic, just clean, idiomatic Go code.
stately schema generate \
--language go \
--schema-id <your-schema-id> \
./pkg/schema
This command generates a schema package you can import directly into your application.
// pkg/schema/schema.go (Generated)
package schema
import (
"github.com/google/uuid"
)
type User struct {
Id uuid.UUID
Name string
Email string
}
// ... and other generated helpersWith the generated package, interacting with StatelyDB is as simple as working with any other Go struct. Our Go SDK provides a clean, minimal API that feels natural to use.
Compare the simplicity of StatelyDB to a typical DynamoDB interaction.
StatelyDB: Simple, Typed, and Clean
import "your-repo/pkg/schema"
func CreateUser(ctx context.Context, client stately.Client) (*schema.User, error) {
// Just create and put a native Go struct.
user, err := client.Put(ctx, &schema.User{
Name: "Alex Doe",
Email: "alex@example.com",
})
if err != nil {
return nil, err
}
return user.(*schema.User), nil
}Typical DynamoDB SDK: Verbose and Untyped
import "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
import "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
func CreateUser(ctx context.Context, client *dynamodb.Client) (*User, error) {
user := &User{
ID: uuid.New(),
Name: "Alex Doe",
Email: "alex@example.com",
}
// Manually marshal the struct into a map of attribute values.
av, err := attributevalue.MarshalMap(user)
if err != nil {
return nil, err
}
// Add partition and sort keys manually.
av["PK"] = &types.AttributeValueMemberS{Value: fmt.Sprintf("USER#%s", user.ID.String())}
av["SK"] = &types.AttributeValueMemberS{Value: "METADATA"}
// Construct and send the verbose request.
_, err = client.PutItem(ctx, &dynamodb.PutItemInput{
TableName: aws.String("my-table"),
Item: av,
})
// ... error handling ...
return user, err
}With StatelyDB, you write less code, eliminate entire classes of errors, and can focus on your application's logic, not on the plumbing of data access.
The best part? When your application's needs change, you can evolve your data model without fear. Simply update your schema.ts file, add a migration command, and regenerate your Go types. StatelyDB's Elastic Schema ensures that your changes are always backwards and forwards compatible, so you can deploy your new code without risky database migrations or downtime.
StatelyDB was designed to solve these problems by putting type safety and developer productivity first. Our workflow is simple, powerful, and removes the friction between your application and your data.
Your data model is the single source of truth. You define it once using our simple, TypeScript-based Elastic Schema™. This schema is not just for validation; it is the blueprint for your entire data layer.
// schema/schema.ts
import { itemType, string, uuid } from "@stately-cloud/schema";
/** A user of our new application. */
itemType("User", {
keyPath: "/user-:id",
fields: {
id: { type: uuid, initialValue: "uuid" },
name: { type: string },
email: { type: string },
},
});
Run a single command to generate a Go package with native structs for every item in your schema. There is no ORM, no magic, just clean, idiomatic Go code.
stately schema generate \
--language go \
--schema-id <your-schema-id> \
./pkg/schema
This command generates a schema package you can import directly into your application.
// pkg/schema/schema.go (Generated)
package schema
import (
"github.com/google/uuid"
)
type User struct {
Id uuid.UUID
Name string
Email string
}
// ... and other generated helpersWith the generated package, interacting with StatelyDB is as simple as working with any other Go struct. Our Go SDK provides a clean, minimal API that feels natural to use.
Compare the simplicity of StatelyDB to a typical DynamoDB interaction.
StatelyDB: Simple, Typed, and Clean
import "your-repo/pkg/schema"
func CreateUser(ctx context.Context, client stately.Client) (*schema.User, error) {
// Just create and put a native Go struct.
user, err := client.Put(ctx, &schema.User{
Name: "Alex Doe",
Email: "alex@example.com",
})
if err != nil {
return nil, err
}
return user.(*schema.User), nil
}Typical DynamoDB SDK: Verbose and Untyped
import "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
import "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
func CreateUser(ctx context.Context, client *dynamodb.Client) (*User, error) {
user := &User{
ID: uuid.New(),
Name: "Alex Doe",
Email: "alex@example.com",
}
// Manually marshal the struct into a map of attribute values.
av, err := attributevalue.MarshalMap(user)
if err != nil {
return nil, err
}
// Add partition and sort keys manually.
av["PK"] = &types.AttributeValueMemberS{Value: fmt.Sprintf("USER#%s", user.ID.String())}
av["SK"] = &types.AttributeValueMemberS{Value: "METADATA"}
// Construct and send the verbose request.
_, err = client.PutItem(ctx, &dynamodb.PutItemInput{
TableName: aws.String("my-table"),
Item: av,
})
// ... error handling ...
return user, err
}With StatelyDB, you write less code, eliminate entire classes of errors, and can focus on your application's logic, not on the plumbing of data access.
The best part? When your application's needs change, you can evolve your data model without fear. Simply update your schema.ts file, add a migration command, and regenerate your Go types. StatelyDB's Elastic Schema ensures that your changes are always backwards and forwards compatible, so you can deploy your new code without risky database migrations or downtime.
Ready to try a database that feels like it was designed for Go?
Follow our Getting Started Guide to create your account, store, and schema.
Install the Go SDK: go get github.com/StatelyCloud/go-sdk
Generate your Go types as shown above.
Initialize the client and start building.
It is that simple