Skip to content

Api

Centralized API handling system with built-in caching and configurable request behavior.

Features

All request methods are centrally defined.

  • centralized HTTP methods
  • single base URL setup
  • built-in GET caching
  • cache invalidation helpers
  • supports custom request logic and transformation
  • conditional behavior based on request metadata
  • auth handling

Supported methods:

  • get, post, put, delete
  • patch, head, options

Api.setContext()

Define how requests should execute internally.

Usually called once at app startup.

Example

jsx
import { Api } from "..";

Api.setContext({
  get: async (url, config) => {
    // config is queryConfig
    const data = await axios.get(`${BASE_URL}/${url}`, {
      headers: config.headers,
    });

    return data;
  },

  post: async (url, data, config) => {
    // ...
  },

  // other methods...
});

Anything passed through queryConfig becomes available inside the context.

Example:

jsx
config.headers;
config.myData;

Api.get()

Perform GET requests with optional caching.

jsx
Api.get(url, config?)

Example

jsx
const data = await Api.get("/users", {
  queryConfig: {
    headers: {
      token: "123",
    },

    myData: {
      role: "admin",
    },
  },

  cache: 45,
});

Config Options

OptionDescription
queryConfigCustom data passed into request context
cacheCache duration in seconds
cacheKeyCustom cache identifier

GET Cache

Caching is available only for GET requests.

Default cache duration:

jsx
0;

meaning no caching.

Global Cache Duration

jsx
Api.setDefaultCacheDuration(60);

All GET requests now cache for 60s.

Example

jsx
Api.get("/users");

If called again within the cache duration, cached data is returned instead of making another network request.

After expiration, a fresh request is made automatically.

Per-request Cache

You can override cache duration per request.

jsx
Api.get("/live-score", {
  cache: 0,
});

Useful for real-time endpoints.

Cache Keys

Cache is stored using:

txt
url -> response

Example:

jsx
Api.get("/users");

stored as:

txt
'/users': response

Cache Collision Problem

These two requests are different:

jsx
Api.get("/users");

Api.get("/users", {
  queryConfig: {
    headers: {
      role: "admin",
    },
  },
});

But both share the same cache key:

txt
/users

which can override previous cache.

Solution: cacheKey

jsx
Api.get("/users");

Api.get("/users", {
  queryConfig: {
    headers: {
      role: "admin",
    },
  },

  cacheKey: "users-by-role",
});

Stored internally as:

txt
/users::users-by-role

Cache Invalidation

Stale Data Problem

Example:

jsx
await Api.post("/addUser", {
  name: "John",
});

Then:

jsx
Api.get("/users");

may still return old cached data.

invalidateCache()

Remove specific GET cache entries manually.

jsx
Api.invalidateCache(
  url,
  cacheKey? // pass if present in GET call
);

Example

jsx
await Api.post("/addUser", {
  name: "John",
});

Api.invalidateCache("/users");

Now the next GET request fetches fresh data from the network.

clearCache()

Clear all stored GET cache.

jsx
Api.clearCache();

Component Usage

Since components run only once, APIs that do not depend on reactive values can be called directly without wrapping inside effects.

Example

jsx
function Posts() {
  const posts = state(list([]));

  const getPosts = async () => {
    const data = await Api.get("/posts");

    posts.set(data);
  };

  getPosts();

  return (
    <div>
      <For each={posts()}>{(post) => <p>{post.title}</p>}</For>
    </div>
  );
}

When to use outside setEffect()

If the API does not depend on changing reactive values, it can run directly inside the component body.

Since components execute only once, the request will not repeat on state updates.

Call Api in setEffect when it depends on some state.

Example: when user change filter, posts should re-fetched which is done using setEffect.

jsx
function Posts() {
  const posts = state(list([]));
  const filters = state({ sort: "ASC" });

  const getPosts = async () => {
    const data = await Api.get("/posts", {
      queryConfig: { filters: filters() },
    });

    posts.set(data);
  };

  setEffect(() => {
    getPosts();
  }, [filters]);

  return (
    <div>
      <button onClick={() => filters.set((f) => ({ ...f, sort: "DESC" }))}>
        Change order of Posts
      </button>
      <For each={posts()}>{(post) => <p>{post.title}</p>}</For>
    </div>
  );
}

Released under the MIT License.