Skip to main content

Advanced Guide

Slim Builds

GoFr compiles several optional subsystems into every binary so they work from configuration alone — set PUBSUB_BACKEND=KAFKA and the Kafka client is there, set DB_DIALECT=postgres and the driver is registered, call app.GraphQLQuery(...) and the engine is linked. That is the behavior most services want, and it is the default.

A service that uses none of them still pays for them in binary size and, in one case, in resident memory. The gofr_no* build tags let you leave those out:

Bash
go build -tags "gofr_nopubsub gofr_nosqldrivers gofr_nographql gofr_nodgraph gofr_nootlp" ./...

Tags compose, so use as many as apply.

gofr_nogrpc is deliberately not in that line. It is the one tag that removes exported methods, so ./... will not build with it in any tree that contains a gRPC server -- including this repo, whose gRPC examples fail with *gofr.App does not implement grpc.ServiceRegistrar, as will any health_gofr.go generated by gofr-cli. Add it and scope the build to the packages that do not register a gRPC service.

The tags

TagLeaves outAffects
gofr_nopubsubthe Kafka, Google Pub/Sub and MQTT clientsPUBSUB_BACKEND=KAFKA, =GOOGLE, =MQTT
gofr_nosqldriversthe PostgreSQL and SQLite driversDB_DIALECT=postgres, =sqlite, =supabase, =cockroachdb
gofr_nographqlthe GraphQL engineapp.GraphQLQuery, app.GraphQLMutation
gofr_nogrpcthe gRPC serverapp.RegisterService, app.AddGRPC*, GRPC_PORT
gofr_nodgraphthe Dgraph migration driverany app.Migrate on a service with a Dgraph datasource -- see below
gofr_nootlpthe OTLP trace and metric exportersTRACE_EXPORTER=otlp, =jaeger, and METRICS_EXPORTER=otlp

Two things are deliberately not affected. PUBSUB_BACKEND=REDIS keeps working under gofr_nopubsub, because the Redis client is linked for caching anyway and removing it would buy nothing. DB_DIALECT=mysql keeps working under gofr_nosqldrivers, because GoFr imports the MySQL driver for its configuration types rather than only for registration, so it is linked either way.

supabase and cockroachdb are in the table because they connect through the PostgreSQL driver, so the tag that omits that driver omits them too.

gofr_nodgraph is broader than "Dgraph migrations"

migration.go chains the Dgraph migrator whenever c.DGraph is set, not only when a migration touches Dgraph. Under this tag the stub's checkAndCreateMigrationTable returns an error and migration.go calls c.Fatalf, so a service with a Dgraph datasource and only SQL migrations exits at app.Migrate.

That is deliberate -- failing loudly beats running half a migration set against a datasource whose migrator was compiled out -- but it means the tag is for services that do not configure Dgraph at all, not merely for services that do not migrate it.

Tags share dependencies, so they compose

A library goes out of the binary when its LAST importer does, not when the first tag that mentions it is set. google.golang.org/grpc is the clearest case -- measured against gofr.dev/pkg/gofr:

Tagsgoogle.golang.org/grpc packages linked
none86
gofr_nogrpc82
all four except gofr_nogrpc69
all four except gofr_nootlp66
all four except gofr_nodgraph64
all four except gofr_nopubsub81
all four: gofr_nogrpc gofr_nootlp gofr_nodgraph gofr_nopubsub0

Four things import gRPC: GoFr's own gRPC server, the OTLP exporters, the Dgraph client's protobuf package (github.com/dgraph-io/dgo/v210/protos/api), and the Google Pub/Sub client through cloud.google.com/go. Leave any one of the four tags off and between 64 and 81 packages stay linked. So setting one tag and measuring little or no change does not mean the tag did nothing -- it means something else still imports the same tree. gofr_nogrpc on its own takes 4 of the 86 packages; the other 82 leave only once the last of those importers is tagged out too. Set the tags for everything you do not use, then measure.

Nothing in your code changes

With one exception, the tags remove implementations rather than API. app.GraphQLQuery still exists and still takes a GoFr Handler under gofr_nographql; the container still has a PubSub field under gofr_nopubsub. Source that compiles without the tags compiles with them.

gofr_nogrpc is the exception, as the table above says: app.RegisterService and the AddGRPC* setters name gRPC types in their signatures, so they cannot exist without the import. Code calling them does not compile under that tag. That is the intended behavior -- a service registering a gRPC service is not one that wanted the gRPC server removed -- but it does mean this is the one tag you cannot add to an existing build without checking what it breaks.

A subsystem you asked for, but did not build in, says so

This is the point of using a tag rather than asking you to wire each subsystem up yourself. If you configure something the binary does not carry, GoFr logs an error naming the tag:

text
ERROR  PUBSUB_BACKEND=KAFKA was configured, but this binary was built with -tags gofr_nopubsub,
       which omits the Kafka client. Rebuild without the tag to use it.

The service still starts. A missing pub/sub client leaves PubSub unset, which is the same state an unconfigured service has, and GoFr already handles it — so a misbuilt deployment is visible in the logs rather than being a crash loop, and it cannot silently half-work.

gofr_nodgraph is the exception: a service with a Dgraph datasource exits at app.Migrate, as the gofr_nodgraph section above explains.

The SQL case reports through database/sql itself:

text
ERROR  could not register sql dialect 'postgres' for traces, error: sql: unknown driver "postgres"
       (forgotten import?)

If you want one of these dialects in a tagged build, import the driver in your own main package, exactly as you would when using database/sql directly:

Go
import _ "github.com/lib/pq"

When it is worth it

Only when the binary size or the memory matters to you — a container image budget, a serverless deployment, a memory-capped runtime. Apart from gofr_nodgraph on a service that configures Dgraph, the tags change nothing about how a service behaves at runtime, so there is no reason to reach for them otherwise.

gofr_nosqldrivers is the one with a memory effect rather than only a size effect: the SQLite driver pulls in modernc.org/libc, whose initialization parses embedded copies of /etc/protocols and /etc/services into permanent Go structures — retained heap in every binary, whether or not SQLite is ever opened.

Verifying a build

The tags are compile-time, so the check is too. Ask what YOUR package links, not what ./... matches -- the second form lists the pubsub packages themselves because they are in the pattern, and reports them as present whatever tags you pass:

Bash
go list -deps -tags gofr_nopubsub ./cmd/my-service | grep pubsub/kafka   # no output: not linked

For scale, against gofr.dev/pkg/gofr itself: 827 packages by default; 614 with gofr_nopubsub, 787 with gofr_nootlp, 783 with gofr_nosqldrivers, 807 with gofr_nographql, 817 with gofr_nogrpc, 825 with gofr_nodgraph -- and 411 with all six, half the default.

Measured on darwin/arm64 with Go 1.26.3. The counts are platform- and toolchain-dependent -- the standard library and modernc.org/libc pull in different package sets per GOOS/GOARCH, so the same commands on linux/amd64 report roughly twenty fewer. The differences between the rows are what the tags are about; the absolute numbers are not comparable across platforms.

These figures move with every dependency change, so treat them as a sense of scale rather than a contract. Run the command above against your own service for the number that matters to you. What CI does enforce is the direction: the Slim Build Tags job fails if any of these tags stops removing the packages it names.