Core Features
CallApi offers a range of powerful features that simplify API calls and error handling, making it easier to work with APIs in your projects. Here's a detailed look at some of the key features CallApi provides:
✔️ Easy Error Handling
CallApi simplifies error management by returning an error
object that captures both HTTP errors (errors from the server's response) and JavaScript errors (e.g., TypeError, SyntaxError). This unified structure helps you handle errors in a consistent way.
The error
object contains the following properties:
name
: The type of error (e.g.,'HTTPError'
,'TypeError'
,'SyntsaxError'
).message
: A brief description of what went wrong.errorData
: The detailed error information, which can either be:- The parsed response from the API if it's an HTTP error.
- The original JavaScript error object for non-HTTP errors.
For HTTP errors, the name
will be 'HTTPError'
, and errorData
will contain the API's error response (which could be an object or a string, depending on the server).
const { data, error } = await callApi("some-url");
console.log(error.name); // 'HTTPError'
console.log(error.message); // '404 Not Found'
console.log(error.errorData); // { error: "Resource not found" }
For non-HTTP errors (like TypeError
or SyntaxError
), name
will reflect the JavaScript error type, and errorData
will reference the corresponding JavaScript Error
object.
const { data, error } = await callApi("some-url");
console.log(error.name); // 'TypeError'
console.log(error.message); // 'Failed to fetch'
console.log(error.errorData); // The original TypeError object
This structure makes it easy to identify and respond to different types of errors during API calls.
✔️ Automatic Request Cancellation
CallApi automatically prevents race conditions by managing API requests for you.
Whenever you make multiple requests to the same URL with the same options, CallApi ensures only the most recent request is processed. This avoids issues like redundant requests or conflicting data responses when multiple calls are made quickly in succession.
How It Works
- When a request is initiated, CallApi checks if another request to the same URL with the same options is still in progress.
- If a previous request exists, it's automatically cancelled.
- The latest request is then sent, ensuring only it gets processed.
- This cycle continues, ensuring no race conditions occur.
This feature is useful in scenarios like search inputs or form submissions where multiple requests could be triggered by user interactions.
Why This is Useful
- Automatic Cancellation: If multiple requests to the same URL happen in quick succession, CallApi cancels any previous requests, ensuring only the latest one is processed.
- Eliminates Race Conditions: This is especially useful when rapid API calls occur, such as during user input in search fields or multiple button clicks.
- Seamless with React Hooks: This feature is perfect for React hooks like
useEffect
, where component updates might trigger frequent API requests. - Configurable: If you want to manage your requests differently, simply disable this feature by setting
{ cancelRedundantRequests: false }
in your options. - Manual Control: For even more control, you can manually cancel requests using
AbortController
, just like with the native Fetch API.
Manually Cancelling Requests with AbortController
You can cancel a request manually using the AbortController
. Here's an example:
const controller = new AbortController();
callApi("some-url", { signal: controller.signal });
// Later, if you need to cancel the request
controller.abort();
✔️ Authorization Headers
CallApi makes it simple to generate Authorization headers by using an auth
property. If you pass in a string, typically used for tokens, CallApi will automatically generate a Bearer Authorization header. When you pass an object, you have the flexibility to choose between two options: using the bearer
property to generate a Bearer Auth header, or using the token
property to generate a Token Auth header.
For example, if you pass a string:
callApi("some-url", { auth: "token12345" });
This is equivalent to writing the following with Fetch:
fetch("some-url", {
headers: { Authorization: `Bearer token12345` },
});
Alternatively, if you pass an object, you can specify the type of authorization. To generate a Bearer Auth header, use:
callApi("some-url", { auth: { bearer: "token12345" } });
Or, for Token Auth, use:
callApi("some-url", { auth: { token: "token12345" } });
These are equivalent to the following Fetch requests:
// For Bearer Auth
fetch("some-url", {
headers: { Authorization: `Bearer token12345` },
});
// For Token Auth
fetch("some-url", {
headers: { Authorization: `Token token12345` },
});
By simplifying the process of handling authorization headers, CallApi ensures that your requests are authenticated with minimal effort.
✔️ Handling the Response Format
CallApi
supports all response types offered by the fetch API, such as json
, text
, blob
, formData
, etc. This means you don't need to handle response.json() manually
, response.text()
, response.formData()
, etc.
The responseType
option allows you to configure your preferred response type. The default type is json
.
// Json (default)
const { data } = await callApi("url", { responseType: "json" });
// Text
const { data } = await callApi("url", { responseType: "text" });
// Blob, etc
const { data } = await callApi("url", { responseType: "blob" });
// Doing this in Fetch would imply:
const response = await fetch("some-url");
const data = await response.json(); // Or response.text() or response.blob() etc
This simplifies fetching data from responses, allowing you to specify the format you want directly.
Custom response parser
Suppose you want to parse a response with a custom function other than the default JSON.parse
. In that case, you can pass a custom parser function to the responseParser
option.
const { data, error } = await callApi("url", {
responseParser: customResponseParser,
});
Or even better, provide it as a base option for callApi
.
const callAnotherApi = callApi.create({
responseParser: customResponseParser,
});
Custom body serializer
You could also provide a custom serializer/stringifier, other than the default JSON.stringify
, for objects passed to the request body via the bodySerializer
option.
const callAnotherApi = callApi.create({
bodySerializer: customBodySerializer,
});
These options give you flexibility in handling both responses and request bodies in the way that best suits your needs.
✔️ Content-Type Generation
CallApi
takes care of setting the Content-Type
for you based on the body content. Here's how it works:
-
Automatic Content-Type Setting:
CallApi
automatically sets theContent-Type
depending on your body data. Data types eligible for this automatic setting include: -
Object
-
Query Strings
-
FormData
-
Object/JSON Handling: If you pass in an object as the request body,
CallApi
will setContent-Type
toapplication/json
for you. It will also handleJSON.stringify
process for you (although you can always pass in a custom stringifier/serializer if you want), so you don't have to do it yourself.
callApi.post("some-url", {
body: { message: "Good game" },
});
// The above request is equivalent to this
fetch("some-url", {
method: "post",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: "Good game" }),
});
-
Query String Handling: If you pass in a query string as the request body,
CallApi
will ensure theContent-Type
is set toapplication/x-www-form-urlencoded
for you.CallApi
also provides atoQueryString
utility that helps you convert objects to query strings, for extra convenience.
import { toQueryString } from "@zayne-labs/callapi/utils";
callApi("some-url", {
method: "POST",
body: toQueryString({ message: "Good game" }),
});
// The above request is equivalent to this
fetch("some-url", {
method: "post",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: "message=Good%20game",
});
- FormData Handling: If you pass in a
FormData
object as the request body,callApi
will let the native fetch function handle theContent-Type
. Generally, this will bemultipart/form-data
with the defaultboundary
.
const data = new FormData(formElement);
callApi("some-url", { body: data });
✔️ Validator Function
CallApi
also provides a responseValidator
option, allowing you to pass in a function that validates the data returned from the server.
A good use case for this would be to use a library like Zod and pass its schema parse
or safeParse
function to validate the data.
If your validator function throws an error and the throwOnError
option is set to true
, you must check and handle the errors in a catch
block. However, if throwOnError
is set to false
(the default), the error object will be returned as usual.
const callMainApi = callApi.create({
responseValidator: zodSchema.parse, // or zodSchema.safeParse or any other validator you wish to use
});
This feature helps ensure the data you receive meets your validation criteria, making error handling more robust and predictable.
✔️ Query search params
You can add a query object as an option, and callApi
will automatically create a query string for you.
callApi("some-url", {
query: {
param1: "value1",
param2: "to encode",
},
});
// The above request can be written in Fetch like this:
fetch("some-url?param1=value1¶m2=to%20encode");