A reference implementation of a fully typed API layer shared between a React client and an Express server, using Zod for runtime validation.
-
shared/api.tshasendpoint = { path: string, input: ZodType, output: ZodType }definitions used by server and client. Input and output types are constrained to be JSON serializable at the type level. -
server/api.tshas aroutefunction to create typesafe routes based onendpointobjects. Theregisterfunction is separate to retain control of when routes are declared which is important since express routes are built up in a stateful manner. We don't want side effects in any other file butserver.ts. Usethrow new ApiError(..)to handle errors and do authentication (401) and authorization (403). -
client/api.tshascallApianduseApifunctions that take anendpointand does the correct fetching and type validation.
const getAdminSettings = endpoint(
'/api/get-admin-settings',
z.null(),
z.object({ someData: z.number() }),
)
export const getAdminSettingsRoute = route(getAdminSettings, async (input, req) => {
if (!isAdmin(req)) throw new ApiError(403, 'Forbidden')
return { someData: 7 }
})./dev