{"id":48726410,"url":"https://github.com/alecthomas/zero","last_synced_at":"2026-04-11T22:57:00.847Z","repository":{"id":306026617,"uuid":"1024716873","full_name":"alecthomas/zero","owner":"alecthomas","description":"An opinionated tool for simplifying building servers in Go.","archived":false,"fork":false,"pushed_at":"2026-03-31T08:13:02.000Z","size":377,"stargazers_count":8,"open_issues_count":11,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-11T22:56:29.007Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alecthomas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-23T06:26:16.000Z","updated_at":"2026-04-07T22:57:03.000Z","dependencies_parsed_at":"2025-07-23T09:18:57.672Z","dependency_job_id":"7e1b938d-49cd-421d-aedf-b4cbde13053c","html_url":"https://github.com/alecthomas/zero","commit_stats":null,"previous_names":["alecthomas/zero"],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/alecthomas/zero","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fzero","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fzero/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fzero/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fzero/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alecthomas","download_url":"https://codeload.github.com/alecthomas/zero/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fzero/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31698152,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-11T21:17:31.016Z","status":"ssl_error","status_checked_at":"2026-04-11T21:17:24.556Z","response_time":54,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-11T22:57:00.166Z","updated_at":"2026-04-11T22:57:00.838Z","avatar_url":"https://github.com/alecthomas.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Zero\n\nAn opinionated tool for simplifying building servers in Go.\n\nRunning `zero` on a codebase will generate a function that completely wires up a service from scratch, including request handlers, cron jobs, pubsub, databases, etc.\n\nA core tenet of Zero Services it that it will work with the normal Go development lifecycle, without any additional steps. Your code should build and be testable out of the box. Code generation is only required for full service construction, but even then it's possible to construct and test the service without code generation. There's minimal lock-in with Zero, because your code is standard Go. The main exception to that is the request handlers, which remove request/response boilerplate.\n\n## Request Handlers\n\nZero will automatically generate `http.Handler` implementations for any method annotated with `//zero:api`, providing request decoding, response encoding, path variable decoding, query parameter decoding, and error handling.\n\n```go\n//zero:api [\u003cmethod\u003e] [\u003chost\u003e]/[\u003cpath\u003e] [\u003clabel\u003e[=\u003cvalue\u003e] ...]\nfunc (s Struct) Method([pathVar0, pathVar1 string][, req Request]) ([\u003cresponse\u003e, ][error]) { ... }\n```\n\n`http.ServeMux` is used for routing and thus the pattern syntax is identical.\n\n## Request decoding\n\nHere's how Zero decodes requests into Go types:\n\n1. If the method is a PUT, POST or PATCH its body will be decoded into the request type.\n2. For all other methods, the Go type will be decoded from the query parameters and must be a struct with optional tags of the form `qstring:\"\u003cname\u003e\"`.\n\n### Response encoding\n\nDepending on the type of the \u003cresponse\u003e value, the response will be encoded in the following ways:\n\n| Type | Encoding |\n| ---- | -------- |\n| `nil`/omitted | 204 No Content |\n| `string` | `text/html` |\n| `[]byte` | `application/octet-stream` |\n| `io.Reader` | `application/octet-stream` |\n| `io.ReadCloser` | `application/octet-stream` |\n| `*http.Response` | Response structure is used as-is. |\n| `http.Handler` | The response type's `ServeHTTP()` method will be called. |\n| `*` | `application/json` |\n\nResponses may optionally implement the interface `zero.StatusCode` to control the returned HTTP status code.\n\nAdditionally, if the default Zero encoding scheme is not to your liking you can provide a custom provider for `zero.ResponseEncoder`.\n\n### Error responses\n\nAs with response bodies, if the returned error type implements `http.Handler`, its `ServeHTTP()` method will be called.\n\nA default error handler may also be registered by creating a custom provider for `zero.ErrorEncoder`.\n\n### OpenAPI Specification\n\nUse `zero --openapi --openapi-title=TITLE --openapi-version=VERSION` to generate an OpenAPI spec for your service. Note that there are currently limitations around\nfine-grained control of the generated spec', but the goal is to improve this as time permits.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eeg. OpenAPI spec for the exemplar.\u003c/summary\u003e\n\n```bash\n$ zero --openapi\n{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"My Zero Service\",\n    \"version\": \"dev\"\n  },\n  \"paths\": {\n    \"/users\": {\n      \"get\": {\n        \"tags\": [\n          \"main\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Success\",\n            \"schema\": {\n              \"type\": \"array\",\n              \"items\": {\n                \"$ref\": \"#/definitions/main.User\"\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\"\n          },\n          \"500\": {\n            \"description\": \"Internal Server Error\"\n          }\n        }\n      },\n      \"post\": {\n        \"tags\": [\n          \"main\"\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/main.User\"\n            }\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"No Content\"\n          },\n          \"400\": {\n            \"description\": \"Bad Request\"\n          },\n          \"500\": {\n            \"description\": \"Internal Server Error\"\n          }\n        }\n      }\n    },\n    \"/users/{id}\": {\n      \"get\": {\n        \"tags\": [\n          \"main\"\n        ],\n        \"parameters\": [\n          {\n            \"type\": \"string\",\n            \"name\": \"id\",\n            \"in\": \"path\",\n            \"required\": true\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Success\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/main.User\"\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\"\n          },\n          \"500\": {\n            \"description\": \"Internal Server Error\"\n          }\n        }\n      }\n    }\n  },\n  \"definitions\": {\n    \"main.User\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"birthYear\": {\n          \"type\": \"integer\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        }\n      }\n    }\n  }\n}\n```\n\u003c/details\u003e\n\n### Service Interfaces (NOT IMPLEMENTED)\n\nAdditionally, any user-defined interface matching a subset of API methods will have the service itself injected. That is, given the following service:\n\n```go\n//zero:api GET /users\nfunc (s *Service) ListUsers() ([]User, error) { ... }\n\n//zero:api POST /users authenticated\nfunc (s *Service) CreateUser(ctx context.Context, user User) error { ... }\n```\n\nInjecting any of the following interfaces will result in the service being injected to fulfil the interface:\n\n```go\ninterface {\n  ListUsers() ([]User, error)\n}\n\ninterface {\n  CreateUser(ctx context.Context, user User) error\n}\n\ninterface {\n  ListUsers() ([]User, error)\n  CreateUser(ctx context.Context, user User) error\n}\n```\n\nThis can be very useful for testing.\n\n## Configuration\n\nA struct annotated with `//zero:config [prefix=\"\u003cprefix\u003e\"]` will be used as embedded [Kong](https://github.com/alecthomas/kong)-annotated configuration, with corresponding config loading from JSON/YAML/HCL. These config structs can in turn be used during dependency injection.\n\nThe variable `${root}` contains the `lower-kebab-case` transformation of the type, and can be interpolated into `prefix`. This is useful for generic configuration to uniquely identify the flags.\n\neg. The following code will result in the following flags, one from each concrete `StorageConfig` type.\n\n```\n--storage-user-path=PATH\n--storage-address-path=PATH\n```\n\n```go\n//zero:config prefix=\"storage-${type}-\"\ntype StorageConfig[T any] struct {\n\tPath string `help:\"Path to the data root.\" required:\"\"`\n}\n\n//zero:provider\nfunc Storage(uconf StorageConfig[User], aconf StorageConfig[Address]) *Store { ... }\n```\n\n## Middleware\n\nA function annotated with `//zero:middleware [\u003clabel\u003e]` will be automatically used as HTTP middleware for any method matching the given `\u003clabel\u003e` if provided, or applied globally if not. Option values can be retrieved from the request with `zero.HandlerOptions(r)`.\n\neg.\n\n```go\n//zero:middleware authenticated\nfunc Auth(next http.Handler) http.Handler {\n  return func(w http.ResponseWriter, r *http.Request) {\n    auth := r.Header().Get(\"Authorization\")\n    // ...\n\n}\n```\n\nAlternatively, for middleware that requires injection, the annotated middleware function can instead be one that *returns* a middleware function:\n\n```go\n//zero:middleware authenticated role\nfunc Auth(role string, dal *DAL) func(http.Handler) http.Handler {\n  return func(next http.Handler) http.Handler {\n    return func(w http.ResponseWriter, r *http.Request) {\n      auth := r.Header().Get(\"Authorization\")\n      // ...\n    }\n  }\n}\n```\n\n## Admin Dashboard\n\nZero has an extensible dashboard built in and served under `/_admin/`.\n\nTo define your own dashboard components:\n\n1. Implement the `github.com/alecthomas/zero/providers/dashboard.Component` interface.\n2. Define a `//zero:api GET /_admin/\u003cslug\u003e` endpoint and use `dashboard.Renderer` to render your HTML. This helper type will render the Dashboard frame around your components content.\n\n## Dependency injection\n\nAny function annotated with `//zero:provider [weak] [multi] [require=\u003cprovider\u003e,...]` will be used to provide its return type during application construction.\n\neg. The following code will inject a `*DAL` type and provide a `*Service` type.\n\n```go\n//zero:provider\nfunc NewService(dal *DAL) (*Service, error) { ... }\n```\n\nThis is somewhat similar to Google's Wire [project](https://github.com/google/wire).\n\n### Weak providers\n\nWeak providers are marked with `weak`, and may be overridden implicitly by creating a non-weak provider, or explicitly by selecting the provider to use via `--resolve`.\n\nWeak providers are selected if any of the following conditions are true:\n\n- They are the only provider of that type.\n- They were explicitly selected by the user.\n- They are injected by another provider via `require=\u003cprovider\u003e`.\n\n### Multi-providers\n\nA multi-provider allows multiple providers to contribute to a single merged type value. The provided type must return a\nslice or a map. Note that slice order is not guaranteed.\n\neg. In the following example the slice `[]string{\"hello\", \"world\"}` will be provided.\n\n```go\n//zero:provider multi\nfunc Hello() []string { return []string{\"hello\"} }\n\n//zero:provider multi\nfunc World() []string { return []string{\"world\"} }\n````\n\n### Explicit dependencies\n\nA weak provider may also explicitly request other weak dependencies be injected by using `require=\u003cprovider\u003e`. This is useful when an injected parameter of the provider is itself reliant on an optional weak type.\n\neg. In this example the `SQLCron()` provider requires that the migrations provided by `CronSQLMigrations()` have already been applied to `*sql.DB`, which in turn requires `[]Migration`. By explicitly specifiying `require=CronSQLMigrations`, the previously ignored weak provider will be added.\n\n```go\n//zero:provider\nfunc NewDB(config Config, migrations []Migration) *sql.DB { ... }\n\n//zero:provider weak multi\nfunc CronSQLMigrations() []Migration { ... }\n\n//zero:provider weak require=CronSQLMigrations\nfunc SQLCron(db *sql.DB) cron.Executor { ... }\n````\n\n## Builtin Providers\n\nZero ships with providers for a number of common use-cases, including SQL, logging, and so on.\n\n### SQL\n\nThe SQL provider supports Postgres, MySQL, and SQLite out of the box, but can be extended at runtime. For each database,\nit supports (re)creation of databases and migrations during development, and dumping of migration files for use with\nproduction migration tooling.\n\nThere are a few steps that have to be followed to configure SQL support:\n\n#### 1. Enable the driver in the build\n\nBy default drivers are excluded via Go build tags to reduce the dependencies for end-user builds. To enable a particular\ndriver use something like:\n\n```bash\nexport GOFLAGS='--tags=postgres'\nzero ./cmd/service\n```\n\n#### 2. Set the DSN for development\n\nTo set the default DSN for the configuration, pass the Kong option `kong.Vars{\"sqldsn\": \"...\"}`.\n\nDSNs are URN-like, where the part after the schema is driver-specific. eg.\n\n```\nsqlite://file:boop?mode=memory\nmysql://root:secret@tcp(localhost:3306)/zero\npostgres://postgres:secret@localhost:5432/zero-test?sslmode=disable\n```\n\n#### 3. Provide migrations\n\nMigrations are provided as a slice of Go `fs.FS` filesystems. Every `.sql` file in the root of each FS will be applied, with all files globally lexically ordered. Files across multiple migration filesystems must be globally unique.\n\nGood practice is to name migration files something like:\n\n```\n\u003cid\u003e_\u003ctable\u003e_\u003cdescription\u003e.sql\n```\n\neg.\n\n```\n001_users_create.sql\n```\n\n\u003e [!NOTE]\n\u003e External provider migrations should use `\u003cid\u003e`'s in the range 00001-00099.\n\nHere's an example of providing migrations from an embedded FS (recommended):\n\n```go\nimport zerosql \"github.com/alecthomas/zero/providers/sql\"\n\n//go:embed migrations/*.sql\nvar migrations embed.FS\n\n//zero:provider multi\nfunc Migrations() zerosql.Migrations {\n\tsub, _ := fs.Sub(migrations, \"migrations\")\n\treturn zerosql.Migrations{sub}\n}\n````\n\n## Leases\n\nZero supports [leases](https://en.wikipedia.org/wiki/Lease_(computer_science)) for coordination. There are two implementations available, in-memory, and one based on SQL. The latter is intended to be robust in the face of failures and timeouts, and in particular has the property that if lease renewal fails, the process will be terminated. This ensures that split-brain cannot occur, but _can_ result in service outage of the database is unavailable. However, if the database is unavailable, your service is likely down anyway.\n\nTo use leases:\n\n1. Inject the lease interface:\n\n    ```go\n    //zero:provider\n    func NewService(leaser leases.Leaser) *Service { ... }\n    ````\n\n2. Select the lease implementation to use:\n\n    ```bash\n    zero --resolve github.com/alecthomas/zero/providers/leases.NewMemoryLeaser ./cmd/service\n    ```\n\n## Cron\n\nA method annotated with `//zero:cron \u003cschedule\u003e` will be called on the given schedule. Schedules currently must be in the form `\u003cn\u003e[smhdw]`.\n\neg.\n\n```go\n//zero:cron 5s\nfunc (s *Service) CheckUsers(ctx context.Context) error {\n\t// ...\n\treturn nil\n}\n````\n\n## PubSub (NOT IMPLEMENTED)\n\nA method annotated with `//zero:subscribe` will result in the method being called whenever the corresponding pubsub topic receives an event. The PubSub implementation itself is described by the `zero.Topic[T]` interface, which may be injected in order to publish to a topic. A topic's payload type is used to uniquely identify that topic.\n\nTo cater to arbitrarily typed PubSub topics, a generic provider function may be declared that returns a generic `zero.Topic[T]`. This will be called during injection with the event type of a subscriber or publisher.\n\neg.\n\n```go\n//ftl:provider\nfunc NewKafkaConnection(ctx context.Context, config KafkaConfig) (*kafka.Conn, error) {\n  return kafka.DialContext(ctx, config.Network, config.Address)\n}\n\n//ftl:provider\nfunc NewPubSubTopic[T any](ctx context.Context, conn *kafka.Conn) (zero.Topic[T], error) {\n  // ...\n}\n```\n\n## Infrastructure (NOT IMPLEMENTED)\n\nWhile the base usage of Zero doesn't deal with infrastructure at all, it would be possible to automatically extract required infrastructure and inject provisioned implementations of those into the injection graph as it is being constructed.\n\nFor example, if a service consumes `pubsub.Topic[T]` and there is no provider, one could be provided by an external provisioning plugin. The plugin could get called with the missing type, and return code that provides that type, as well as eg. Terraform for provisioning the infrastructure.\n\nThis is not thought out in detail, but the basic approach should work.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falecthomas%2Fzero","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falecthomas%2Fzero","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falecthomas%2Fzero/lists"}