Copied to clipboard

(Taking a) REST with Apollo Client

Apollo Client is amazing when it comes to fetching data from any GraphQL server using any modern front-end framework. But how about using Apollo with a REST API? In this tutorial, we show how to successfully get them to work together!

20/11/2019
-
12
min read
(Taking a) REST with Apollo Client

Before we go into the details, we need to explain a few concepts.


First things first: what is Apollo Client?


Apollo Client is a GraphQL client. It helps you request data from any endpoint in a declarative way and takes care of the whole request cycle. To top it off, Apollo Client has built-in features that enable you to implement data caching, pagination, refetching, subscriptions and more out of the box.


The best way to explain how Apollo works is through the graph below:

  1. The UI (any modern front-end framework) uses a GraphQL query to communicate with Apollo Client.
  2. Apollo Client uses the query to request data from any endpoint (GraphQL server, REST API, …).
  3. API sends the result back which Apollo Client catches.
  4. Apollo Client sends the result back to the UI so it can update accordingly.

You can find more information about Apollo Client and GraphQL here:

Apollo Client:

GraphQL


Why did we start using it?


We used to work with Redux for managing our state. While it’s a fantastic library, it also brings in a lot of boilerplate code and stuff you have to manage manually. We’ve been looking for ways to reduce this, which is how we found Apollo. However, this doesn’t mean that Apollo Client should replace Redux. It can – and for us it did – but you can also use both hand in hand.


So what makes Apollo Client so valuable for us? The main reason is its query caching and request cycle management, while also managing our state for us! We use another library for managing local state, but that’s a matter for another blog post ;)


Because you get all this out of the box, you as a developer have to write much less code while still maintaining full control – and the overall structure looks much cleaner.


Apollo Client typically works with a GraphQL server. But at November Five, there are 2 reasons why we couldn’t just switch everything to GraphQL:


That’s where apollo-link-rest comes in: the main topic of this blogpost!


Basic setup

For the setup of Apollo Client with a REST API, we assume you already have a basic React project setup & Yarn as your package manager.


To start off we need to have the appropriate libraries installed. You can do that by running the command below:


yarn add apollo-client apollo-link-rest apollo-cache-inmemory react-apollo graphql graphql-tag

This will install the following libraries:

For the actual basic setup of the Apollo Client in your React project, you can use this guide and check out this SandBox for the implementation.


The most important aspect is the restLink, which will enable us to use a REST API as an endpoint for our requests.


For this blogpost we will be using reqres.in as our API base url. In your restLink change the uri to this url:


{% c-block language="js" %}
const restLink = new RestLink({
uri: 'https://reqres.in', // this is your API base url
credentials: 'same-origin',
});
{% c-block-end %}

Later on we will use this endpoint to illustrate how data fetching with apollo-link-rest works in React.


Queries

We need 2 things to fetch data in our application:

  1. A GraphQL query
  2. Apollo’s Query component


Bear in mind that to use GraphQL queries in our code, we need some way to parse it. We can do this in 2 ways:

  1. Parse it in our Javascript files using graphql-tag’s gql template tag
  2. Let webpack parse it for us


We’re not getting into the details here, but at November Five we use option 2. You can use the graphql-tag/loader as a webpack loader, which allows you to use .gql files to write your queries! We will also continue this guide by using option 2.


To implement this, you can follow this guide.


1. Create a GraphQL query


To fetch all users from our API, we need to have a GraphQL query.

  1. Create a file users.gql
  2. Add the following query:


{% c-block language="js" %}
query GET_USERS {
users @rest(type: "Users", path: "/users") {
total
data @type(name: "User") {
id
first_name
last_name
}
}
}
{% c-block-end %}

In this case, we want to know the total amount of users and some data of every user. Apollo will automatically filter the response to only provide us with these keys. Want more data from the users? Just add the key to the query!


You will also notice a few things:


2. Create a React component that implements this query


To use this query with Apollo to fetch some data in our application:


