Ctrl K
ComunidadGuías…

go_llm_instructions.txt

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
## Go style guide

MUST write valid Go v1.22+ code, best practices.

## Generated folders — do not edit

`encore.gen/` + `.encore/` CLI-regenerated; never edit.

`encore.app` at repo root = app manifest — JSON **text** file (comments allowed), not binary, despite `.app` extension. Read like any source file.

## Encore local MCP server

Encore's local MCP server is registered as **`encore-local`** in the editor's MCP config when set up via `encore app create` / `encore llm-rules init` (Claude Code: `.mcp.json`, Cursor: `.cursor/mcp.json`, VS Code: `.vscode/mcp.json`, Zed: `.zed/settings.json`). Not configured? Add a stdio server running `encore mcp run --app=<app-id>`. Prefer it for live app introspection; use its docs tools when unsure of Encore API surface.

MCP client gets full tool catalog (names, descriptions, input schemas) via `tools/list` on session init — no list here.

### Important tools (runtime-only — no filesystem equivalent)

These do what file search + reading source can't. For static structure, see "MCP only for runtime data" below.

- **`call_endpoint`** — args: `service`, `endpoint`, `method`, `path`, `payload` (JSON string w/ body/query/headers/path-params), optional `auth_token` / `auth_payload` / `correlation_id`. **auto-starts app** if not running. Use instead of `curl` to test endpoints.
- **`query_database`** — args: `queries` (array of `{database, query}`). Runs SQL against named DBs, multi queries per call. Beats `encore db conn-uri` + `psql` round-trips.
- **`get_traces`** — args: optional `service`, `endpoint`, `topic`, `subscription`, `error` (`"true"`/`"false"`), `start_time` / `end_time` (ISO), `min_duration_ms` / `max_duration_ms`, `parent_trace_id`, `limit`. Recent root request/Pub/Sub/test traces (timing, status, ids).
- **`get_trace_spans`** — args: `trace_ids` (array from `get_traces`). Full per-span details for deep debug.
- **`get_objects`** — args: `buckets` (array of bucket names). Lists objects + metadata in storage buckets.
- **`search_docs`** — args: `query`, optional `hits_per_page`, `page`, `facet_filters`. Algolia-backed Encore docs search.
- **`get_docs`** — args: `paths` (array of doc paths, e.g. `/docs/go/primitives/databases`). Fetches full doc pages found via `search_docs`.

### When to reach for it

| Situation | Tool |
|---|---|
| "What services / endpoints / databases exist?" | `get_metadata` once |
| "Test my new POST /orders endpoint" | `call_endpoint` (auto-starts app) |
| "Did the Pub/Sub handler run after I published?" | `get_traces` w/ `topic` / `subscription` filter |
| "Why did last request fail?" | `get_traces` then `get_trace_spans` |
| "How does Encore API surface work?" | `search_docs` then `get_docs` |

- **MCP only for runtime data** - For **static structure** in source (services, endpoints, topics, subscriptions, schemas, secrets, middleware, cron jobs), searching + reading source files = faster + more reliable than `get_*` tools.

## Encore Cloud access

Needed for deploying, cloud secrets, `encore logs --env`, `encore db shell --env`, and the Cloud MCP. Local dev (`encore run`, `encore test`, `encore-local` MCP) needs no login.

### CLI login

