Skip to content

Your first request

Complete installation, then create these files in the fetcher-first-request directory.

Create server.mjs

mjs
/*
 * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { createServer } from 'node:http';

const server = createServer((request, response) => {
  const found = request.method === 'GET' && request.url === '/users/1';
  response.writeHead(found ? 200 : 404, {
    'content-type': 'application/json; charset=utf-8',
  });
  response.end(
    JSON.stringify(found ? { id: 1, name: 'Ada' } : { error: 'Not Found' }),
  );
});

server.listen(8787, '127.0.0.1', () => {
  console.log('Fixture listening on http://127.0.0.1:8787');
});

process.on('SIGINT', () => server.close());

Create client.ts

ts
/*
 * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
  ExchangeError,
  Fetcher,
  HttpStatusValidationError,
  ResultExtractors,
} from '@ahoo-wang/fetcher';

interface User {
  id: number;
  name: string;
}

const client = new Fetcher({ baseURL: 'http://127.0.0.1:8787' });
const user = await client.get<User>(
  '/users/1',
  {},
  { resultExtractor: ResultExtractors.Json },
);

if (user.id !== 1 || user.name !== 'Ada') {
  throw new Error(`Unexpected user: ${JSON.stringify(user)}`);
}

try {
  await client.get('/missing');
  throw new Error('Expected /missing to fail with HTTP 404');
} catch (error) {
  if (
    !(error instanceof ExchangeError) ||
    !(error.cause instanceof HttpStatusValidationError) ||
    error.exchange.response?.status !== 404
  ) {
    throw error;
  }
}

console.log(user.name);

The generic describes the expected TypeScript shape; it does not validate server data. The explicit id and name check provides this example's runtime boundary. ResultExtractors.Json is passed in the third argument to get, where request options belong.

The 404 is exposed as an ExchangeError. Its cause is HttpStatusValidationError, and exchange.response.status retains 404. Transport and parsing failures do not match that branch and are rethrown.

Create tsconfig.json

json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "outDir": "./dist"
  },
  "include": ["client.ts"]
}

Compile and run

Start the deterministic local fixture:

bash
pnpm exec tsc -p tsconfig.json
node server.mjs

In another terminal in the same directory, run:

bash
node dist/client.js

The client verifies both the fixed user and the 404 branch, then prints:

text
Ada

Press Ctrl+C in the server terminal to stop it. Running the client without the server exits non-zero because the transport error is rethrown.

See the repository HTTP example for repository-specific verification commands.

Released under the Apache License 2.0.