{% c-block language="js" %}
import React, { PureComponent } from 'react';
import { Query } from 'react-apollo';
import GET_USERS from './users.gql'; // if you only have a single query in this file
// import { GET_USERS } from './users.gql'; // if you have multiple queries in this file
class Users extends PureComponent {
render() {
return (
<Query query={GET_USERS}>
{({ data: { users: { data } }, loading, error }) => (
<ul>
{data.map(user => (
<li key={user.id}>{user.first_name} {user.last_name}</li>
))}
</ul>
)}
</Query>
)
}
}
export default Users;
{% c-block-end %}

A few things to note here:

1. When importing straight from a .gql file there are 2 possibilities:

  1. You have only one query in this file: Import the default (you can’t import it as a single export)
  2. You have multiple queries in this file: Import the single export


2. Render props: all response-related information is passed as render props. For now, the most important ones are:

You can find a full list of the properties here.

And there you have it, you’re now fetching data from a REST API with apollo-client and apollo-link-rest!


3. How to use custom variables


At the moment, we have a basic query to fetch data. But what if we want to request a specific user instead of all of them? To do this, we need to be able to pass variables to our query. Luckily this isn’t hard to do. Let’s add a query to our users.gql file to request a specific user.


{% c-block language="js" %}
query GET_USER {
user(id: $id)  @rest(type: "User", path: "/users/:id") {
id
first_name
last_name
}
}
{% c-block-end %}

Now that we have 2 queries in users.gql, we will need to go back and update our GET_USERS import, remember?


Change it from:


{% c-block language="js" %}
import GET_USERS from ‘./users.gql';
{% c-block-end %}

To:


{% c-block language="js" %}
import { GET_USERS } from ‘./users.gql';
{% c-block-end %}

To implement the new query, we can do exactly the same as we did in our Users.js file. We simply need to:

  1. Import the GET_USER query
  2. Add a variables prop with the appropriate values

Since we defined $id in our GET_USER query, we define id with a value inside the variables prop object.

{% c-block language="js" %}
<Query query={GET_USER} variables={{ id: 5 }}>
    // …
</Query>
{% c-block-end %}

4. How to handle custom paths

Pathbuilder is a feature of apollo-link-rest that allows you to dynamically create the path for your query.



Note: this feature is not well documented, but you can find more information in the source code.


It allows you to construct your own path string based on variables you passed to the query. This is super useful if you need to build up requests with, for example, query params that are sometimes there but sometimes not. The variables available in pathBuilder are the ones that are defined above.


To illustrate, let’s create a situation where we first want to fetch all users and afterwards fetch all users on page 3 (/users?page=3). If we did it with our normal structure, the variable page would be undefined in our first fetch, which would cause apollo-link-rest to crash. That’s where the pathbuilder comes in.


In these examples, we will use simplified code to show you how pathbuilder works – but of course you can automate the adding/removing of variables.


{% c-block language="js" %}
<Query
query={GET_USERS}
    variables={{
         page: this.state.page,
         pathBuilder: variables => variables.page ? `users?page=${variables.page}` : 'users';
    }}
>
        // …
<Query/>
{% c-block-end %}

Then in your @rest directive you can do the following:

{% c-block language="js" %}
@rest(type: "Users", pathBuilder: $pathBuilder)
{% c-block-end %}


to pass the pathBuilder’s path to the query as your new path. Neat.


Mutations

As we mentioned previously, there are 3 types of queries you can use: Query, Mutation and Subscription. Mutation looks quite a lot like Query, but with a single key difference: instead of simply requesting data, you use it to update data.


Let’s say we want to POST some data. First we need to build our mutation. You can add it to users.gql.


{% c-block language="js" %}
mutation CREATE_USER {
user(input: $input) @rest(type: "User", path: "/users", method: "POST") {
first_name
last_name
}
}
{% c-block-end %}

There are a few new things:


{% c-block language="js" %}
{
first_name: 'Samuel',
last_name: 'De Pooter'
}
{% c-block-end %}

Now that we have our mutation, it’s time to add it to an Apollo Mutation component.


Let’s create a new component named CreateUser.js that will implement the mutation. This component is responsible for passing the necessary props to our Form component.


