This project is an experimental simple API, which exposes a server and client package.
- Node.js 18
The server package is built on top of express
, and leverages zod
for declaring types for validation.
cd packages/server
npm run start
The client package is built by generating Typescript types from the server using zod-to-ts
, and leverages the type system to expose a factory that creates functions for making strongly typed calls into the server. The resulting function will optionally take parameters based on how the client API is defined:
- The route to the API
- The method to invoke on the API (GET, POST, etc.)
- For methods that define a body - The content type of the request
- For methods that define a body, headers or query arguments - An object that includes each of those as fields
- An optional remaining argument for any additional
RequestInit
arguments excluding those that have already been specified.
Creating an API requires two things:
- A generic argument, which defines the API schema
- A parameter, which defines the root URL of the API
import { api } from './api';
export type HelloApi =
{
'hello': {
get: {
query: { name: string },
ok: { message: string },
err: { message: string },
},
},
}
const rootUrl = 'http://localhost:3000';
const hello = api<HelloApi>(rootUrl, { });
When invoking the API the return result includes a tuple with the following entries:
- The partial type expected when the server responds with an ok status code
- The partial type expected when the server responds with a non-ok status code
- The raw response (with the body already read)
const [ok, err, res] = await hello('hello', 'get', {
query: { name: 'get query' },
});
if (ok) {
console.log('great job!', ok.message);
} else if (err) {
console.error('uh oh!', err.message);
}
console.debug('http status', res.status);
Perform type checking against ok
and err
to determine what the server responded with. The API will never return both a non-null ok
and err
.
APIs include a raw
function, which provides typed access to the parameters, but does not attempt to perform any additional processing on top of the Response
.
const res = await hello.raw('hello', 'get', {
query: { name: 'get query' },
});
console.log('http body', await res.text());
console.debug('http status', res.status);
cd packages/client
npm run test