DEV Community

Cover image for SolidJS: Consume REST API in an elegant way
Damien Le Dantec for Zenika

Posted on

5 1

SolidJS: Consume REST API in an elegant way

In this brief article, I will show you an elegant way (for me) to consume REST API.

Context

When we consume REST API endpoints, it is often the same.
REST API are strict on HTTP verbs and naming of endpoints.

It is often a GET request to fetch array of items and POST / DELETE / PATCH to respectively create / delete / edit an item.

This article only deals with the following HTTP verbs: GET / POST / DELETE / PATCH. You can easly adapt the code for your case.

What do we want

The goal of this article is to create a hook to easly consume REST API.

We want something like this (example of Quotes REST API management):

const { items, add, remove, edit } = useQuotes();
Enter fullscreen mode Exit fullscreen mode

remove and not delete because this is a reserved keywork in JavaScript

This hook handles the REST API calls AND updates local data!
The fetch is automatically done.

To be able to do it, we use createResource API and Context API.

To facilate the creation of this hook, we create a "Hook contructor" to easily create many types of endpoints.

How is our "Hook constructor" used?

Simply:

useCitations.tsx

import { createRESTApiHook } from "./createRESTApiHook";

export type Quote = {
  id: string;
  text: string;
  title: string;
  author?: string;
  tags: string[];
  numberOfVotes: number;
};

const { Provider, useRESTApi } = createRESTApiHook<Quote>();

export {
  Provider as QuotesProvider,
  useRESTApi as useQuotes
};
Enter fullscreen mode Exit fullscreen mode

Usage of useQuotes:

  const { items, add, remove, edit, refetch } = useQuotes();
Enter fullscreen mode Exit fullscreen mode

Because we use Context:

App.tsx

<QuotesProvider baseURL="http://localhost:8080/quotes">
  // Components using API
</QuotesProvider>
Enter fullscreen mode Exit fullscreen mode

And that's all!

How is our "Hook contructor" created?

Simply:

createRESTApiHook.tsx

import {
  createContext,
  createResource,
  Resource,
  JSXElement,
  useContext,
} from "solid-js";

interface ProviderProps {
  baseURL: string;
  children: JSXElement;
}


export function createRESTApiHook<T>() {
  interface ContextValue {
    items: Resource<T[]>;
    add: (item: Omit<T, "id">) => Promise<boolean>;
    edit: (itemId: string, item: Partial<T>) => Promise<boolean>;
    remove: (itemId: string) => Promise<boolean>;
    refetch: () => void;
  }

  const context = createContext<ContextValue>();


  function Provider(props: ProviderProps) {
    const [items, { mutate, refetch }] =
      createResource<T[]>(fetchItems);

    async function fetchItems() {
      const res = await fetch(props.baseURL);
      return res.json();
    }

    async function add(item: Omit<T, "id">) {
      try {
        const res = await fetch(props.baseURL, {
          method: "POST",
          body: JSON.stringify(item),
          headers: {
            "Content-Type": "application/json",
          },
        });
        const addedItem = await res.json();
        mutate((prev) => (prev ? [...prev, addedItem] : [addedItem]));
        return true;
      } catch (err) {
        console.error(err);
        return false;
      }
    }

    async function edit(itemId: string, item: Partial<T>) {
      try {
        const res = await fetch(`${props.baseURL}/${itemId}`, {
          method: "PATCH",
          body: JSON.stringify(item),
          headers: {
            "Content-Type": "application/json",
          },
        });
        const updatedItem = await res.json();
        mutate((prev) =>
          prev?.map((elt: any) => (elt.id === itemId ? updatedItem : elt))
        );
        return true;
      } catch (err) {
        console.error(err);
        return false;
      }
    }

    async function remove(itemId: string) {
      try {
        await fetch(`${props.baseURL}/${itemId}`, {
          method: "DELETE",
        });
        mutate((prev) => prev?.filter((elt: any) => elt.id !== itemId));
        return true;
      } catch (err) {
        console.error(err);
        return false;
      }
    }

    const value: ContextValue = {
      items,
      add,
      edit,
      remove,
      refetch,
    };

    return <context.Provider value={value}>{props.children}</context.Provider>;
  }

  function useRESTApi() {
    const ctx = useContext(context);

    if (!ctx) {
      throw new Error("useRESTApi must be used within a RestAPIProvider");
    }

    return ctx;
  }

  return {
    Provider,
    useRESTApi
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

You can already put this "Hook contructor" in your application.

Of course, this function can be improved or adapted.
For example, if the ID field of your items is not id, you can put something else or be adaptable via a props.

Thanks for reading!

API Trace View

How I Cut 22.3 Seconds Off an API Call with Sentry đź‘€

Struggling with slow API calls? Dan Mindru walks through how he used Sentry's new Trace View feature to shave off 22.3 seconds from an API call.

Get a practical walkthrough of how to identify bottlenecks, split tasks into multiple parallel tasks, identify slow AI model calls, and more.

Read more →

Top comments (0)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

đź‘‹ Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay