API Schemas

How to design schemas for your APIs

APIs in Encore are regular functions with request and response data types. These types are structs (or pointers to structs) with optional field tags, which Encore uses to encode API requests to HTTP messages. The same struct can be used for requests and responses, but the query tag is ignored when generating responses.

All tags except json are ignored for nested tags, which means you can only define header and query parameters for root level fields.

For example, this struct:

type NestedRequestResponse struct { Header string `header:"X-Header"`// this field will be read from the http header Query string `query:"query"`// this field will be read from the query string Body1 string `json:"body1"` Nested struct { Header2 string `header:"X-Header2"`// this field will be read from the body Query2 string `query:"query2"`// this field will be read from the body Body2 string `json:"body2"` } `json:"nested"` }

Would be unmarshalled from this request:

POST /example?query=a%20query HTTP/1.1 Content-Type: application/json X-Header: A header { "body1": "a body", "nested": { "Header2": "not a header", "Query2": "not a query", "body2": "a nested body" } }

And marshalled to this response:

HTTP/1.1 200 OK Content-Type: application/json X-Header: A header { "Query": "not a query", "body1": "a body", "nested": { "Header2": "not a header", "Query2": "not a query", "body2": "a nested body" } }

Path parameters

Path parameters are specified by the path field in the //encore:api annotation. To specify a placeholder variable, use :name and add a function parameter with the same name to the function signature. Encore parses the incoming request URL and makes sure it matches the type of the parameter. The last segment of the path can be parsed as a wildcard parameter by using *name with a matching function parameter.

// GetBlogPost retrieves a blog post by id. //encore:api public method=GET path=/blog/:id/*path func GetBlogPost(ctx context.Context, id int, path string) (*BlogPost, error) { // Use id to query database... }

Fallback routes

Encore supports defining fallback routes that will be called if no other endpoint matches the request, using the syntax path=/!fallback.

This is often useful when migrating an existing backend service over to Encore, as it allows you to gradually migrate endpoints over to Encore while routing the remaining endpoints to the existing HTTP router using a raw endpoint with a fallback route.

For example:

//encore:service type Service struct { oldRouter *gin.Engine // existing HTTP router } // Route all requests to the existing HTTP router if no other endpoint matches. //encore:api public raw path=/!fallback func (s *Service) Fallback(w http.ResponseWriter, req *http.Request) { s.oldRouter.ServeHTTP(w, req) }

Headers

Headers are defined by the header field tag, which can be used in both request and response data types. The tag name is used to translate between the struct field and http headers. In the example below, the Language field of ListBlogPost will be fetched from the Accept-Language HTTP header.

type ListBlogPost struct { Language string `header:"Accept-Language"` Author string // Not a header }

Query parameters

For GET, HEAD and DELETE requests, parameters are read from the query string by default. The query parameter name defaults to the snake-case encoded name of the corresponding struct field (e.g. BlogPost becomes blog_post).

The query field tag can be used to parse a field from the query string for other HTTP methods (e.g. POST) and to override the default parameter name.

Query strings are not supported in HTTP responses and therefore query tags in response types are ignored.

In the example below, the PageLimit field will be read from the limit query parameter, whereas the Author field will be parsed from the query string (as author) only if the method of the request is GET, HEAD or DELETE.

type ListBlogPost struct { PageLimit int `query:"limit"` // always a query parameter Author string // query if GET, HEAD or DELETE, otherwise body parameter }

Body parameters

Encore will default to reading request parameters from the body (as JSON) for all HTTP methods except GET, HEAD or DELETE. The name of the body parameter defaults to the field name, but can be overridden by the json tag. Response fields will be serialized as JSON in the HTTP body unless the header tag is set.

There is no tag to force a field to be read from the body, as some infrastructure entities do not support body content in GET, HEAD or DELETE requests.

type CreateBlogPost struct { Subject string `json:"limit"` // query if GET, HEAD or DELETE, otherwise body parameter Author string // query if GET, HEAD or DELETE, otherwise body parameter }

Supported types

The table below lists the data types supported by each HTTP message location.

TypeHeaderPathQueryBody
boolXXXX
numericXXXX
stringXXXX
time.TimeXXXX
uuid.UUIDXXXX
json.RawMessageXXXX
listXX
structX
mapX
pointerX

Raw endpoints

In some cases you may need to fulfill an API schema that is defined by someone else, for instance when you want to accept webhooks. This often requires you to parse custom HTTP headers and do other low-level things that Encore usually lets you skip.

For these circumstances Encore lets you define raw endpoints. Raw endpoints operate at a lower abstraction level, giving you access to the underlying HTTP request.

Learn more in the raw endpoints documentation.

Sensitive data

Encore's built-in tracing functionality automatically captures request and response payloads to simplify debugging. That's not desirable if a request or response payload contains sensitive data, such as API keys or personally identifiable information (PII).

For those use cases Encore supports marking a field as sensitive using the struct tag encore:"sensitive". Encore's tracing system will automatically redact fields tagged as sensitive. This works for both individual values as well as nested fields.

Note that inputs to auth handlers are automatically marked as sensitive and are always redacted.

Raw endpoints lack a schema, which means there's no way to add a struct tag to mark certain data as sensitive. For this reason Encore supports tagging the whole API endpoint as sensitive by adding sensitive to the //encore:api annotation. This will cause the whole request and response payload to be redacted, including all request and response headers.

Please note

The encore:"sensitive" tag is ignored for local development environments to make development and debugging with the Local Development Dashboard easier.

Example

package blog // service name import ( "time" "encore.dev/types/uuid" ) type Updates struct { Author string `json:"author,omitempty"` PublishTime time.Time `json:"publish_time,omitempty"` } // BatchUpdateParams is the request data for the BatchUpdate endpoint. type BatchUpdateParams struct { Requester string `header:"X-Requester"` RequestTime time.Time `header:"X-Request-Time"` CurrentAuthor string `query:"author"` Updates *Updates `json:"updates"` MySecretKey string `encore:"sensitive"` } // BatchUpdateResponse is the response data for the BatchUpdate endpoint. type BatchUpdateResponse struct { ServedBy string `header:"X-Served-By"` UpdatedIDs []uuid.UUID `json:"updated_ids"` } //encore:api public method=POST path=/section/:sectionID/posts func BatchUpdate(ctx context.Context, sectionID string, params *BatchUpdateParams) (*BatchUpdateResponse, error) { // Update blog posts for section return &BatchUpdateResponse{ServedBy: hostname, UpdatedIDs: ids}, nil }