Best Practices
entrest generates OpenAPI 3.0.3 specs and validates them during its own test suite. The patterns below are what the generator targets — they align with common linter rules (including IBM OpenAPI Validator) and with tools that generate HTTP clients from a spec.
Generated specs are checked with libopenapi-validator before entrest merges changes. You can run the same class of checks on your project's openapi.json with IBM OpenAPI Validator or Spectral. See Recommended Tools.
If validation fails after customizing the spec, the failure is usually a duplicate operationId, a conflicting path merge, or an invalid $ref.
Reuse components instead of duplicating schemas
Section titled “Reuse components instead of duplicating schemas”entrest puts shared pieces under components and references them with $ref:
| Component | Purpose |
|---|---|
PagedResponse | Shared pagination metadata (page, last_page, is_last_page, total_count) |
Page, PrettyResponse, FilterOperation | Shared query parameters |
ErrorBadRequest, ErrorNotFound, … | Shared error responses |
{Entity}TypeEnum, etc. | Hoisted enum schemas |
{Entity}Create, {Entity}Update, {Entity}Read | Per-operation body schemas |
Enum fields are hoisted into component schemas (for example UserTypeEnum) so the same enum is not inlined on every property and filter parameter.
Global request headers are attached once per path via components/parameters, not repeated on every operation. Global response headers and error responses use components/headers and components/responses the same way.
When you extend the spec with Extending the Spec, prefer adding reusable components and $refing them rather than copying JSON into each path.
Each generated operation includes:
- operationId — Unique across the whole spec. Default pattern:
createUser,getUser,updateUser,deleteUser,listUsers,listUserPets, etc. Override withWithOperationIDwhen you need a stable name for client codegen. - tags — One tag per entity (
Users,Pets, …). Edge endpoints include both parent and child tags.AddEdgesToTagsoptionally adds eager-loaded entity names to parent operations. - summary and description — Defaults are generated from the entity name; override with
WithOperationSummaryandWithOperationDescription. - Path parameters —
{userID}style names (camelCase entity name +ID), defined incomponents/parametersand referenced on the path item.
Paths use plural kebab-case segments (/users, /users/{userID}/pets).
Create, update, and read use different component schemas (UserCreate, UserUpdate, UserRead). That keeps required fields and writable properties accurate for client generators — a create body is not the same as a response object.
Read responses use User plus optional UserEdges composed with allOf when edges are eager-loaded. List responses reference UserRead (or a paged wrapper around it).
Where ent (or your annotations) provide the data, the spec includes:
| Source | OpenAPI field |
|---|---|
field.Comment() or WithDescription | description |
WithExample | example |
Ent field Default() | default |
Go integer width (int8, uint32, …) | minimum / maximum on integers |
Optional() + Nillable() | nullable: true |
| Ent enum values | enum (often via a hoisted component schema) |
Add examples and descriptions on fields that clients or docs viewers will display often. Passwords, tokens, and fields marked Sensitive() are omitted from read schemas; use WithSkip or WithReadOnly to keep fields out of write schemas too.
JSON fields without a native OpenAPI type need WithSchema (for example SchemaObjectAny for arbitrary objects).
List endpoints document page, per_page, sort, and order with defaults and bounds from Configuration. Paginated list responses use allOf with PagedResponse and a content array — see Pagination.
Filter query parameters are defined in components/parameters and referenced on the operation, with descriptions derived from the filter predicate (for example “Filters field email to equal the provided value”).
By default, every operation documents shared error responses for 400, 401, 403, 404, 409, 429, and 500. Each points at a component response whose body matches the shape returned by the generated HTTP handler:
{ "error": "…", "type": "Not Found", "code": 404, "request_id": "…", "timestamp": "2024-04-26T12:19:01Z"}type, code, and timestamp are always present; request_id appears when request ID middleware is configured.
entrest skips some status codes where they do not apply:
| Status | Omitted when |
|---|---|
| 404 on list | ListNotFound is false (default). Empty filter results return 200 with an empty content array. |
| 409 | Operation is not create or update. |
Customize the set with GlobalErrorResponses or extend bodies in Extending the Spec.
Tools like OpenAPI Generator, oapi-codegen, and Speakeasy rely on:
- Stable, unique
operationIdvalues — do not hand-edit generated IDs; use annotations if a generator chokes on a default name. - Tags — group operations in generated clients; keep tag names aligned with your API surface.
- Explicit component schemas — entrest avoids anonymous inline objects on success responses where possible.
- Consistent error models — one error schema per status code, referenced everywhere.
If you only need the spec and not the handler, set Handler to HandlerNone and feed openapi.json directly into your generator.
Large Ent graphs produce large specs. Most of the bulk is filter parameters and eager-loaded edge schemas.
| Approach | Effect |
|---|---|
| Eager-load only edges callers need | Fewer nested schemas in read/list responses. See Eager Loading. |
DisableEagerLoadedEndpoints | Drops redundant edge paths when the edge is already on the parent response. |
DisableEdgeEndpoints or per-edge WithEdgeEndpoint | Fewer paths and operations. |
Narrow WithFilter usage | Fewer query parameters per list endpoint. |
WithExcludeOperations | Drop unused CRUD operations for internal entities. |
Leave AddEdgesToTags disabled | Less tag noise on operations (edge endpoints still get both tags). |
DisablePagination on small, bounded lists | Simpler list response schemas (plain arrays instead of paged wrappers). |
Enum hoisting and shared pagination/error components already reduce duplication; the items above reduce how much API surface is generated in the first place.
field.Enum("type"). NamedValues("System", "SYSTEM", "User", "USER"). Default("USER"). Annotations( entrest.WithExample("USER"), entrest.WithFilter(entrest.FilterGroupEqualExact | entrest.FilterGroupArray), ). Comment("Account kind: SYSTEM for internal actors, USER for normal accounts.")This yields a UserTypeEnum component, $ref usage on create/update/read schemas and filter parameters, default and example on the enum, and the comment as description.
- Codegen Configuration — generation-time options that shape the spec
- Annotation Reference — per-field and per-schema overrides
- Extending the Spec — base spec, hooks, security schemes
- Runtime Configuration — runtime behavior that matches documented errors

