How to convert your REST calls to return GraphQL responses using Apollo - NextJs - React

Ever wondered if the REST API's in your project could be returned as GraphQL responses. Well when I ventured into the question at first I was as lost as a rabbit in a hole. However as they say there is light at the end of the tunnel - that light was Apollo.

Without further adieu - lets get going to work.

Objective

We will hit the google book API - which gives a rest response - however using Apollo - we will convert the response to GraphQL.

Basically a search box which will hit the REST API but return the response as a Graph.

Tech Stack : NextJs+ Apollo (apollo-link-rest) + Formik (Not necessary) to achieve this.

Project Setup

Create a new project using NextJs

npx create-next-app your-project-name

We will be using formik cause this project is a part of a larger cause I am trying to build.

 npm install formik --save

Also this would be a nice time to include the apollo/client and link-rest dependencies:

npm install --save @apollo/client apollo-link-rest graphql qs

Well I would be using fontawesome - hence adding those dependencies too:

npm i --save @fortawesome/fontawesome-svg-core
npm install --save @fortawesome/free-solid-svg-icons
npm install --save @fortawesome/react-fontawesome

With that out of the way let's get into coding.

Creating the Basic Setup

Creating an apollo-client

GraphQL is a single endpoint API - hence we need to define the client with the URI and the caching mechanism you need to implement - In this case we will be using InMemoryCache.

Create a file called - apollo-client.js in the root folder and the code goes as follows:

import { ApolloClient, InMemoryCache } from "@apollo/client";
import { RestLink } from "apollo-link-rest";

const restLink = new RestLink({
  uri: "https://www.googleapis.com/books/v1/volumes",
});

const client = new ApolloClient({
  link: restLink,
  cache: new InMemoryCache(),
});

export default client;

Once that is done - Go to the pages/_app.js and wrap our base component with this provider.

import { ApolloProvider } from "@apollo/client";
import client from "../apollo-client";
import "../styles/globals.css";

function MyApp({ Component, pageProps }) {
  return (
    <ApolloProvider client={client}>
      <Component {...pageProps} />
    </ApolloProvider>
  );
}

export default MyApp;

Creating Components

Create a folder called components which will host 2 components mainly:

  • Book -> Component for rendering the book as a card.
  • Search -> A form to search the books by name / author.

Create a file as components/book/main-book.jsx. A simple component to render each book by the response which we receive from GQL.

import styles from "./main-book.module.css";

const MainBook = (props) => {
  let book = props.book;
  return (
    <>
      <div className={styles.card}>
        <div className={styles.card_top}>
          <img src={book.volumeInfo.imageLinks?.thumbnail} alt="Image" />
        </div>
        <div className={styles.card_body}>
          <div className={styles.title}>
            <p>{book.volumeInfo.title} </p>
          </div>
          {book.volumeInfo.pageCount && (
            <p className={styles.page_count}>
              <span>{book.volumeInfo.pageCount} Pages </span>
            </p>
          )}
          <div className={styles.card_info}>
            {book.volumeInfo.authors && (
              <div>
                <p className={styles.author_title}>Authors</p>
                <div className={styles.author_names}>
                  {book.volumeInfo.authors.map((author, index) => {
                    return <p key={index}>{author}</p>;
                  })}
                </div>
              </div>
            )}
          </div>
        </div>
      </div>
    </>
  );
};

export default MainBook;

Add the corresponding css class under components/book/main-book.module.css. Using CSS modules here - will be transitioning to styled components eventually.

.card {
  width: 300px;
  height: 410px;
  border: 4px solid  #ffb2b2;
  float: left;
  border-radius: 20px;
  padding: 10px;
  margin: 10px;
  background-color: white;
}

.card:hover {
  box-shadow: 0 10px 6px -6px #ff253a;
}

.card .card_top {
  padding: 10px 20px 0 20px;

  height: 60%;
  display: flex;
  justify-content: center;
}

.card_top img {
  width: 100%;
  border-radius: 20px;
  box-shadow: 0 10px 16px -6px #ff253a;
}

.card .card_body {
  height: 40%;
}

.card_body .title {
  overflow: hidden;
  text-overflow: ellipsis;
  word-wrap: break-word;
  max-height: 3.4em;
  text-align: center;
  font-size: medium;
  font-weight: bold;
}

.card_body .card_info {
  width: auto;
  word-wrap: break-word;
  display: flex;
  justify-content: space-between;
}

.card_body .page_count {
  color: darkgray;
  font-weight: bolder;
  text-align: center;
  margin-top: -20px;
  font-size: x-small;
}

.card_info .author_title {
  font-weight: bold;
  font-size: small;
  text-decoration: underline;
  color: #348cb5;
}

.card_info .author_names {
  height: 50px;
  margin-top: -10px;
  font-size: small;
  font-style: oblique;
  color: dimgrey;
  font-weight: 600;
  overflow-y: scroll;
}

