Bolero

Bolero is a domain-specific language for building CRUD applications. It is a superset of Go and remains fully compatible with existing Go codebases.

Bolero is in early alpha. The features below outline where the language is headed, along with the current state of each.

Features

Enums

Bolero has first-class enum types with exhaustive switch statements checked at compile time.

A value of an enum type is always one of its declared variants — illegal states are unrepresentable, and the compiler, not a code review, guarantees that every case is handled.

Status: implemented
type Color enum {
    Red
    Green
    Blue
}

func Rose() Color {
    return Color::Red
}
// Like structs, enums can define methods.
func (c Color) Hex() string {
    switch c {
        Red:
            return "F00"
        Green:
            return "0F0"
        Blue:
            return "00F"
    }
}
// Enum values support comparasion operators
func Max(c, d Color) Color {
    if c >= d {
        return c
    }
    return d
}
// Enum values can be ranged over.
func PrintPalette() {
    for c := range Color {
        fmt.Println(c.Hex())
    }
}

Static properties

Every struct field can carry constant values attached to the type itself, shared across all instances.

Static properties subsume field tags — typed, checked by the compiler, and accessible from ordinary code.

Packages can declare their own property groups, so third-party properties live in a namespace of their own and never collide.

Status: in progress
type Person struct {
    Name string {
        MaxLength: 50
    }
    Age uint
}
// A package can declare its own group of
// properties — no collisions between packages.
type Person struct {
    Name string {
        MaxLength: 50,
        json: {
            Name: "name",
            OmitEmpty: true
        }
    }
    Age uint {
        json: {
            Name: "age"
        }
    }
}
// :: reads type-level data: p::Name is the
// field descriptor, p.Name is the value.
func validate(p Person) error {
    if len(p.Name) > p::Name.MaxLength {
        return errors.New("too long")
    }
}

// Grouped properties are reached the same way.
func jsonKey() string {
    return Person::Name.json.Name
}

Components

Components are a general notation for declarative, tree-shaped structures — not just HTML. The same syntax describes PDF layouts, web services, even parsers.

Status: planned, syntax not fixed
comp VendorPage(vendor Vendor){
    Heading(){
        vendor.Name
    },
    FieldBox(){
        Field(Editable = false){
            vendor.Address
        },
        Field(){
            vendor.City
        },
        Field(MaxLength = 10){
            vendor.Vat
        }
    }
}
// The same notation drives tables,
// sorting and pagination.
comp VendorList(vendors []Vendor){
    Heading(){
        "Vendors"
    },
    Table(
        Paginate = true,
        Data = vendors
    ){
        Column(){
            Vendor::Name
        },
        Column(Sortable = true){
            Vendor::City
        }
    }
}
comp VendorRows(vendors []Vendor){
    for _, v := range vendors {
        Row(){
            v.Name,
            v.City
        }
    }
}
comp Decimal(){
    Sequence(){
        Optional(){
            Choice(){
                Literal("+"),
                Literal("-")
            }
        },
        Digits(),
        Optional(){
            Sequence(){
                Literal("."),
                Digits()
            }
        }
    }
}

Data layer

Models describe persistent data directly in the language: references, computed aggregates, and constraints live next to the fields they concern.

Queries are written in Bolero itself and typechecked against the models. No SQL strings, no runtime surprises.

Transactions are a block construct: atomic commits on success and rolls back on error. First-class support for views, schemas, and indexes is also planned.

Status: planned, syntax not fixed
// A model maps to a database table.
type City model {
    Id autoincrement
    Name string {
        Length: 20,
    }
    PostCode string
}

// references declares a typed relation.
type Invoice model {
    Id       autoincrement
    Customer references Customer
    Amount   decimal {
        Precision: 12,
        Scale: 2
    }
}

// Aggregates live next to the data:
// Spending sums the customer's invoices.
type Customer model {
    Id       autoincrement
    Name     string
    Location references City.Name
    Spending sum Invoice.Amount
                where Invoice.Customer = Name
}
// Queries are typechecked against the
// models — no SQL strings.
func FindTopCustomer(db Connection) string {
    q := with db
            from Customer
            select Name
            orderBy Spending
            limit 1
    
    topCustomer, _ := q.Execute()

    return topCustomer
}
// atomic runs its body in one transaction:
// commit on success, rollback on any error.
func DeleteCustomer(db Connection, id int) error {
    return atomic with db {
        delete Invoice
            where Customer = id

        delete Customer
            where Id = id
    }
}

Full-stack framework

Bolero will ship with a full-stack framework built on the features above, aimed squarely at CRUD-heavy business applications: ERP, WMS, back offices, and the like.

The framework will cover user management and authentication, user permissions, localization, reports, mailing, background tasks, monitoring, retention policies and archiving, and many more things...

Development

Join development of Bolero on GitHub and influence the language design.

The plan is to have beta before 2027, and v1.0 release before 2028. After that, as Go, we will promise full backward compatibility.

Why?

But, why?

Somewhere in the last decade we forgot how efficient we once were at building business applications. We traded the ugly but solid interfaces of legacy enterprise software for mountains of JavaScript, and along the way abandoned basic principles of human interface design in the chase for the next shiny thing.

Tools like NAV, FoxPro, Microsoft Access, Sybase, Omnis, and countless other RAD platforms failed to evolve with their developers. But they clearly got something right: applications written in them decades ago (often by people who were not software engineers) are still running in production today.

Why not use existing low code solution?

Mostly because majority of existing tools is commercial.

Idea of Bolero is to give opportunity even to smallest organizations/teams to build custom applications without need to invest any money.

Why Go?

Go offers excellent performance, trivially simple deployment, and a mature, still-growing ecosystem. Its compatibility promise makes it a stable foundation to build a language on.

The many languages that already target Go show how it is an attractive platform for new languages.

Why no algebraic data types?

Bolero try to stays as close as possible to the database, minimizing the object–relational impedance mismatch.

Sum types widen that gap: relational databases have no natural representation for them. The exception is enums, sums of singletons, which map cleanly and are fully supported.

Why no feature X?

I have some more features in my head, but still without a solid design. For example, maybe it would be bee good to implement default values for struct fields and function parameters. Also a less explicit error handling would maybe be a good idea.

I am open to any idea that serves the goal of the language - make building CRUD applications simple.

If you have an idea, don't hesitate to open an issue on the repo.

Why use Bolero in the age of AI?

DSLs like Bolero sit between general-purpose languages and natural language, and inherit trade-offs from both sides. They are more formal than natural language and, within their domain, more expressive than a general-purpose language. In exchange, they demand somewhat more technical skill than prompting an LLM, and they are less flexible than a general-purpose language.

The first trade-off is softened by pairing Bolero with LLMs an approach whose benefits are described well in the article LLMs and DSLs.

The second is addressed by leaning on Go itself: for every feature Bolero adds, a plain Go construct remains available as a substitute. Ultimately, Bolero will let you transpile any Bolero source to pure Go.

Finally, while Bolero works well with AI, it also aims to remove some of the need for it: developers without access to LLMs should be just as productive in building CRUD apps. And I do believe, that developer with clear idea and well designed DSL can outperform LLM.