Fetching data using a GraphQL Apollo Server in a NextJS App

I have written an API that successfully queries a FaunaDB GraphQL instance. However I am receiving a 400 Bad Request for "/api/graphql" for fetching the data that should be rendered in-component. Here’s my code:

import useSWR from 'swr'

const Query = `
  books {
    title
  }
`

export default function Home() {

  const fetcher = async () => {
    const response = await fetch('/api/graphql', {
      body: JSON.stringify({ Query }),
      headers: { "Content-type": "application/json" },
      method: "POST"
    });
    const { data } = await response.json();
    return data
  }

  const { data, error } = useSWR([Query], fetcher)

  return (
    <div>

      <div>Books: {data?.books?.title}</div>

    </div>
  )
}

The fetch within the fetcher function is misspecified, it cannot fetch from this location, but I’m not sure what the call should be. The API is in the API folder and the file is titled graphql.js.

Perhaps there is an easier way to fetch GraphQL data? I have searched the internet as extensively as I can.

Are you providing a secret within your Authorization header? The docs on that are here: How to access GraphQL endpoints | Fauna Documentation

This is how you authenticate your request. The secret also determines which database you are accessing, since all Fauna users are accessing a common set of GraphQL endpoints.

1 Like

From your question, it looks like your 400 is coming from your API to your client, and not Fauna to your API. If you can share your API code, we might be able to help you stitch the two together.

If it is a matter of connecting to Fauna in your API, we can help debug that, too, but we will need some more details about your API code. @alexnitta shared good resources for setting up GraphQL calls, and that information is necessary whether you are making requests from the user client or an API.

It is possible to send GraphQL requests straight from your client to Fauna. There are plenty of reasons you might choose to use a server or API functions between your client and Fauna, though.

Hi @ptpaterson @alexnitta . Below is the API for the /api/graphql route:

import Cors from 'micro-cors'
import { gql, ApolloServer } from 'apollo-server-micro'
import { Client, Map, Paginate, Documents, Collection, Lambda, Get } from 'faunadb'

const client = new Client({
    secret: process.env.FAUNA_SECRET,
    domain: "db.fauna.com",
})

export const config = {
    api: {
        bodyParser: false
    }
}

const typeDefs = gql`
    type Book {
        title: String
        author: String
    }

    type Query {
        books: [Book]
    }
`

const resolvers = {
    Query: {
        books: async () => {
            const response = await client.query(
                Map(
                    Paginate(Documents(Collection('Book'))),
                    Lambda((x) => Get(x))
                )
            )
            const books = response.data.map(item => item.data)
            return [...books]
        },
    },
}
const cors = Cors()

const apolloServer = new ApolloServer({
    typeDefs,
    resolvers,
    context: ({ req }) => {

    },
    introspection: true,
    playground: true,
})

const serversStart = apolloServer.start()

export default cors(async (req, res) => {
    if (req.method === "OPTIONS") {
        res.end();
        return false;
    }

    await serversStart;
    await apolloServer.createHandler({ path: '/api/graphql' })(req, res)
})

Through a Chrome browser, I can view the data it calls through the Apollo GraphQL Studio and it successfully responds:

Following your suggesting @alexnitta, I added the following code to the _app.js file:

import '../styles/globals.css'
import { ApolloClient, InMemoryCache } from "@apollo/client";
import { ApolloProvider } from "@apollo/client";


const client = new ApolloClient({
  uri: "https://graphql.fauna.com/graphql",
  headers: {
    authorization: `Bearer ${process.env.FAUNA_SECRET}`,
  },
  cache: new InMemoryCache(),
});



function MyApp({ Component, pageProps }) {

  console.log(client)
  return (
    <ApolloProvider client={client}>
      <Component {...pageProps} />
    </ApolloProvider>
  )
}

export default MyApp

Although, I’m not sure if the uri parameter should be set to /api/graphql instead.

An alternate page which should call the data is the test.js page:

import gql from "graphql-tag";
import { useQuery } from "@apollo/react-hooks";

const Query = gql`
query Query {
    books {
      title
    }
  }
`
export default function Test() {
    const { data, loading } = useQuery(Query);
    if (loading) {
        return "Loading...";
    }

    return (
        <div>
            <div>
                TEST
            </div>
            {data.books.title}
        </div>
    );
}

As you can probably tell, I’m quite green to Apollo and also fauna development.

The page localhost:3000/test simply returns the TypeError: Cannot read properties of undefined (reading 'books').

I’m not sure how to render the data :confused:

Since your API is returning data from fauna, your client will only be interested in calling the API. You will not need to include any auth header in your app code.

(Unless you want users to send login-tokens on each request, in which case we can handle that later :slight_smile: )

Your Apollo client needs to connect to your API, not to Fauna. This blog that focuses on Apollo Client and Next.js may be helpful. For what it’s worth, I like Apollo much better than SWR for a GraphQL client haha :grinning_face_with_smiling_eyes: Getting Started With Apollo Client in Next.js - Apollo GraphQL Blog

You had the right idea at the beginning to send your application client to /api/graphql. You may need to include the entire URL, e.g. mydomain.com/api/graphql, or set to localhost when you are working locally.

const client = new ApolloClient({
  uri: "https://mydomain.com/api/graphql",
  // ...
});

Other considerations

Here are some other things to consider that are probably outside of the scope of this topic. If you have follow up questions, I do suggest starting a new forums topic, so this one can focus on getting you connected and moving on.

User login

Does your app always fetch publis documents? Then this is a great way to stash a server key away in your server and give folks access to that.

If users need access to private information, then you can give them tokens from the Login function, or Create(Token(), /* ... */) method. Then those users can pass that token to your API through an auth header or cookie. At that point your GraphQL resolvers will need to create a new client for each request that uses that secret.

You can access cookies sent to your API like they did here, and you can set headers and cookies with an apollo plug in such as apollo-server-plugin-http-headers.

That leads me to:

Create a new Fauna Client on every request

Do to the nature of serverless functions, we strongly recommend you create a new client for every request. Known issues in Fauna drivers | Fauna Documentation

In your Apollo server resolver, I would recommend creating the client there.

import Cookies from 'cookies'

const resolvers = {
  Query: {
    books: async (parent, args, context, info) => {
      // check cookies
      const {req, res} = context // get next
      const cookies = new Cookies(req, res)
      const userToken = cookies.get('fauna-user-token')

      // default to public key with permissions only to public documents
      const secret = userToken || process.env.FAUNA_SECRET

      const client = new Client({
        secret: secret,
        domain: "db.fauna.com",
      })

      /* ... */
    },
  },
}
1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.