Defjs

Call one GET and branch on typed 200 vs declared 404.

GET with a declared 404

Declare both success and 404 bodies. Branch on error.code and error.status — you get typed error.data for the declared miss.

See HTTP and Errors for details.

import { createClient, defineRequest, struct, withEndpoint } from "@defjs/core";

const client = createClient(withEndpoint("https://api.example.com"));

const getUser = defineRequest({
  method: "GET",
  path: "/users/:id",
  input: struct.object({ id: struct.number() }),
  builder: (input) => ({ path: { id: input.id } }),
  output: struct.object({ id: struct.number(), name: struct.string() }),
  error: struct.object({ message: struct.string() }),
});

async function main() {
  const [error, user, response] = await getUser(client, { id: 7 });
  if (error?.code === "HTTP_STATUS" && error.status === 404) {
    console.log(error.data.message);
    return;
  }
  if (error) {
    console.error(error.code);
    return;
  }
  console.log(`Loaded ${user.name} from ${response.status}`);
}

void main();

api.example.com is a placeholder. With a server that answers GET /users/7 with 200 and { "id": 7, "name": "Ada" }, the snippet prints:

Loaded Ada from 200

A non-2xx status is HTTP_STATUS. Declaring error gives its body a type; omitting error leaves error.data as undefined and the body unread. If a declared error body cannot be read (wrong media type, invalid JSON, wrong shape), the result is a RES_* fault instead. One error Struct covers every non-2xx status, so use struct.or(...) or a discriminated union when the shapes differ.

Edit this page on GitHub
Released under the MIT License. · Copyright © 2026 MunMunMiao

On this page