Skip to content

Part 2: Run comby

comby is a powerful framework that offers the user many options. On the one hand, the so-called defaults from comby can be used, which offer the user ready-made domains such as Tenant, Account, Groups, Admin Dashboard and so on or the user is completely free to start from scratch. In this guide, we will show you how to start comby with the defaults.

TIP

Further information on the options for configuring comby as needed can be found in the documentation. The starting point is: Documentation

Minimal Example - completely blank

go
package main

import (
	"fmt"

	"github.com/gradientzero/comby"
	"github.com/gradientzero/comby/facade"
)

func main() {
	fc := facade.NewFacade()
	fmt.Println(comby.PrinInfo(fc))
	// notice: application will exit immediately with the following message:

	//     _______  _____  _______ ______  __   __                 
	//     |       |     | |  |  | |_____]   \_/   » 1.0.0 
	//     |_____  |_____| |  |  | |_____]    |    » darwin » arm64 

	//  » Working Directory..... /path/to/my/working/directory
	//  » App Name.............. app
	//  » Event Store........... memory
	//  » Command Store......... memory
	//  » Cache Store........... memory
	//  » Data Store............ memory
	//  » Broker................ disabled
	//  » Email................. disabled

}

Even the output to the console is optional, but this just shows how to create a new facade. However, not much is happening here yet. Usually there is a listener - for example an HTTP listener - that ensures that the application can accept and process HTTP requests. The newly created facade can then be tackled.

Real World Example - with defaults

Click me to view the code
go
package main

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/danielgtaylor/huma/v2"
	"github.com/danielgtaylor/huma/v2/adapters/humachi"
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/go-chi/cors"
	"github.com/gradientzero/comby"
	combyApi "github.com/gradientzero/comby/api"
	combyApiMiddleware "github.com/gradientzero/comby/api/middleware"
	combyDomain "github.com/gradientzero/comby/domain"
	"github.com/gradientzero/comby/domain/auth"
	"github.com/gradientzero/comby/facade"
	"github.com/gradientzero/comby/store"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
	// setup stores
	eventStore := store.NewEventStoreSQLite("eventStore.db")
	commandStore := store.NewCommandStoreSQLite("commandStore.db")
	dataStore := store.NewDataStoreFileSystem("./__data__")

	// create empty facade
	fc := facade.NewFacade(
		facade.WithEventStore(eventStore),
		facade.WithCommandStore(commandStore),
		facade.WithDataStore(dataStore),
	)

	// register default domains (tenant, account, identity, etc.) to facade
	ctx := context.Background()
	if err := combyDomain.RegisterDefaults(ctx, fc); err != nil {
		panic(err)
	}

	// register my own domains
	// ...

	// create new router (chi)
	router := chi.NewRouter()
	router.Use(middleware.Timeout(30 * time.Second))
	router.Use(middleware.Logger)
	router.Use(cors.New(cors.Options{
		AllowedOrigins: []string{"*"},
		AllowedMethods: []string{
			"GET",
			"POST",
			"PATCH",
			"DELETE",
			"OPTIONS",
		},
		AllowedHeaders: []string{
			"Accept",
			"Authorization",
			"Content-Type",
			"X-CSRF-Token",
		},
		ExposedHeaders: []string{
			"Link",
		},
		AllowCredentials: true,
		MaxAge:           300,
	}).Handler)
	router.Use(combyApiMiddleware.AuthorizationToCtx(auth.ApiReadmodel))
	router.Use(combyApiMiddleware.PrometheusMiddleware)
	router.Mount("/admin", combyApi.AdminDashboardRouter())
	router.Mount("/prometheus", promhttp.Handler())

	// create new api (huma)
	humaCfg := huma.DefaultConfig("My API", "1.0.0")
	humaCfg.Components.SecuritySchemes = map[string]*huma.SecurityScheme{
		"bearer": {
			Type:         "http",
			Scheme:       "bearer",
			BearerFormat: "JWT",
		},
	}
	humaApi := humachi.New(router, humaCfg)

	// register comby api endpoints
	if err := combyApi.RegisterDefaults(fc, humaApi); err != nil {
		panic(err)
	}

	// register own api endpoints
	// ...

	// restore state
	if err := fc.RestoreState(); err != nil {
		panic(err)
	}

	// run http server
	fmt.Println(comby.PrinInfo(fc))
	http.ListenAndServe("127.0.0.1:8080", router)

	//     _______  _____  _______ ______  __   __
	//     |       |     | |  |  | |_____]   \_/   » 1.0.0
	//     |_____  |_____| |  |  | |_____]    |    » darwin » arm64

	//  » Working Directory..... /path/to/my/working/directory
	//  » App Name.............. app
	//  » Event Store........... sqlite - eventStore.db
	//  » Command Store......... sqlite - commandStore.db
	//  » Cache Store........... memory
	//  » Data Store............ filesystem - __data__
	//  » Broker................ disabled
	//  » Email................. disabled

	// -----------------------------------------------
	//  email:         admin@comby.io
	//  password:      Placeholder@1
	// -----------------------------------------------
}

There are countless variations between the minimal version and a version from a real-world application. In this example we are using SQLite storage for the Events and Commands. We also define the location of the uploaded assets (images, files, etc.).

The defaults are used here (combyDomain.RegisterDefaults and combyApi.RegisterDefaults), which means that the Tenant, Account, Groups, Admin Dashboard and many other features are available out of the box.

We then build our own chi router and connect it to the endpoints of comby as well as the admin dashboard and our own – yet to be defined – endpoints from the application itself.

comby internally uses huma, which enables the generation of OpenAPI 3.1. This is used in real examples to generate TypeScript clients.

The comby defaults initially creates a user with whom you can, for example, log in directly to the admin dashboard or log in for the bearer token.

Open the browser and go to http://localhost:8080/admin to see the admin dashboard. Login with the provided credentials.