Connecting a Todo app to a serverless GraphQL API using Apollo Client and solving CORS

Chris Biscardi
InstructorChris Biscardi
Share this video with your friends

Social Share Links

Send Tweet
Published 5 years ago
Updated 4 years ago

Here we take a working client side app and a working GraphQL server and replace the client side logic with network calls to the GraphQL API. We also cover common pitfalls like adding CORS headers to the graphql server

Instructor: [00:00] We have a GraphQL API in production. I used in-memory arrays on the server to handle our to-dos. We have the add to-do mutation, the update to-do done mutation, and also the query to get all the to-dos.

[00:10] On the client, we're still using in-memory arrays to deal with our to-dos. We need to connect our client to this GraphQL API that we just created. To do that, we're going to set up Apollo Client. In this case, we'll be using Apollo Client 3.0Take note of that if you're looking at the documentation.

[00:26] To get everything to work in Rapid element, we're going to add the Apollo provider, which will take an Apollo client instance. The Apollo client instance will be built out of the Apollo client from Apollo/client, as well as an HTTP link and an in-memory cache.

[00:41] Creating a new client uses the Apollo Client constructor with the in-memory cache as the cache key, and the HTTP link as the link key. You'll notice that the URL for our server is pointed at production and is our Netlify function's GraphQL API.

[00:55] We also need to set up the Apollo Provider and pass in the client value. This will allow any of our React Components to access the client through the Context, which enables us to use useQuery and useMutation.

[01:08] There's a couple of areas of logic that we need to replace in our dashboard. The first one is this todo's reducer, which handles all of the actions that we previously had in memory. For each of these actions, addTodo and toggleTodoDone, we'll replace them with a mutation that calls to our GraphQL API.

[01:25] The third replacement we need to make is to query for all of the todos, which are currently held in memory by the todo's reducer, but will come from our GraphQL API instead. We'll need to import GQL, which is a tagged template literal function, useMutation for our mutations, and useQuery for our queries. All of these come from Apollo Client.

[01:47] First, we'll handle our mutations. If we go to our addTodo mutation, we can see that we have an addTodo that takes a text. We'll replace the example code with our mutation. Next, we'll go down to our addTodo reducer payload, and replace it with the GraphQL mutation.

[02:02] UseQuery and useMutation are React hooks, which means we need to put them at the top of our functional component, and we can't stick them in any if statements. We'll take our addTodo query, we'll create a function called addTodo from that useMutation call and the data that comes back from that mutation.

[02:20] Now, if we go down to dispatch, where we dispatch the addTodo reducer action, we can replace it with the addTodo mutation. The call looks slightly different. We'll keep the same payload. The addTodo mutation takes an object, of which one of the keys is variables. That's where we'll put our text from the input. All of our other logic stays the same.

[02:47] Now that we can add todos, we'll also need to write the GraphQL queries so we can fetch the todos. If we go back to our GraphQL Playground, we can see the query that we want. In this case, we'll also give the query a name. We'll make use of more of useQuery's return types than we will of useMutation's, in this case, loading, error, and data.

[03:07] Since the data that we care about is going to come from the query, we'll get rid of the data and the useMutation. Down where we're mapping over todos, we're going to need to check loading and error to see if they exist, before we map over the todos.

[03:21] In this case, we've decided that if we're loading, then we'll display a loading message, if we have an error, then we'll display an error message, and if we're not loading and we don't have an error, we'll display the data.

[03:33] We have one more mutation to write, and that's the update the done status of a todo. We'll keep the same structure that we have for our previous todo, copying our query out of the GraphQL explorer, giving it a name, and changing in the variables.

[03:48] Then we'll use useMutation with the updateTodoDone query to create an updateTodoDone function. This function will replace our last dispatch. Instead of using the index, we use todo.ID. Let's boot up our site and see how we did. It looks like our site has a single problem. This problem is that fetch doesn't exist in Node. We can trivially check this by opening the Node repo and checking for fetch.

[04:15] We have two options here. One is that we can avoid using our Apollo client in anything but Gatsby Browser. The other is that we can install an isomorphic-fetch polyfill. We can see that if we completely remove Apollo client, the build completes successfully, which validates our assumption that this is a problem only in the build.

[04:33] We'll take all of the Apollo client logic out of our shared wrappered element, and move it to only be in Gatsby Browser. We can still call our original wrappered element, wrapping it in the Apollo Provider, but only in Browser. It lets us use our development application.

[04:46] If we go to the dashboard, we see a network error, "Access to fetch at our GraphQL API from origin localhost:8000 has been blocked by CORS policy." Cross-origin resource sharing can be annoying for many developers. In this case, we need to redeploy our GraphQL API.

[05:01] The Apollo apollo-server-lambda createHandler takes an object with some options. In this case, you can specify that the CORS origin is star. Now we have a change we want to deploy, but we want to deploy it without deploying all of the other logic that we're trying to test in development. We can git add this specific file and add a message.

[05:23] Note, that the file we've committed has disappeared from our index, and we're also ahead by one commit. We'll push this up and wait for the build to finish. Now that the site is live, we can try again. We've eliminated our network errors, but we also have a bit of a usability issue here.

[05:40] You'll note that we see loading when it's loading, but after it loads and there are no todos, we see nothing. Before shipping this as a production application, we would probably want to fix that.

[05:50] We can see that after adding the todos, they don't show up, even though it looks like the request succeeded and we can see the keys. If we refresh, we can see two boxes that look like they're empty. In the console, we can see each child in the list should have a unique key prop, which is a reactor.

[06:05] First thing we need to do is specify a key reflex. If we use the todo ID, that will get rid of the first React warning. The second thing we'll want to do is actually log out the data that we're getting. It looks like we have todos in an array, with an ID TextDone, and the type name, which you'll see come back in GraphQL requests.

[06:25] If we Inspect Element on the todos, we can see that the spans are empty. That's because we've renamed the value to text, so we'll change that here. Now we can see 1 and 2, which are the two todos we've added. If we add 3, we still don't see it, unless we refresh the page.

[06:42] To fix this, we can refetch our original query and useQuery for getTodos destructure a refetch. We'll get rid of our console.log as well, since we know they're coming back. After we add the todo, we'll want to refetch.

[06:57] Note, that as we're working on this, sometimes our items disappear before we expect them to. This is because we're holding the data in memory in a serverless function. The serverless function can be torn down outside of our control. This gets rid of the memory and gives us new memory.

[07:11] When we add refetch, we'll also want to make this an async function and await the todo. Now we can see that when we add a todo, it gets added to the list and refetched. If we click on the todo, we can see that the client-side doesn't update the todo status until we refetch. We'll add the async await and refetch again, and we updateTodoDone.