.author_names p {
  margin: 0;
}

Create a file as components/search/Search.jsx with the below code. A simple form to get the search value and onSubmit calls the function passed in props by the parent.

import { faSearch } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Field, Form, Formik } from "formik";
import styles from "./Search.module.css";

const Search = (props) => {
  return (
    <>
      <Formik
        initialValues={{
          searchText: "",
        }}
        onSubmit={props.search}
      >
        <Form>
          <div className={styles.wrapper}>
            <div className={styles.search_box}>
              <button type="submit" className={styles.search_btn}>
                <FontAwesomeIcon icon={faSearch} />
              </button>
              <Field
                className={styles.input_search}
                placeholder="Search Book or Author"
                id="searchText"
                name="searchText"
                placeholder="Search Books, Authors"
              />
            </div>
          </div>
        </Form>
      </Formik>
    </>
  );
};

export default Search;

Add the corresponding css class under components/search/Search.module.css. Using CSS modules here - will be transitioning to styled components eventually.

.wrapper {
  display: flex;
  justify-content: center;
  align-items: center;
}

.search_box {
  background: #f5e5e6;
  position: relative;
  padding: 15px;
  border-radius: 50px;
  display: flex;
  flex-direction: row-reverse;
  box-shadow: 0 10px 16px -6px #ff253a;
}

.search_box .search_btn {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: #ff253a;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #fff;
  margin-left: 15px;
  cursor: pointer;
}

.search_box .input_search {
  outline: none;
  border: 0;
  background: #ff253a;
  color: #fff;
  padding: 15px 20px;
  width: 500px;
  height: 20px;
  border-radius: 50px;
}

.search_box ::placeholder {
  color: #fff;
}

.search_box ::-webkit-input-placeholder {
  color: #fff;
}

Creating the magical API.

Well this is the main part where we will be making a call to the google books api URI with the search path through the gql client query.

Create a file as pages/api/[searchText].js . The [] is so that we enable dynamic routing with the text. This will have a default handler function which the NextJS looks for.

import gql from "graphql-tag";
import client from "../../apollo-client";

async function handler(req, res) {
  const {searchText} =  req.query;
  const { data } = await client.query({
    query: gql`
      query search {
        books(q: ${searchText}) @rest(type: "BooksPayload", path: "?{args}") {
          totalItems
          items @type(name: "Book") {
            id
            selfLink
            volumeInfo @type(name: "VolumeInfo") {
              authors
              title
              pageCount
              imageLinks @type(name: "ImageLinks"){
                thumbnail
              }
            }
          }
        }
      }
    `,
  });
  res.status(200).json(data.books);
}

export default handler;

If you look at it closely the we pass in the parameters to the books query and the path with what the google books api requires.

We are trying to replicate this URL - googleapis.com/books/v1/volumes?q=trending with the searchParameter as trending in this case.

Put it all together in index.js

The final piece of the puzzle is to put in the pages/index.js which is the entry point to define our components.

A searchHandler which will pass our search results to the api/[searchText].js handler.

Most importantly we are fetching exactly what we need - nothing less or nothing more - No more converting it to the format we need. Phew !!!

import { useState } from "react";
import MainBook from "../components/book/main-book";
import Search from "../components/search/Search";
import styles from "./index.module.css";

export const Home = (props) => {
  const [books, setBooks] = useState([]);

  const searchHandler = async (search) => {
    const response = await fetch("/api/" + search.searchText);
    const searchResult = await response.json();
    setBooks(searchResult.items);
  };

  return (
    <div>
      <div className={styles.search_container}>
        <Search search={searchHandler} />
      </div>
      <div className={styles.book_container}>
        {books.map((book) => {
          return <MainBook key={book.id} book={book} />;
        })}
      </div>
    </div>
  );
};

export default Home;

Add the corresponding css class under pages/index.module.css. Using CSS modules here - will be transitioning to styled components eventually.

.book_container {
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  width: 100%;
  margin: 0 0 0 50px;
}

.search_container {
  text-align: center;
  margin: 120px 120px 20px 120px;
}

Code Structure

The final Code structure should look something like this:

image.png

Well with all that in place - let's look at the final reveal.

Outcome

Well the final piece is to run the code - duh!!! - The command - npm run dev and the magic should happen right in front of your eyes.

If you search for summer - it should provide with all the books returned by google books REST API which is converted to GQL response. Pretty cool I say.

Screen Shot 2021-06-09 at 8.19.25 PM.png

Conclusion

Well this is a just to get you up and going. The apollo-link-rest is a wonderful library which is probably a gift for all of us who would want to covert the rest API responses to GQL.

If you have a GraphQL backend to work with - great - if not - well you have the answer now.

The Apollo documentation is the best place to get your hands dirty.

No Comments Yet