{% c-block language="js" %}
import { Mutation } from 'react-apollo';
// ...
<Mutation query={CREATE_USER}>
    {(createUser, { data, loading, error }) => (
        <Form
            createUser={createUser}
            data={data}
            loading={loading}
            error={error}
        />
    )}
</Mutation>
{% c-block-end %}

As you can see there are a few new things:


Let’s create that Form component. We keep things very basic, since this is simply illustrative.


{% c-block language="js" %}
import React, { PureComponent } from 'react';
class CreateUserForm extends PureComponent {
handleSubmit = () => {
// get variable values for example from the state
this.props.createUser({ variables: { input: { first_name, last_name } } })
}
render() {
return (
<form onSubmit={this.handleSubmit}>
// ...
</form>
)
}
}
{% c-block-end %}

The most important part is our form’s onSubmit prop. Here we have a handleSubmit function that executes the createUser function from our props. We pass input as a variable and add our form values to it.


And that’s it! We now have a working POST call.


Type patching

As previously mentioned, you need to define a query’s type in order to properly store it in Apollo’s cache. If you don’t, Apollo doesn’t know where to store this new data in the cache. Nested objects all need to be typed individually and manually.


There are 2 ways to do this:

  1. When creating your RestLink, one of the options you can pass is typePatcher. Here, you can manually patch an entire response object. It works but it’s not the best way to do it.
  2. There’s an @type directive you can use to type objects directly inside your query. This is the way to go!


Let’s take our old GET_USERS query and add some extra information to it that we should type:


{% c-block language="js" %}
query GET_USERS {
users @rest(type: "Users", path: "/users") {
total
data @type(name: "User") {
id
first_name
last_name
friends @type(name: "UserFriend") {
id
age
}
}
}
}
{% c-block-end %}

As you can see, we defined a type for each nested object!


A nice pattern to use here is to reuse the parent’s type name and add the current object description name to it.


You can use the Apollo Client Developer Tools to inspect your cache and see the type patching in action.


(De)normalise

The link of apollo-link-rest has a feature that allows you to normalise or denormalise your data. When you create your link:


{% c-block language="js" %}
const restLink = new RestLink({
uri: 'https://reqres.in/', // this is your API base url
credentials: 'same-origin',
});
{% c-block-end %}

You can also pass in 2 extra keys:

  1. fieldNameNormalizer
  2. fieldNameDenormalizer


{% c-block language="js" %}
const restLink = new RestLink({
uri: 'https://reqres.in/', // this is your API base url
credentials: 'same-origin',
fieldNameNormalizer: key => humps.camelize(key)
fieldNameDenormalizer: key => humps.decamelize(key)
});
{% c-block-end %}

In this example, we use humps to camelise our response data and snake_case our request data.


When should you use this?


{% c-block language="js" %}
const user = { first_name: "Samuel", last_name: "De Pooter" };
{% c-block-end %}

to

{% c-block language="js" %}
const user = { firstName: "Samuel", lastName: "De Pooter" };
{% c-block-end %}


{% c-block language="js" %}
const user = { firstName: "Samuel", lastName: "De Pooter" };
{% c-block-end %}

to

{% c-block language="js" %}
const user = { first_name: "Samuel", last_name: "De Pooter" };
{% c-block-end %}


For example, this is super useful if you have an API that sends you snake_case keys, but you want to use camelCase in your application. First you normalise the keys and before every request you denormalise them. How you implement it (snake_case, camelCase, lowercase, UPPERCASE, …) is up to you!


And that concludes our Apollo Client & REST tutorial!

If you’re looking for further support on Apollo Client or apollo-link-rest issues, you can always post an issue on their respective repositories; join the community channel or of course, contact us!


New look for modals in iOS apps

With iOS 13, Apple brings a new look to modals, i.e. when you need to focus your user’s attention on making a choice or performing a task other than their current task. Going forward, the ‘sheet’ presentation style – right-hand screen in the screenshot below – will become the default design, and the recommended style for non-complex tasks.

iOS modals

