arkynChangelogGuides
docs / services / api-service

ApiService

The ApiService is a class that provides a simple and consistent interface for making HTTP requests to REST APIs. It supports all common HTTP methods (GET, POST, PUT, PATCH, DELETE), automatic header management, token-based authentication, and optional debug logging.

Import

ts

import { ApiService } from "@arkyn/server/apiService";
Learn how subpath and root imports differ in How do I use imports.

Constructor

Creates a new ApiService instance with the provided configuration.
  • baseUrl (required): The base URL for all API requests. This will be prepended to all endpoint paths.
  • baseHeaders (optional): Default headers to include in every request. These can be overridden by request-specific headers.
  • baseToken (optional): A default Bearer token for authorization. This can be overridden by request-specific tokens.
  • enableDebug (optional): Enable debug logging for requests. When true, request details will be logged using flushDebugLogs. Default is false.
  • timeoutMs (optional): Default request timeout, in milliseconds, for every request made by this instance. Added in v3.0.12. Can be overridden per request, see timeoutMs under "Request data options" below. Falls back to 10000 (10s) if not set here or per request.

typescript

const api = new ApiService({
baseUrl: "https://api.example.com",
baseHeaders: { "Content-Type": "application/json" },
baseToken: "your-api-token",
enableDebug: true,
timeoutMs: 15000,
});

Methods

All methods return a Promise with the API response containing:
  • success: A boolean indicating whether the request was successful.
  • status: The HTTP status code of the response.
  • message: A message describing the result of the request.
  • response: The parsed response payload (generic type T).
  • cause: Additional error information, if applicable (string | Error | null).
get(endpoint, data?) - Sends a GET request to the specified endpoint.
post(endpoint, data?) - Sends a POST request with an optional body.
put(endpoint, data?) - Sends a PUT request with an optional body.
patch(endpoint, data?) - Sends a PATCH request with an optional body.
delete(endpoint, data?) - Sends a DELETE request with an optional body.

Request data options

For GET requests:
  • headers: Additional headers for this request
  • token: Bearer token (overrides baseToken)
  • urlParams: Values substituted into :paramName placeholders in the endpoint path (not appended as a query string)
  • timeoutMs (optional, added in v3.0.12): Per-request timeout in milliseconds, overrides the instance's timeoutMs for this call only. Defaults to 10000 (10s) if neither this nor the instance default is set.
For POST, PUT, PATCH, DELETE requests:
  • body: Request body (any serializable data)
  • headers: Additional headers for this request
  • token: Bearer token (overrides baseToken)
  • urlParams: Values substituted into :paramName placeholders in the endpoint path (not appended as a query string)
  • timeoutMs (optional, added in v3.0.12): Per-request timeout in milliseconds, overrides the instance's timeoutMs for this call only. Defaults to 10000 (10s) if neither this nor the instance default is set.

Usage example

typescript

import { ApiService } from "@arkyn/server/apiService";
const api = new ApiService({
baseUrl: "https://api.example.com",
baseHeaders: { "Content-Type": "application/json" },
enableDebug: process.env.NODE_ENV === "development",
});
// GET request with a route variable
const user = await api.get("/users/:id", {
urlParams: { id: "42" },
});
// GET request with custom token
const profile = await api.get("/me", {
token: "user-specific-token",
});
// POST request with body
const newUser = await api.post("/users", {
body: { name: "John", email: "john@example.com" },
});
// PUT request
const updatedUser = await api.put("/users/:userId", {
body: { name: "John Doe" },
urlParams: { userId: "123" },
});
// PATCH request
const patchedUser = await api.patch("/users/:userId", {
body: { status: "active" },
urlParams: { userId: "123" },
});
// DELETE request
const deleted = await api.delete("/users/:userId", {
urlParams: { userId: "123" },
});
// Handling responses
if (user.status === 200) {
console.log(user.response); // Response data
} else {
console.log(user.message); // Error message
}

Header priority

Headers are merged in the following order (later values override earlier ones):
  1. baseToken (as Authorization: Bearer {token})
  2. baseHeaders (from constructor)
  3. headers (from request data)
  4. token (from request data, as Authorization: Bearer {token})
Note: Content-Type is always forced to application/json by the client after the headers above are merged, so it cannot be overridden via baseHeaders or per-request headers.

Debug output

When enableDebug is enabled, each request logs the following information:
  • Base URL
  • Endpoint
  • HTTP method and response status
  • Response message
  • Headers (if present)
  • Request body (if present)
Debug logs are output using flushDebugLogs with the "yellow" color scheme.
As of v3.0.9, headers and the request body are passed through parseSensitiveData before being logged, so Authorization and other sensitive fields (Cookie, password, token, apiKey, secret, matched case-insensitively) print as "****" instead of their real value. Non-sensitive data is unaffected.

Request timeouts

Added in v3.0.12. Every request is aborted via AbortController if it doesn't complete within timeoutMs (per-request value, falling back to the instance's timeoutMs, falling back to 10000, 10s). Previously, requests had no timeout at all and could hang indefinitely on a stalled connection.
When a request times out, it resolves (it does not throw) to a distinguishable result instead of the generic network-failure message:

typescript

const result = await api.get("/slow-endpoint", { timeoutMs: 2000 });
if (!result.success && result.status === 504) {
console.log(result.message); // "Request timed out after 2000ms"
}
Any other network failure (DNS failure, connection refused, etc.) still resolves with success: false and the generic "Network error or request failed" message, with no fixed status override.

Route variables

Replaces placeholders in the URL path (e.g., /users/:userId becomes /users/123)
Using route variables with :param syntax is optional, but recommended when integrating with LogService. The log system generates logs based on URLs, so having a consistent base URL with variables as route parameters makes logs more readable and aggregatable.

typescript

// Without route variables - each userId creates a different log entry
await api.get("/users/123");
await api.get("/users/456");
// Logs: GET /users/123, GET /users/456
// With route variables - logs are grouped by the base pattern
await api.get("/users/:userId", { urlParams: { userId: "123" } });
await api.get("/users/:userId", { urlParams: { userId: "456" } });
// Logs: GET /users/:userId (with userId as metadata)

Notes

The service automatically prepends the baseUrl to all endpoint paths, so endpoints should start with /.
All methods are async and should be awaited. The response object always contains success, status, message, response, and cause properties.
Related in Services
On this page
    arkyn