API Schemas
How to design schemas for your APIs
APIs in Encore for TypeScript are async
functions with request and response data types.
These request and response data types are TypeScript interfaces, which Encore uses to encode API requests to HTTP messages.
Encore parses the source code to understand the request and response schema of each endpoint. By default, the data is parsed as a JSON body for incoming requests, and written back as JSON responses.
This can be customized on a per-field basis, allowing individual fields to be parsed from query strings and HTTP headers with ease.
For example:
import { api, Header, Query } from "encore.dev/api";
interface Data {
header: Header<"X-Header">; // this field will be read from the http header
query: Query<string>; // this will be parsed from the '?query=...' parameter in the request url
body: string; // this will be sent as part of the JSON body
// These will also sent as part of the JSON body:
nested: {
body2: string;
header2: Header<"X-Other-Header">; // Header has no effect inside nested fields
query2: Query<string>; // Query has no effect inside nested fields
};
}
// A simple API endpoint that echoes the data back.
export const echo = api(
{ method: "POST", path: "/echo" },
async (params: Data): Promise<Data> => {
return params; // echo the data back
},
);
This API endpoint expects incoming requests to look like this:
POST /echo?query=hello HTTP/1.1 Content-Type: application/json X-Header: this is a header { "body": "a body", "nested": { "body2": "nested body field", "header2": "not a header", "query2": "not a query string" } }
The Encore runtime will parse this request, constructing a JavaScript object matching the TypeScript type definition
for the Data
type. The endpoint handler would receive this as a JavaScript object that looks like this:
{
header: "this is a header",
query: "hello",
body: "a body",
nested: {
body2: "nested body field",
header2: "not a header",
query2: "not a query string"
}
}
And when the echo
endpoint returns this object back to Encore, Encore will serialize the request back to HTTP.
For HTTP responses the Query<string>
type is considered to be part of the JSON response body, since query strings
only make sense for incoming requests.
This data would be serialized as a HTTP response that looks like this:
HTTP/1.1 200 OK Content-Type: application/json X-Header: this is a header { "query": "hello", "body": "a body", "nested": { "body2": "nested body" "header2": "not a header", "query2": "not a query" } }
Path parameters
Path parameters are specified by the path
field in the API Options in api
call.
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.
Each path parameter (whether a single segment like :name
or a wildcard parameter like *name
) must have
a matching field in the request data type.
For example:
// Retrieves a blog post by its id.
export const getBlogPost = api(
{method: "GET", path: "/blog/:id/*path"},
async (params: {id: number; path: string}): Promise<BlogPost> {
// Use id and path to query database...
}
)
Headers
Headers are defined by setting the field type to Header<"Name-Of-Header">
. It can be used in both request and response data types.
In the example below, the Language
field of ListBlogPost
will be fetched from the
Accept-Language
HTTP header.
import { Header } from "encore.dev/api";
interface ListBlogPost {
language: Header<"Accept-Language">; // parsed from header
author: string; // not a header
}
Query parameters
For GET
, HEAD
and DELETE
requests, parameters are read from the query string by default, since those HTTP methods
do not support request bodies.
For other HTTP methods (that do support request bodies), parameters are by default read from the HTTP request body as JSON.
In those cases, the Query
type can be used to specify that a field should be parsed from the query string instead.
Query strings are not supported in HTTP responses, and are treated as being part of the HTTP response body in JSON.
In the example below, the limit
field will be read from the limit
query parameter for all HTTP methods,
whereas the author
field will be parsed from the query string only if the method of
the request is GET
, HEAD
or DELETE
(and otherwise from the HTTP request body as JSON).
interface ListBlogPost {
limit: Query<number>; // 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 is the field name.
Response fields will be serialized as JSON in the HTTP body unless the Header
type is used to override it.
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.