- Check first: `encore auth whoami`. Logged in → done.
- Logged out + interactive: ask the user to run `encore auth login` (opens a browser; can't be completed by an agent). New account: `encore auth signup`.
- CI / no browser: set `ENCORE_AUTH_KEY` (app-scoped auth key from Cloud Dashboard > app > App Settings > Auth Keys; prefer ephemeral keys); CLI picks it up automatically. Never pass a real key via `--auth-key`: leaks into shell history + process list.

### Cloud MCP (deployed environments)

Remote MCP server at `https://api.encore.dev/mcp` (HTTP transport): prod/staging traces, deploys, builds + logs, envs, infra, secrets list, read-only DB queries. `encore-local` only sees the local app. Not configured by `encore app create` / `encore llm-rules init`; register as `encore-cloud` if the app is on Encore Cloud and the user wants it.

Auth (separate from CLI login):
- OAuth (default, local dev): no credentials in config. First use opens a browser to grant access → tell the user to expect it.
- API key (CI / shared config): user generates one in Cloud Dashboard > app Settings > API Keys, send as `Authorization: Bearer <key>` header. Keep key out of git: use an env var or user-level (not project) config.

Config:
- Claude Code: `claude mcp add --transport http encore-cloud https://api.encore.dev/mcp` (API key: add `--header "Authorization: Bearer $ENCORE_API_KEY"`). Verify: `claude mcp list`.
- Cursor (`.cursor/mcp.json`): `{"mcpServers": {"encore-cloud": {"url": "https://api.encore.dev/mcp"}}}`
- VS Code (`.vscode/mcp.json`): `{"servers": {"encore-cloud": {"type": "http", "url": "https://api.encore.dev/mcp"}}}`
- Zed (`.zed/settings.json`): `{"context_servers": {"encore-cloud": {"url": "https://api.encore.dev/mcp"}}}`
- API key in JSON configs: add `"headers": {"Authorization": "Bearer ${env:ENCORE_API_KEY}"}` (Cursor/VS Code syntax) rather than a literal key.

Docs: https://encore.dev/docs/platform/cloud-mcp

## Encore check (verify app builds + endpoints work)

`encore check` compiles, boots, health-checks app, optionally runs `curl` once healthy. Use instead of manual `encore run + healthz poll + curl`.

```bash
encore check                                          # compile + boot only
encore check 'curl /ping'                             # GET a relative path
encore check 'curl /orders -X POST -d "{\"customer_id\":\"c1\",\"amount_cents\":1999}"'   # POST with JSON body (flags after path)
encore check 'curl /a; curl /b'                       # chain in one quoted string
encore check 'curl /a' 'curl /b'                      # or as separate quoted args
encore check < commands.txt                           # one curl per line, piped from file
```

Rules embedded curl DSL:
- **Paths relative** (start with `/`). Use `curl /orders/1`, **not** `curl http://localhost:4000/orders/1` — host/port supplied by `encore check`. Absolute URL fails with `curl path "http..." must be relative`.
- **Path first, flags after.** `curl /orders -X POST -d '...'` works; `curl -X POST /orders ...` fails — parser reads first token after `curl` as path, rejects flags there (`curl path "-X" must be relative`).
- One curl per quoted command. Chain `;` inside one quoted string, pass multiple quoted args, or pipe file one curl per line.
- **HTTP errors fail command.** Status >= 400 → command fails, response body still printed. Expecting error status (e.g. 401 without auth)? Add `--no-fail`: `curl /admin --no-fail -i`.

## Pub/sub verification

- Delivery async + at-least-once + not order-preserving.
- For "most-recent first" by publish order, DO NOT sort by `created_at = NOW()` or subscription-side row id — both subscription-insert order, at-least-once can flip. Sort by column monotonic with publish (entity id from event payload, or publish-time sequence in event).
- Handler-only checks, skip infra: `et.Topic(T).PublishedMessages()`.

## Deploying to Encore Cloud

Deploy = commit + push to the git repo the app deploys from:
- Default: Encore-managed git, so push to the `encore` remote.
- App linked to a GitHub repository (Cloud Dashboard > Settings > GitHub): push to that repository (usually `origin`); GitHub pushes trigger deploys.
- Check `git remote -v`. If you can't tell which one the app deploys from, ask the user.

Which environment deploys: each environment deploys from its own branch (environment Settings > Branch Push) or only manually. New apps have one environment, `staging` (type `development`), deploying the app's main branch. One push can deploy several environments. If a production environment deploys from the branch you push, the push deploys to production, so check before pushing.

```bash
git add -A && git commit -m "describe change"
git push encore              # Encore-managed git
git push origin <branch>     # GitHub-linked app
```

- `encore` remote: success prints one line per environment, `remote: <branch>: triggered deploy https://app.encore.dev/<app-id>/envs/<env>/deploys/<id>`. No such line = nothing deployed (nothing new committed, or no environment deploys from the pushed branch).
- GitHub: git prints nothing from Encore. Check Deployments in the Cloud Dashboard, or the Cloud MCP (above; lists environments, deploys, builds and their logs).
- Push returns once the deploy starts; no CLI wait. Environment API: `https://<env>-<app-id>.encr.app` (unless a custom domain is set). Logs: `encore logs --env=<env>`.
- The deploy runs the app's tests; failing tests block the deploy, so run `encore test ./...` first.
- Every declared secret needs a value in the target environment, else the deploy fails with `secret key(s) not defined`. Set per environment type: `printf '%s' "$VAL" | encore secret set --type <types> <Name>` (`dev` = development environments including `staging`, `prod` = production, `pr` = preview, `local`), or for one environment: `--env <env-name>`.
- Redeploy without a code change (e.g. after setting a secret): `git commit --allow-empty -m "Redeploy"` and push to the deploy branch, or Cloud Dashboard > Deployments > New Deploy > Branch.
- No `encore` remote and no GitHub link = app created logged out or with `--platform=false`. Log in (see Encore Cloud access > CLI login), `rm encore.app` (copy settings other than `id`/`lang` first, re-add after), `encore app init <name> --lang go` (registers the app and adds the `encore` remote), commit, push. App created in the Cloud Dashboard: `encore app clone <app-id>` (GitHub-linked: `git clone` the GitHub repository).
- Failed deploys: https://encore.dev/docs/platform/deploy/troubleshooting

## Encore Go domain knowledge

### Application structure

Encore uses monorepo design — one app = entire backend. Enables distributed tracing + architecture diagrams via unified application model. Supports monolith + microservices with monolith-style DX.

Directory structure:
/app-name
  encore.app
  service1/
    migrations/
      1_create_table.up.sql
    service1.go
    service1_test.go
  service2/
    service2.go

Sub-packages internal to services, cannot define APIs, used for helpers + code organization.

Large apps — group related services into system directories (logical groupings, no special runtime behavior):
/app-name
  encore.app
  system1/
    service1/
    service2/
  system2/
    service3/

### API definition

Create type-safe APIs from regular Go functions via //encore:api annotation.

Access controls:
- public: Accessible to anyone on internet
- private: Only accessible within app + via cron jobs
- auth: Public but requires valid auth

Function signatures:
func Foo(ctx context.Context, p *Params) (*Response, error)  // full
func Foo(ctx context.Context) (*Response, error)             // response only
func Foo(ctx context.Context, p *Params) error               // request only
func Foo(ctx context.Context) error                          // minimal

Request/response data locations:
- header: Use `header` tag for HTTP headers
- query: Default GET/HEAD/DELETE, uses snake_case, supports basic types/slices
- body: Default other methods, uses `json` tag, supports complex types

Path parameters: Use :name for variables, *name for wildcards. Place at end of path.

Sensitive data:
- Field level: `encore:"sensitive"` tag, auto-redacted in tracing
- Endpoint level: Add `sensitive` to //encore:api annotation

Type support by location:
- headers/path: bool, numeric, string, time.Time, UUID, json.RawMessage
- query: All above plus lists
- body: All types including structs, maps, pointers

### Services

Service defined by creating at least one API within Go package. Package name = service name.

//encore:service annotation enables custom init + graceful shutdown:

//encore:service
type Service struct {
    // Dependencies here
}

func initService() (*Service, error) {
    // Initialization code
}

//encore:api public
func (s *Service) MyAPI(ctx context.Context) error {
    // API implementation
}

Graceful shutdown via Shutdown method:
func (s *Service) Shutdown(force context.Context)
- Graceful phase: Several seconds for completion
- Forced phase: When force context canceled, terminate immediately

### Raw endpoints

Lower-level HTTP access (webhooks, WebSockets):

//encore:api public raw
func Webhook(w http.ResponseWriter, req *http.Request) {
    // Process raw HTTP request
}

//encore:api public raw method=POST path=/webhook/:id
func Webhook(w http.ResponseWriter, req *http.Request) {
    id := encore.CurrentRequest().PathParams.Get("id")
}

### SQL databases

Encore treats SQL databases as logical resources with native PostgreSQL support.

Create database:
var tododb = sqldb.NewDatabase("todo", sqldb.DatabaseConfig{
    Migrations: "./migrations",
})

Migration naming: number_description.up.sql (e.g., 1_create_table.up.sql)
Migrations folder structure:
service/
  migrations/
    1_create_table.up.sql
    2_add_field.up.sql
  service.go

Data operations:
// Insert
_, err := tododb.Exec(ctx, `
    INSERT INTO todo_item (id, title, done)
    VALUES ($1, $2, $3)
`, id, title, done)

// Query
err := tododb.QueryRow(ctx, `
    SELECT id, title, done FROM todo_item LIMIT 1
`).Scan(&item.ID, &item.Title, &item.Done)
// Use errors.Is(err, sqldb.ErrNoRows) for no results

Always Scan into typed struct fields, never `interface{}` slots — compiler enforces column-to-field shape + catches drift between SELECT list + destination struct.

CLI commands:
- encore db shell database-name [--env=name] - Open psql shell
- encore db conn-uri database-name [--env=name] - Output connection string
- encore db proxy [--env=name] - Setup local connection proxy

### External databases

Existing databases — create dedicated package with lazy connection pool:

package externaldb

import (
    "context"
    "fmt"
    "github.com/jackc/pgx/v5/pgxpool"
    "go4.org/syncutil"
)

func Get(ctx context.Context) (*pgxpool.Pool, error) {
    err := once.Do(func() error {
        var err error
        pool, err = setup(ctx)
        return err
    })
    return pool, err
}

var (
    once syncutil.Once
    pool *pgxpool.Pool
)

var secrets struct {
    ExternalDBPassword string
}

func setup(ctx context.Context) (*pgxpool.Pool, error) {
    connString := fmt.Sprintf("postgresql://%s:%s@hostname:port/dbname?sslmode=require",
        "user", secrets.ExternalDBPassword)
    return pgxpool.New(ctx, connString)
}

Works with Cassandra, DynamoDB, BigTable, MongoDB, Neo4j, other services.

### Shared databases

Default: per-service databases for isolation. Share via sqldb.Named:

// In report service, access todo service's database:
var todoDB = sqldb.Named("todo")

//encore:api method=GET path=/report/todo
func CountCompletedTodos(ctx context.Context) (*ReportResponse, error) {
    var report ReportResponse
    err := todoDB.QueryRow(ctx,`
        SELECT COUNT(*) FROM todo_item WHERE completed = TRUE
    `).Scan(&report.Total)
    return &report, err
}

### Cron jobs

Declarative periodic tasks. Does not run locally or in Preview Environments.

import "encore.dev/cron"

var _ = cron.NewJob("welcome-email", cron.JobConfig{
    Title:    "Send welcome emails",
    Every:    2 * cron.Hour,
    Endpoint: SendWelcomeEmail,
})

//encore:api private
func SendWelcomeEmail(ctx context.Context) error {
    return nil
}

Scheduling options:
- Every: Must divide 24 hours evenly (e.g., 10 * cron.Minute, 6 * cron.Hour)
- Schedule: Cron expressions (e.g., "0 4 15 * *" for 4am UTC on 15th)

Requirements: Endpoints must be idempotent, no request parameters, signature func(context.Context) error or func(context.Context) (*T, error)

### Caching

Redis-based distributed caching.

import "encore.dev/storage/cache"

var MyCacheCluster = cache.NewCluster("my-cache-cluster", cache.ClusterConfig{
    EvictionPolicy: cache.AllKeysLRU,
})

// Keyspace with type safety
var RequestsPerUser = cache.NewIntKeyspace[auth.UID](MyCacheCluster, cache.KeyspaceConfig{
    KeyPattern:    "requests/:key",
    DefaultExpiry: cache.ExpireIn(10 * time.Second),
})

// Structured keys
type MyKey struct {
    UserID auth.UID
    ResourcePath string
}
var ResourceRequestsPerUser = cache.NewIntKeyspace[MyKey](MyCacheCluster, cache.KeyspaceConfig{
    KeyPattern:    "requests/:UserID/:ResourcePath",
    DefaultExpiry: cache.ExpireIn(10 * time.Second),
})

Supports strings, integers, floats, structs, sets, ordered lists.

### Object storage

Cloud-agnostic API compatible with S3, GCS, S3-compatible services.

var ProfilePictures = objects.NewBucket("profile-pictures", objects.BucketConfig{
    Versioned: false,
})

// Public bucket with CDN
var PublicAssets = objects.NewBucket("public-assets", objects.BucketConfig{
    Public: true,
})

Operations: Upload, Download, List, Remove, Attrs, Exists

Bucket references for permissions:
type myPerms interface {
    objects.Downloader
    objects.Uploader
}
ref := objects.BucketRef[myPerms](bucket)

### Pub/Sub

Async event broadcasting with automatic infra provisioning.

type SignupEvent struct{ UserID int }

var Signups = pubsub.NewTopic[*SignupEvent]("signups", pubsub.TopicConfig{
    DeliveryGuarantee: pubsub.AtLeastOnce,
})

// Publishing
messageID, err := Signups.Publish(ctx, &SignupEvent{UserID: id})

// Topic reference
signupRef := pubsub.TopicRef[pubsub.Publisher[*SignupEvent]](Signups)

// Subscribing
var _ = pubsub.NewSubscription(
    user.Signups, "send-welcome-email",
    pubsub.SubscriptionConfig[*SignupEvent]{
        Handler: SendWelcomeEmail,
    },
)

// Method handler with dependency injection
var _ = pubsub.NewSubscription(
    user.Signups, "send-welcome-email",
    pubsub.SubscriptionConfig[*SignupEvent]{
        Handler: pubsub.MethodHandler((*Service).SendWelcomeEmail),
    },
)

Delivery guarantees:
- AtLeastOnce: Handlers must be idempotent
- ExactlyOnce: Stronger guarantees (AWS: 300 msg/sec, GCP: 3000+ msg/sec)

Ordering: Use OrderingAttribute matching pubsub-attr tag

Testing:
msgs := et.Topic(Signups).PublishedMessages()
assert.Len(t, msgs, 1)

### Secrets

Built-in secrets manager for API keys, passwords, private keys.

var secrets struct {
    SSHPrivateKey string
    GitHubAPIToken string
}

func callGitHub(ctx context.Context) {
    req.Header.Add("Authorization", "token " + secrets.GitHubAPIToken)
}

CLI management:
- encore secret set --type production secret-name
- encore secret set --type development secret-name
- encore secret set --env env-name secret-name (env-specific override)

Types: production (prod), development (dev), preview (pr), local

Local override via .secrets.local.cue:
GitHubAPIToken: "my-local-override-token"

### API calls

Call APIs like regular functions with automatic type checking:

import "encore.app/hello"

//encore:api public
func MyOtherAPI(ctx context.Context) error {
    resp, err := hello.Ping(ctx, &hello.PingParams{Name: "World"})
    if err == nil {
        log.Println(resp.Message) // "Hello, World!"
    }
    return err
}

### Errors

Structured errors via encore.dev/beta/errs package.

return &errs.Error{
    Code: errs.NotFound,
    Message: "sprocket not found",
}
// Returns HTTP 404 {"code": "not_found", "message": "sprocket not found"}

Wrapping:
errs.Wrap(err, msg, metaPairs...)
errs.WrapCode(err, code, msg, metaPairs...)

Builder pattern:
eb := errs.B().Meta("board_id", params.ID)
return eb.Code(errs.NotFound).Msg("board not found").Err()

Error codes: OK(200), Canceled(499), Unknown(500), InvalidArgument(400), DeadlineExceeded(504), NotFound(404), AlreadyExists(409), PermissionDenied(403), ResourceExhausted(429), FailedPrecondition(400), Aborted(409), OutOfRange(400), Unimplemented(501), Internal(500), Unavailable(503), DataLoss(500), Unauthenticated(401)

Inspection: errs.Code(err), errs.Meta(err), errs.Details(err)

### Authentication

Flexible auth with different access levels.

import "encore.dev/beta/auth"

// Basic
//encore:authhandler
func AuthHandler(ctx context.Context, token string) (auth.UID, error) {
    // Validate token and return user ID
}

// With user data
type Data struct {
    Username string
}

//encore:authhandler
func AuthHandler(ctx context.Context, token string) (auth.UID, *Data, error) {
    // Return user ID and custom data
}

// Structured auth params
type MyAuthParams struct {
    SessionCookie *http.Cookie `cookie:"session"`
    ClientID string `query:"client_id"`
    Authorization string `header:"Authorization"`
}

//encore:authhandler
func AuthHandler(ctx context.Context, p *MyAuthParams) (auth.UID, error) {
    // Process structured auth params
}

Usage: auth.Data(), auth.UserID()
Override for testing: auth.WithContext(ctx, auth.UID("my-user-id"), &MyAuthData{})

Error handling:
return "", &errs.Error{
    Code: errs.Unauthenticated,
    Message: "invalid token",
}

### Configuration

Env-specific config via CUE files.

package mysvc

import "encore.dev/config"

type SomeConfigType struct {
    ReadOnly config.Bool
    Example  config.String
}

var cfg *SomeConfigType = config.Load[*SomeConfigType]()

CUE tags for constraints:
type FooBar struct {
    A int `cue:">100"`
    B int `cue:"A-50"`
    C int `cue:"A+B"`
}

Config types: config.String, config.Bool, config.Int, config.Float64, config.Time, config.UUID, config.Value[T], config.Values[T]

Meta values:
- APIBaseURL, Environment.Name, Environment.Type (production/development/ephemeral/test), Environment.Cloud (aws/gcp/encore/local)

Testing: et.SetCfg(cfg.SendEmails, true)

CUE patterns:
- Defaults: value: type | *default_value
- Switch: array with conditionals, take [0]

### CORS

Configure in encore.app file:
- debug: Enable CORS debug logging
- allow_headers: Additional accepted headers ("*" allows all)
- expose_headers: Additional exposed headers
- allow_origins_without_credentials: Defaults to ["*"]
- allow_origins_with_credentials: For authenticated requests, supports wildcards like "https://*.example.com"

### Metadata

Access app + request info via encore.dev package.

// Application metadata
meta := encore.Meta()
// meta.AppID, meta.APIBaseURL, meta.Environment, meta.Build, meta.Deploy

// Request metadata
req := encore.CurrentRequest()
// req.Service, req.Endpoint, req.Path, req.StartTime

// Cloud-specific behavior
switch encore.Meta().Environment.Cloud {
case encore.CloudAWS:
    return writeIntoRedshift(ctx, action, user)
case encore.CloudGCP:
    return writeIntoBigQuery(ctx, action, user)
}

### Middleware

Reusable code running before/after API requests.

//encore:middleware global target=all
func ValidationMiddleware(req middleware.Request, next middleware.Next) middleware.Response {
    payload := req.Data().Payload
    if validator, ok := payload.(interface { Validate() error }); ok {
        if err := validator.Validate(); err != nil {
            err = errs.WrapCode(err, errs.InvalidArgument, "validation failed")
            return middleware.Response{Err: err}
        }
    }
    return next(req)
}

// With dependency injection
//encore:middleware target=all
func (s *Service) MyMiddleware(req middleware.Request, next middleware.Next) middleware.Response {
    // Implementation
}

// Tag-based targeting
//encore:middleware target=tag:cache
func CachingMiddleware(req middleware.Request, next middleware.Next) middleware.Response {
    // ...
}

//encore:api public method=GET path=/user/:id tag:cache
func GetUser(ctx context.Context, id string) (*User, error) {
    // Implementation
}

Ordering: Global before service-specific, lexicographic by filename.

### Mocking

Built-in mocking for isolated testing.

// Mock endpoint for single test
func Test_Something(t *testing.T) {
    t.Parallel()
    et.MockEndpoint(products.GetPrice, func(ctx context.Context, p *products.PriceParams) (*products.PriceResponse, error) {
        return &products.PriceResponse{Price: 100}, nil
    })
}

// Mock endpoint for all tests in package
func TestMain(m *testing.M) {
    et.MockEndpoint(products.GetPrice, func(ctx context.Context, p *products.PriceParams) (*products.PriceResponse, error) {
        return &products.PriceResponse{Price: 100}, nil
    })
    os.Exit(m.Run())
}

// Mock entire service
et.MockService("products", &products.Service{
    SomeField: "a testing value",
})

// Type-safe service mocking
et.MockService[products.Interface]("products", &myMockObject{})

### Testing

Run tests: encore test ./...
Supports all standard go test flags. Built-in tracing at localhost:9400.

Database testing:
- Automatic setup in separate cluster, optimized for speed
- Temporary databases: et.NewTestDatabase() creates isolated, fully migrated DB

Service structs: Lazy init, instance sharing between tests
- Isolate: et.EnableServiceInstanceIsolation()

### Validation

Automatic request validation via Validate() method.

type MyRequest struct {
    Email string
}

func (r *MyRequest) Validate() error {
    if !isValidEmail(r.Email) {
        return &errs.Error{Code: errs.InvalidArgument, Message: "invalid email"}
    }
    return nil
}

Validation runs after deserialization, before handler. Non-errs.Error errors become InvalidArgument (HTTP 400).

### CGO

Enable in encore.app:
{
  "id": "my-app-id",
  "build": {
    "cgo_enabled": true
  }
}
Uses Ubuntu builder with gcc. Libraries must support static linking.

### Clerk auth

Implement Clerk auth:

package auth

import "github.com/clerkinc/clerk-sdk-go/clerk"

var secrets struct {
    ClientSecretKey string
}

//encore:service
type Service struct {
    client clerk.Client
}

func initService() (*Service, error) {
    client, err := clerk.NewClient(secrets.ClientSecretKey)
    if err != nil {
        return nil, err
    }
    return &Service{client: client}, nil
}

type UserData struct {
    ID                    string
    Username              *string
    FirstName             *string
    LastName              *string
    ProfileImageURL       string
    PrimaryEmailAddressID *string
    EmailAddresses        []clerk.EmailAddress
}

//encore:authhandler
func (s *Service) AuthHandler(ctx context.Context, token string) (auth.UID, *UserData, error) {
    // Token verification and user data retrieval
}

Set secrets:
- encore secret set --type prod ClientSecretKey
- encore secret set --type dev,local ClientSecretKey

### Dependency injection

Add dependencies as struct fields for easy testing:

package email

//encore:service
type Service struct {
    sendgridClient *sendgrid.Client
}

func initService() (*Service, error) {
    client, err := sendgrid.NewClient()
    if err != nil {
        return nil, err
    }
    return &Service{sendgridClient: client}, nil
}

//encore:api private
func (s *Service) Send(ctx context.Context, p *SendParams) error {
    // Use s.sendgridClient
}

// For testing, use interface
type sendgridClient interface {
    SendEmail(...)
}

func TestFoo(t *testing.T) {
    svc := &Service{sendgridClient: &myMockClient{}}
    // Test
}

### Pub/Sub outbox

Transactional outbox pattern for database + Pub/Sub consistency.

var SignupsTopic = pubsub.NewTopic[*SignupEvent](/* ... */)
ref := pubsub.TopicRef[pubsub.Publisher[*SignupEvent]](SignupsTopic)
ref = outbox.Bind(ref, outbox.TxPersister(tx))

Required schema:
CREATE TABLE outbox (
    id BIGSERIAL PRIMARY KEY,
    topic TEXT NOT NULL,
    data JSONB NOT NULL,
    inserted_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX outbox_topic_idx ON outbox (topic, id);

Relay setup:
type Service struct {
    signupsRef pubsub.Publisher[*SignupEvent]
}

func initService() (*Service, error) {
    relay := outbox.NewRelay(outbox.SQLDBStore(db))
    signupsRef := pubsub.TopicRef[pubsub.Publisher[*SignupEvent]](SignupsTopic)
    outbox.RegisterTopic(relay, signupsRef)
    go relay.PollForMessage(context.Background(), -1)
    return &Service{signupsRef: signupsRef}, nil
}

Supports: encore.dev/storage/sqldb, database/sql, github.com/jackc/pgx/v5

### Encore Toolbar (frontend dev panel)

Dev-only browser panel: intercepts frontend HTTP calls, captures Encore trace IDs, jumps to traces in the dashboard. Install: add `<script src="https://encore.dev/encore-toolbar.js"></script>` early in `<head>` (no `async`/`defer`). Reach for it when a frontend talks to an Encore Go backend.

### Example apps

- Hello World: https://github.com/encoredev/examples/tree/main/hello-world
- URL Shortener: https://github.com/encoredev/examples/tree/main/url-shortener
- Uptime Monitor: https://github.com/encoredev/examples/tree/main/uptime

## Encore CLI reference

Execution:
- encore help [command] - Lists available commands (or shows usage for specific command). Reach for it when unsure which subcommand or flag to use.
- encore run [--debug] [--watch=true] [-p port] [flags] - Runs app. Before starting yourself, check if already running by polling `http://localhost:<port>/__encore/healthz` (default port `4000`) — `200` = healthy, hit endpoints directly without re-running.
- encore check ['curl ...'; 'curl ...'] | encore check < commands.txt - Compile + boot app, verify every service healthy, optionally run curl commands. See dedicated section above.
- encore test ./... [go test flags] - Wraps `go test ./...` with Encore's infra provisioning — boots isolated test databases, Pub/Sub, etc. before handing off to `go test`. Accepts all `go test` flags, plus `--prepare`, `--codegen-debug`, `--trace`.
- encore exec path/to/script [args...] - Run an executable script against the local Encore app

App management:
- encore app clone [app-id] [directory] - Clone app
- encore app create [name] [--example=name] [-l go] - Create new app
- encore app init [name] - Create from existing repo
- encore app link [app-id] [-f] - Link to an existing Encore Cloud app (adds the `encore` remote only if the repo has no remotes)

Authentication:
- encore auth login/logout/signup/whoami

Daemon:
- encore daemon - Restart daemon
- encore daemon env - Output environment info

Database:
- encore db shell database-name [--env=name] - psql shell (--write, --admin, --superuser)
- encore db conn-uri database-name [--env=name] - Connection string
- encore db proxy [--env=name] - Local proxy
- encore db reset <db-names...|--all> - Reset databases

Code generation:
- encore gen client [app-id] [--env=name] [--lang=lang] - Generate API client
  Languages: go, typescript, javascript, openapi

Logging:
- encore logs [--env=prod] [--json] [-q] - Stream logs

Kubernetes:
- encore k8s configure --env=ENV_NAME - Update kubectl config

Secrets:
- encore secret set --type TYPE <secret-name> (types: production, development, preview, local)
- encore secret set --env env-name <secret-name>
- encore secret list [keys...]
- encore secret delete <id>

Namespaces (infrastructure environments, alias `encore ns`):
- encore namespace list [--output=columns|json] - List infra namespaces
- encore namespace create NAME - Create a namespace
- encore namespace switch [--create] NAME - Switch active namespace
- encore namespace delete NAME - Delete a namespace

MCP (programmatic access to the local app — same surface as `.mcp.json`):
- encore mcp run - stdio-based MCP session
- encore mcp start - SSE-based MCP session; prints the SSE URL

Config:
- encore config <key> [<value>] [--app|--global|--all] - Get/set CLI configuration

Telemetry:
- encore telemetry - Status
- encore telemetry enable / disable - Toggle telemetry reporting

LLM rules:
- encore llm-rules init - Generate LLM rule files for this project

Random data (utility):
- encore rand uuid [-1|-4|-6|-7] - Generate UUID (default v4)
- encore rand bytes N [-f format] - Generate N random bytes
- encore rand words [--sep=SEP] NUM - Generate memorable passphrase

Deploy:
- git push encore (or push to the linked GitHub repository) - Deploy (see Deploying to Encore Cloud)

Version:
- encore version - Report version
- encore version update - Check and apply updates

Build:
- encore build docker IMAGE_TAG [--base string] [--push] [--cgo] [--os] [--arch] - Build Docker image