While the top edge of the underlying content remains visible, all uncovered areas are dimmed to focus the user and prevent interaction with them. Users can still tap a button to dismiss the card and return to the previous screen, but they can also do so by swiping down.

What can I do?

While this won’t bring any dramatic changes, take the opportunity to make sure you’re delivering the best user experience. Keep the following tips in mind:

Android catches up on restrictions to background activity

With Android 10, apps running in the background will no longer be able to start activities.

This brings Android more in line with Apple’s strict guidelines for how and when apps can run in the background, the goal being to minimise interruptions for users and give them more in control of what’s shown on their screen.

If your app only starts activities as a direct result of user interaction, then most likely your app won’t be affected by this change.

However, apps with alarm functions will be severely impacted, since the alarm functionality will no longer work unless the app is opened in the foreground.

What can I do?

If your app needs to start in the background, create a notification to let the user know what’s happening or what they need to do.

And while this may require changes to your app, remember that it also brings benefits from both a user experience and app developer perspective:

New foreground services attributes for android 10

When your app uses a foreground service to let your users know about activities going on in the background, e.g. a data sync for Fitbit or location tracking for a running app, Android 10 needs to know the type of foreground service.

What can I do?

Make sure to include the foregroundServiceType in the service tag of the AndroidManifest. Available types include phoneCall, mediaPlayback, location, dataSync, and connectedDevice.

Apple takes your privacy to the next level

Privacy is one of the hottest topics around, and Apple has recognised that control over our own data and information is critical to today’s users. The new ‘Sign In with Apple’ option, and changes to location permissions are two features that need to be on your radar.

Sign In with Apple

Apple has developed a unique approach to compete with social login, i.e. using existing information from a social media account like Facebook, Twitter or Google+, to sign in to a third-party app or website.

With ‘Sign In with Apple’, users can use their Apple ID to sign in to iOS apps and websites. Using this option, apps can only ask for a user’s name and email address – no more sharing friend lists or date of birth. And going one step further, users can even request that Apple creates a unique ‘burner’ email address that forwards to the user’s real one, keeping real email addresses out of spammers’ hands.

As of iOS 13, apps in the AppStore that already provide third-party login will have to offer ‘Sign In with Apple’ as one of the login options, and app updates will be rejected if this is not the case. Note that this will also apply to webview login for iOS apps.

Location permissions

Changes to location permission settings with iOS 13 put the user right back in the driving seat when it comes to determining just what location information is tracked and when.

This permission will be stored during the session (and a limited time after) and will let the app access the user’s location. When the user closes the app, the location permissions are set to ‘not determined’, and when he/she reopens the app, the same pop-up will be displayed, asking once again for permission to use the location.

While of course you would like your users to grant more permanent permissions, this one-off chance lowers the bar for them to give your app’s location features a try. And if they like what you do, maybe they’ll be back!

Foreground location permission


Background location permission

What can I do?

First of all, take the opportunity now to review your location code to make sure you’re only requesting this data when it can bring real value for the user. And where the user experience really can be enhanced by sharing their location – or conversely, be greatly limited by not sharing – help your users understand this by being open with them, and explaining it to them (well, the main points!).

They’ll be much more likely to grant permission – even if the first few times it’s only ‘Allow Once’ – if they trust you and believe in the benefits. If you don’t take action, the introduction of the map feature could result in users seeing worrying amounts of location tracking, and subsequently, revoking permissions.

Also, keep a careful eye on the ‘Allow once’ feature – make sure that the OS doesn’t change the location permissions to notAuthorized instead of notDetermined after the session. If this happens – or if in the future this evolves to ‘Allow only once & don’t ask again’ – location access would be denied after the first session, and the user might not even be aware of the limitations.

Again, make sure your users understand how sharing their location benefits them!

Ready, set ... Go!

Are you ready to embrace the changes that iOS 13 and Android 10 are bringing? Not quite?

If you’re looking for extra support on any of the features mentioned above – or any others that these new releases will bring – don’t hesitate to contact us. Drop us a line at antwerp@novemberfive.co and we’ll be happy to arrange a chat!

No items found.

Read further

Written by

Samuel De Pooter

Samuel

Engineer