Helper function for defining an individual HTTP route. It accepts an object with 4 properties.
The route's path along with possible path variables. Variables should be surrounded with
brackets like {name}
. They will be passed to the request codec under the params
object.
Example route with no path params:
httpRoute({
path: '/example',
method: 'GET',
request: httpRequest({
params: {
// Nothing in here, in fact `params` can just be undefined
},
}),
response: {
200: t.string,
},
});
Example with parameters:
httpRoute({
path: '/example/{id}',
method: 'GET',
request: httpRequest({
params: {
id: NumberFromString,
},
}),
response: {
200: t.string,
},
});
The HTTP method for this route. Currently one of GET, POST, PUT, or DELETE. Others can be added in the future.
The codec used to decode incoming HTTP requests on the server side, and encode outgoing
ones on the client side. The primary intended usage here is to combine it with the
httpRequest
codec builder function.
const Route = httpRoute({
path: '/example/{id}',
method: 'GET',
request: httpRequest({
params: {
id: NumberFromString,
},
}),
response: {
200: t.string,
},
});
// Due to property flattening, the decoded type of the above request codec is this
type RequestProps = {
id: number;
};
// Now imagine I have some api client using the above route definition. `superagent-wrapper`
// can be used to create a client like this for a whole group of routes in one line.
const routeApiClient: (props: RequestProps) => Promise<string>;
// The parameters passed to the client are typechecked and also abstracted such that the caller does
// not need to know which parameters go in the path/query/body/etc. In addition, any necessary type
// conversions happen automatically
const response: string = await routeApiClient({ id: 1337 });
Declares the potential responses that a route may return along with the codec associated to each response. Response keys correspond to HTTP status codes. Incoming responses are assumed to be JSON.
const Route = httpRoute({
path: '/example',
method: 'GET',
request: httpRequest({}),
response: {
200: t.type({
foo: t.string,
}),
404: t.type({
message: t.string,
}),
400: t.type({
message: t.string,
}),
},
});
In some cases, the httpRequest
function isn't expressive enough for a particular route
on its own. It is possible to combine it with other io-ts
functions to work around
some of these issues until better support is added. The request
property passed to
httpRoute
only needs to conform to a certain output type, defined as
GenericHttpRequest
:
type GenericHttpRequest = {
params: {
[K: string]: string;
};
query: {
[K: string]: string | string[];
};
headers: {
[K: string]: string;
};
body?: Json;
};
Any codec that outputs an object matching that type is accepted. Here are some examples of known limitations and their workarounds.
Currently httpRequest
assumes a request body is an object with properties that can be
flattened into the decoded object. If this isn't the case, for example if request just
wants a string body (though still a JSON string rather than plain text), then
httpRequest
can be combined with another codec that has the desired type.
const StringBodyRoute = httpRoute({
path: '/example/{id}',
method: 'POST',
request: t.intersection([
httpRequest({
params: { id: t.string },
}),
t.type({
body: t.string,
}),
]),
response: {
200: t.string,
},
});
// Decoded type
// t.TypeOf<typeof StringBodyRoute['request']>
type DecodedType = {
id: string;
body: string;
};
This results in the decoded type having a body
property breaking the desired
abstraction, but it works.
This can be supported with a union of multiple httpRequests
const UnionRoute = httpRoute({
path: '/example',
method: 'GET',
request: t.union([
httpRequest({
query: {
type: t.literal('ping'),
},
}),
httpRequest({
query: {
type: t.literal('message'),
message: t.string,
},
}),
]),
response: {
200: string,
},
});
// Decoded type
// t.TypeOf<typeof UnionRoute['request']>
type DecodedType = { type: 'ping' } | { type: 'message'; message: string };