React Url Shortener

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 70

Build a URL Shortener using React, Apollo

and GraphQL
Peter Jausovec
This book is for sale at http://leanpub.com/react-url-shortener

This version was published on 2019-03-24

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.

© 2019 Peter Jausovec


Contents

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Intended Audience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Source Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
How To Use This Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i

I Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1. Development Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1 Prerequisites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Creating the Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Setting up GraphQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 Setting up Apollo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.5 Building React Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

II Working with GraphQL . . . . . . . . . . . . . . . . . . . . . . . . . . 10


2. Queries, Mutations and Subscriptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.1 Using GraphQL Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2 Using GraphQL Mutations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.3 Using GraphQL Subscriptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

III Serverless Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27


3. Creating Serverless Hashing Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.1 Before and After Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.2 Subscriptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.3 Resolvers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.4 Choosing the Function Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.5 Using Subscription to Implement the Hash Function . . . . . . . . . . . . . . . . . . . 30
3.6 Testing the Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

4. React Router . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
CONTENTS

4.1 Routing Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38


4.2 Tracking Clicks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

IV User Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.3 Login and Sign-Up React Components . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.4 Adding Auth Support to Graphcool Service . . . . . . . . . . . . . . . . . . . . . . . . 52
4.5 Add the template . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.6 Sign me up! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.7 Log me in! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.8 Add Authorization Token to Apollo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.9 Linking Data Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

About The Author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65


Introduction
This book will help you get started with React, Apollo and GraphQL by working through a simple
project you will build from scratch. The project you will be builiding is a simple URL shortener.
You will set up a new React project and learn how to use Graphcool and connect to the GraphQL
endpoint using Apollo. You will learn the following:

• Set up a new React project


• Create a new Graphcool service
• Use Apollo to connect to the GraphQL endpoint
• Create GraphQL mutations, queries and subscriptions
• Create simple React components that display GraphQL data
• Implement login and sign-up functionality

I originally wrote the contents of this book as series of posts that were published on Medium. I
was learning about React and GraphQL and wanted to document my experiences in a form of a
tutorial that might help others as well. The book doesn’t go into a lot of details and deep technical
explanations of different parts, however it does give you enough information to get your project up
and running.

Intended Audience
The book is written as a tutorial and assumes you have basic understanding of Javascript and React.
It’s mostly meant for beginners and anyone who likes to get practical information on how to get
started with React, Apollo and GraphQL.

Source Code
Source code for the created project can be downloaded from https://github.com/peterj/shortly.

How To Use This Book


That will depend on your style of learning. You can grab the full source code of the project and start
looking through it, while you’re reading through the book. Alternatively, you can follow along with
the book - start by creating a new project yourself and adding and updating code as needed.
Cover Photo by the_roaming_platypus
I Getting Started
In this book I’ll explain how to build a simple URL shortener using React, Apollo and GraphQL
(Graphcool). GitHub repo with the project source code is located here.
The idea behind the URL shortener is simple — the shortener takes a long URL such as www.example.com/thisisalongu
and shortens it to http://goo.gl/ABC. When shortened URL is accessed, the service expands it to
the original URL and redirects you to the original URL. The algorithm I’ll use to calculate the hash
(short URL) is explained here.
Here are some of the bigger areas and features we will implement:

• Setup and displaying short URLs using GraphQL (this post)


• Creating short URLs
• Short URL stats (number of clicks, etc.)
• Expiring the URLs (e.g. no clicks in X weeks/months/years)
• User authentication
1. Development Setup
This chapter explains the prerequisites and tools we will be using throughout the book. By the end
of this chapter you will have a skeleton React project set up and a new GraphQL service created.

1.1 Prerequisites
We are going to use React and Apollo on the frontend and and Graphcool on the backend.
Here are the prerequisites and tools I’ll be using to work on this project:

• create-react-app (Install with: npm install -g create-react-app)


• Graphcool CLI (sign up here then run npm install -g graphcool to install the CLI)
• Visual Studio Code - you can also use any other editor

Once you have everything, you can get started with the project!

1.2 Creating the Project


I’ll name this project Shortly as I am not too good with naming things. It doesn’t matter what you
name it, I just want to make sure that if you see that name anywhere throughout the book, you
know what I am referring to.
Open your terminal in your projects folder and start by running create-react-app to scaffold a new
React website:
create-react-app shortly

To make sure all went well, go inside the folder that got created by the above command, and run
yarn start. If all went well, you should see the default React website open in your default browser.
Development Setup 3

Default React App

1.3 Setting up GraphQL


I am using the Graphcool CLI to set up and initialize the GraphQL project. The initialization
command creates a bunch of files, so let’s create a subfolder in the root project folder first. Make
sure you run graphcool login command before running the init command.
Development Setup 4

1 $ cd shortly
2 $ mkdir graphcool && cd graphcool
3 $ graphcool init
4 Creating a new Graphcool service in .... �
5 Written files:
6 ├─ types.graphql
7 ├─ src
8 │ ├─ hello.js
9 │ └─ hello.graphql
10 ├─ graphcool.yml
11 └─ package.json
12 ...

The Graphcool CLI creates the following files:

• graphcool.yml — configuration file for the Graphcool service that references all types, serverless
functions and defines permissions
• types.graphql — defines the data model for the Graphcool service
• src folder — holds source code for any serverless functions

Let’s delete the src folder and remove the references to any file in that folder from the graphcool.yml
file, so the file look like this (I removed the comments as well):

1 types: ./types.graphql
2 permissions:
3 - operation: "*"

Similarly, remove the User type from the types.graphql file for now and add the Link type. This is
the type you will use to store the short links. You are going to start with a very simple model and
add more to it later. The contents of the types.graphql file should look like this:

1 type Link @model {


2 id: ID! @isUnique
3 hash: String!
4 url: String!
5 description: String
6 }

The definition above is pretty self-explanatory — for each link we have a unique ID, a string hash,
a string URL and an optional description. If you’re wondering about what the exclamation mark
on the fields those, well it makes that field required. You can read more about modelling data in
Graphcool here.
Development Setup 5

Finally, we need to create the actual service and push the changes we made by running the deploy
command from the graphcool folder you created earlier. You’d run the same command each time
you make a change to your types or functions:
graphcool deploy

Since you are running the deploy command for the first time, you will need to make some choices.

1. Pick a shared cluster from the list below:


• shared-eu-west-1 (Europe)
• shared-ap-northeast-1 (Asia)
• shared-us-west-2 (US)

In addition to the above shared clusters, you could also pick a custom cluster and run everything
inside a Docker container locally on your machine.

For the purpose of this book, I will pick one of the shared clusters above.

1. Pick the target environment name (default being prod). Just press enter to accept the default
value.
2. Choose the service name - I went with shortly, but feel free to use any other name.

A couple of seconds later, the CLI will give you an overview of what was added/deployed, as well
as the GraphQL endpoints called Simple API, Relay API and Subscriptions API. You will use the
Simple API endpoint to connect the React website to the GraphQL backend.

1.4 Setting up Apollo


In this section you are going to install Apollo packages to the React website and use it to connect
to the GraphQL endpoint. First, you need to install Apollo packages you’ll be using. Run the below
command from the root project folder:
yarn add apollo-client apollo-cache-inmemory apollo-link apollo-link-http react-apollo
graphql-tag graphql

Once that’s installed, you can configure the Apollo client to connect to the GraphQL endpoint and
use a higher-order component to wrap the App component with the ApolloProvider.

1. Add import statements to index.js:


Development Setup 6

1 import { ApolloClient } from 'apollo-client'


2 import { InMemoryCache } from 'apollo-boost';
3 import { ApolloProvider } from 'react-apollo';

2. Create the ApolloClient instance — replace the [SERVICE_ID] value with the one you got when
you deployed the Graphcool service, or just run graphcool info to show that information
again.

1 const client = new ApolloClient({


2 link: new HttpLink('https://api.graph.cool/simple/v1/[SERVICE_ID]'),
3 cache: new InMemoryCache()
4 });

3. Create a higher-order component to wrap the App component:

1 const withApolloProvider = Comp => (


2 <ApolloProvider client={client}>{Comp}</ApolloProvider>
3 );
4 ReactDOM.render(
5 withApolloProvider(<App />),
6 document.getElementById('root'));

The higher-order component withApolloProvider is a function that takes a component, wraps it


inside the ApolloProvider and returns a new component.

1.5 Building React Components


Let’s add the prop-types library, so we get runtime type checking to React props. It’s a great way
to ensure you’re passing correct props to your components.
yarn add prop-types

We are going to start with a Link and LinkList components for displaying a hardcoded (for now)
list of links.

1. Create the src/components folder and a Link.js file. The component in this file represents a
single link.
Development Setup 7

src/components/Link.js

1 import React, { Component } from 'react';


2 import PropTypes from 'prop-types';
3
4 class Link extends Component {
5 render() {
6 return (
7 <div>
8 <div>
9 {this.props.link.description} ({this.props.link.url} -{' '}
10 {this.props.link.hash})
11 </div>
12 </div>
13 );
14 }
15 }
16
17 Link.propTypes = {
18 link: PropTypes.shape({
19 id: PropTypes.string,
20 url: PropTypes.string,
21 hash: PropTypes.string,
22 description: PropTypes.string
23 })
24 };
25
26 export default Link;

We are passing a single link prop to the component and displaying it inside a div.

1. Create a LinkList.js file — this component is used for displaying a list of links (at this step
you will use a hardcoded array of links and once you switch over to GraphQL, you will remove
it)
Development Setup 8

src/components/LinkList.js
1 import React, { Component } from 'react';
2 import Link from './Link';
3
4 const ALL_LINKS = [
5 {
6 id: '1',
7 hash: 'ABC',
8 url: 'http://google.com',
9 description: 'Google shortlink'
10 },
11 {
12 id: '2',
13 hash: 'DEF',
14 url: 'http://graph.cool',
15 description: 'Graphcool shortlink'
16 },
17 {
18 id: '3',
19 hash: 'GHI',
20 url: 'http://reactjs.org',
21 description: 'ReactJS shortlink'
22 }
23 ];
24
25 class LinkList extends Component {
26 render() {
27 return (
28 <div>
29 {ALL_LINKS.map(link => (
30 <Link key={link.id} link={link} />
31 ))}
32 </div>
33 );
34 }
35 }
36
37 export default LinkList;

1. Finally, let’s update App.js and render the LinkList component you created in the previous
step:
Development Setup 9

src/App.js

1 import React, { Component } from 'react';


2 import LinkList from './components/LinkList';
3
4 class App extends Component {
5 render() {
6 return <LinkList />;
7 }
8 }
9
10 export default App;

Run yarn start from the terminal, then open the browser and navigate to http://localhost:3000 to
confirm you can see the hardcoded links as shown in the figure below.

Hardcoded list of links


II Working with GraphQL
In this chapter you will learn how to create GraphQL queries, mutations and subscriptions and how
to hook them up with React components to display data.
2. Queries, Mutations and
Subscriptions
GraphQL uses queries, mutations and subscriptions to work with the data. Queries are used by the
clients to request the data from the server. Mutations are used for creating, updating and deleting
existing data. Finally, subscriptions are a used for creating and maintaining connections to the
service - they allow clients to subscribe to events and once those events occur, server sends the
data back to the client.

2.1 Using GraphQL Queries


In order to get the data from GraphQL you need to write a GraphQL query that’s going to fetch all
links from the backend. This is the query you will be using:

1 const ALL_LINKS_QUERY = gql`


2 query AllLinksQuery {
3 allLinks {
4 id
5 url
6 description
7 hash
8 }
9 }`;

The gql is a helper function from graphl-tag and you can use it to define the GraphQL queries.
The allLinks query name was automatically generated by Graphcool and it can be used to fetch
multiple nodes. The query name (AllLinksQuery) is the name we came up with for the query. Any
values inside the allLinks query (e.g. id, url, …) are the fields we want GraphQL to return. In the
case above we will be returning an id, url, description and a hash.
Another query that is automatically generated for our type is a Link query. With it, we can query
for a single node (an instance of a Link) by id. For example:
Queries, Mutations and Subscriptions 12

1 query SingleLink {
2 Link (id: "someid") {
3 url
4 description
5 }
6 }

Query API
For a more detailed information about the Query API, you can head over to the Query API
docs.

Now that you have the ALL_LINKS_QUERY defined, you can use the graphql container to wrap
the LinkList component with it. When query finishes executing, the results get populated to the
component’s props object. Here’s the updated code for LinkList.js:
src/components/LinkList.js

1 import React, { Component } from 'react';


2 import Link from './Link';
3
4 import gql from 'graphql-tag';
5 import { graphql } from 'react-apollo';
6
7 const ALL_LINKS_QUERY = gql`
8 query AllLinksQuery {
9 allLinks {
10 id
11 url
12 description
13 hash
14 }
15 }
16 `;
17
18 class LinkList extends Component {
19 render() {
20 if (this.props.allLinksQuery && this.props.allLinksQuery.loading) {
21 return <div>Loading ...</div>;
22 }
23
24 if (this.props.allLinksQuery && this.props.allLinksQuery.error) {
25 return <div>Error occurred</div>;
Queries, Mutations and Subscriptions 13

26 }
27
28 const allLinks = this.props.allLinksQuery.allLinks;
29 if (allLinks.length === 0) {
30 return <div>No links...</div>;
31 }
32
33 return (
34 <div>
35 {allLinks.map(link => (
36 <Link key={link.id} link={link} />
37 ))}
38 </div>
39 );
40 }
41 }
42
43 export default graphql(ALL_LINKS_QUERY, { name: 'allLinksQuery' })(LinkList);

Let’s explain what’s happening with the above code starting at the top.
Lines 4–16 You imported gql and graphql and defined the ALL_LINKS_QUERY.
Lines 20–31 You are using the loading and error properties on the query name prop to check if the
data was fetch or if there was an error fetching the data. In those cases you either return a simple
div with “Loading” or “Error” text. Note that these should probably be components, so you can
re-use them throughout the site. If query is done loading and there are no errors, you get the data
(allLinks) and as a final check you return a div that’s says “No links…” if there weren’t any links
returned.
Line 41 Using the graphql container, you are combining the component and the query. You are
naming the query (allLinksQuery). This name ends up being the prop name that Apollo adds to the
component.
At this point, you can navigate to http://localhost:3000 to get the “No links” message.
Let’s use the Graphcool’s playground to add a couple of links. From the command line, run graphcool
playground to open the playground. Alternatively, you can go to http://graph.cool and click the Open
Console button and then the Playground button to open the playground UI.
To create a new Link we are going to write GraphQL mutation. Copy the mutation below to the
Graphcool playground:
Queries, Mutations and Subscriptions 14

1 mutation CreateLinkMutation {
2 createLink(
3 description:"First link from GraphQL",
4 url:"http://example.com",
5 hash:"somehash") {
6 id
7 }
8 }

Just like with the queries, Graphcool creates a createLink mutation for you. You provide the values
for required fields and return an id of the created Link. Click the play button on the playground to
execute the query. The result should look something like the figure below.

Executed mutation and created link


Queries, Mutations and Subscriptions 15

Notice how the result contains the value of the id - you could also return any other field by simply
including the field name in the mutation.
Now if you go back to the React app running at http://localhost:3000 and refresh, you should see the
Loading message first and then after query loads, you will see the link you created.

Showing links from GraphQL

In the next chapter, you will create a new React component that will be used for creating new links
using GraphQL mutation. Furthermore, you will implement a simple hash algorithm as a Graphcool
serverless function.
Let’s create a file src/components/CreateShortLink.js and come up with the UI for creating short
links. The component has two inputs, one for the URL and one for a description, and a button that
actually creates the link. Both values are stored in component state and can be retrieved when create
button is clicked.
Queries, Mutations and Subscriptions 16

src/components/CreateShortLink.js

1 import React, { Component } from 'react';


2
3 class CreateShortLink extends Component {
4 constructor(props) {
5 super(props);
6 this.state = {
7 description: '',
8 url: ''
9 };
10 }
11
12 createShortLink = async () => {
13 // Create a short link here.
14 };
15
16 render() {
17 return (
18 <div>
19 <input
20 id="url"
21 type="text"
22 value={this.state.url}
23 placeholder="Link URL"
24 onChange={e => this.setState({ url: e.target.value })}
25 />
26 <input
27 id="description"
28 type="text"
29 value={this.state.description}
30 placeholder="Link description"
31 onChange={e => this.setState({ description: e.target.value })}
32 />
33 <button onClick={() => this.createShortLink()}>Create</button>
34 </div>
35 );
36 }
37 }
38
39 export default CreateShortLink;

The main logic for creating the links will be in the createShortLink function. From there, the
Queries, Mutations and Subscriptions 17

GraphQL mutation to create a link is going to be called.

2.2 Using GraphQL Mutations


Similarly as you have done with the queries, you need to create a mutation and then wrap the
component with graphql container.
In the previous chpater, we have already used a GraphQL mutation for creating links:

1 mutation CreateLinkMutation {
2 createLink(
3 description:"First link from GraphQL",
4 url:"http://example.com",
5 hash:"somehash") {
6 id
7 }
8 }

This time, instead of hardcoding the values, we need a way to provide the values to the mutation
that we are using. To declare variables in a mutation (or a query), you simply prepend a $ sign to
the name of the variable like this: $myvariable.
Let’s take the above mutation and refactor it to use the variables:

1 mutation CreateLinkMutation($url: String!, $description: String!, $hash: String!) {


2 createLink(
3 url: $url,
4 description: $description,
5 hash: $hash) {
6 id
7 }
8 }

Inside the parenthesis next to the mutation name we declared the required string variables — later
on, we will remove the hash variable as we want to create the hashes using a serverless function.
These values get passed to the createLink mutation and when mutation executes, it returns the id
that represents the created short link.
Also, just like with the queries, we can reference this mutation when we wrap our component in
the graphql container like this:
Queries, Mutations and Subscriptions 18

1 import gql from 'graphql-tag';


2 import { graphql } from 'react-apollo';
3
4 const CREATE_SHORT_LINK_MUTATION = gql`
5 mutation CreateLinkMutation($url: String!, $description: String!, $hash: String!\
6 ) {
7 createLink(url: $url, description: $description, hash: $hash) {
8 id
9 }
10 }
11 `;
12
13 // CreateShortLink component code omitted ...
14
15 export default graphql(CREATE_SHORT_LINK_MUTATION, {
16 name: 'createShortLinkMutation',
17 })(CreateShortLink);

To access and call the mutation, Apollo injects the mutation name into the props objects — you need
to call it and provide the values for those three variables. Before you do that though, you need to
implement the hashing function to use for creating short links. I am not going into details about the
actual hashing algorithm. Instead, we are going to use a unique id (in our case it is going to be the
total count of links) and convert it to a base 62 encoding to get the hash digits. Finally, we translate
those digits into a unique hash string.

Shortening Strings using Base 62 Encoding


Read more about the algorithm for shortening strings in an article, written by Matthias
Kerstner.

Hashing Code Snippet

1 createHash = itemCount => {


2 let hashDigits = [];
3 // dividend is a unique integer (in our case, number of links)
4 let dividend = itemCount + 1;
5 let remainder = 0;
6 while (dividend > 0) {
7 remainder = dividend % 62;
8 dividend = Math.floor(dividend / 62);
9 hashDigits.unshift(remainder);
10 }
11 console.log(hashDigits);
12 const alphabetArray = `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456\
Queries, Mutations and Subscriptions 19

13 789`.split(
14 ''
15 );
16 // Convert hashDigits to base62 representation
17 let hashString = '';
18 let i = 0;
19 while (hashDigits.length > i) {
20 hashString += alphabetArray[hashDigits[i]];
21 i++;
22 }
23 return hashString;
24 };

Since we need to pass the variable itemCount to the hashing algorithm, we need a way to
get that value using GraphQL. Luckily for us, Graphcool automatically implements aggregation
queries for all types in the schema. The aggregation queries are named following this format
_all[TYPE_NAME]Meta where TYPE_NAME is the name of your type (Link in our case). A query to
get the count of all links would look like this:

1 query GetLinkCountQuery {
2 links: _allLinksMeta {
3 count
4 }
5 }

Notice the name links? You can use that variable to store the result of the query so that you can
refer to it in the code. Without that, the name of the prop would be _allLinksMeta.
There are two ways we can obtain the link count — we can follow the same pattern we did when
executing the queries in the previous post — basically, execute the query, check for errors and if the
query is done loading and then return. The second option is that we could execute the query on
demand when we need the value. We are going with the latter in this case, to demonstrate how to
do that.
So, instead of using graphql container to wrap the component, we can use withApollo container
that injects the Apollo client instance as a prop to the wrapped container similarly, as it injects the
queries and mutations.
Let’s bring this all together — the hashing code, import withApollo from react-apollo, add the link
count query and take care of the component wrapping:
Queries, Mutations and Subscriptions 20

src/components/CreateShortLink.js
1 import React, { Component } from 'react';
2
3 import gql from 'graphql-tag';
4 import { graphql, withApollo } from 'react-apollo';
5
6 const CREATE_SHORT_LINK_MUTATION = gql`
7 mutation CreateLinkMutation($url: String!, $description: String!) {
8 createLink(url: $url, description: $description) {
9 id
10 }
11 }
12 `;
13
14 const GET_LINK_COUNT_QUERY = gql`
15 query GetLinkCountQuery {
16 links: _allLinksMeta {
17 count
18 }
19 }
20 `;
21
22 const createHash = itemCount => {
23 let hashDigits = [];
24 // dividend is a unique integer (in our case, number of links)
25 let dividend = itemCount + 1;
26 let remainder = 0;
27 while (dividend > 0) {
28 remainder = dividend % 62;
29 dividend = Math.floor(dividend / 62);
30 hashDigits.unshift(remainder);
31 }
32 console.log(hashDigits);
33 const alphabetArray = `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456\
34 789`.split(
35 ''
36 );
37 // Convert hashDigits to base62 representation
38 let hashString = '';
39 let i = 0;
40 while (hashDigits.length > i) {
41 hashString += alphabetArray[hashDigits[i]];
42 i++;
Queries, Mutations and Subscriptions 21

43 }
44 return hashString;
45 };
46
47 class CreateShortLink extends Component {
48 constructor(props) {
49 super(props);
50 this.state = {
51 description: '',
52 url: ''
53 };
54 }
55
56 createShortLink = async () => {
57 // ADD CODE HERE!
58 };
59
60 render() {
61 return (
62 <div>
63 <input
64 id="url"
65 type="text"
66 value={this.state.url}
67 placeholder="Link URL"
68 onChange={e => this.setState({ url: e.target.value })}
69 />
70 <input
71 id="description"
72 type="text"
73 value={this.state.description}
74 placeholder="Link description"
75 onChange={e => this.setState({ description: e.target.value })}
76 />
77 <button onClick={() => this.createShortLink()}>Create</button>
78 </div>
79 );
80 }
81 }
82
83 export default graphql(CREATE_SHORT_LINK_MUTATION, {
84 name: 'createShortLinkMutation'
85 })(withApollo(CreateShortLink));
Queries, Mutations and Subscriptions 22

Now that everything is in place, we can write the code that’s going to get the values we entered, get
the link count, create a hash and finally run the mutation to create a new short link.

1 createShortLink = async () => {


2 const linkCountQuery = await this.props.client.query({
3 query: GET_LINK_COUNT_QUERY,
4 });
5
6 const linkCount = linkCountQuery.data.links.count;
7 const hash = createHash(linkCount);
8
9 const { url, description } = this.state;
10 await this.props.createShortLinkMutation({
11 variables: {
12 url,
13 description,
14 hash,
15 },
16 });
17 };

Next, let’s update the render method in App.js and add the CreateShortLink to it:
src/App.js
1 import React, { Component } from 'react';
2 import LinkList from './components/LinkList';
3 import CreateShortLink from './components/CreateShortLink';
4
5 class App extends Component {
6 render() {
7 return (
8 <div>
9 <div>
10 <h2>All links</h2>
11 <LinkList />
12 </div>
13 <div>
14 <h2>Create a short link</h2>
15 <CreateShortLink />
16 </div>
17 </div>
Queries, Mutations and Subscriptions 23

18 );
19 }
20 }
21
22 export default App;

At this point, you can navigate to http://localhost:3000 to see it in action:

Create Short Link UI

Note that it’s not perfect as we need to reload the page manually each time a new link is created.
We could use the GraphQL subscriptions to handle this.
Queries, Mutations and Subscriptions 24

2.3 Using GraphQL Subscriptions


In addition to queries and mutations, GraphQL also supports subscriptions. With subscriptions, you
can push data from the server to any clients that want to listen. A typical scenario for subscriptions
is notifying clients about certain events, such as creation or deletion of an object or updated fields.
Install the dependencies first:
yarn add apollo-link-ws subscriptions-transport-ws

Next, we create a web socket link and use the Subscriptions API endpoint for the Graphcool service
(run graphcool info to get to the endpoints). With both links set, we are going to use split and
getMainDefinition functions from Apollo to check what type of operation is being used (e.g. a
query, mutation or a subscription) and return the correct link. Here’s how the split function looks
like (explained in more details here):

1 import { WebSocketLink } from 'apollo-link-ws';


2 import { split } from 'apollo-link';
3 import { HttpLink } from 'apollo-link-http';
4 import { getMainDefinition } from 'apollo-utilities';
5
6 const wsLink = new WebSocketLink({
7 uri: 'wss://subscriptions.us-west-2.graph.cool/v1/[SERVICE_ID]',
8 options: {
9 reconnect: true
10 }
11 });
12
13 const httpLink = new HttpLink({
14 uri: 'https://api.graph.cool/simple/v1/[SERVICE_ID]'
15 });
16
17 const link = split(
18 ({ query }) => {
19 const { kind, operation } = getMainDefinition(query);
20 return kind === 'OperationDefinition' && operation === 'subscription';
21 },
22 wsLink,
23 httpLink
24 );

Once you have this in your index.js file, you can use the link variable when creating the Apollo
Client instance:
Queries, Mutations and Subscriptions 25

1 const client = new ApolloClient({


2 link,
3 cache: new InMemoryCache(),
4 });

At this point everything should still work they way it did before — you need to create and actually
use the subscription in the LinkList component where we are showing all links.
Here is the query for subscription that triggers each time a new link is created - add this subscription
to the LinkList.js file:

1 const LINKS_SUBSCRIPTION = gql`


2 subscription NewLinkCreatedSubscription {
3 Link(filter: { mutation_in: [CREATED] }) {
4 node {
5 id
6 url
7 description
8 hash
9 }
10 }
11 }
12 `;

As with mutations and queries, we provide a name for the subscription and define the filter we
want — in our case, we want to get back all fields when new Link object is created.
We are going to use a function called subscribeToMore on the allLinksQuery prop to subscribe to
updates. This method takes two arguments: first one is the subscription query we defined above
and the second one is an updateQuery function that gets called each time a new link is created.
This function takes the previous state and data that was sent from the subscription and we need
to merge the new data with the previous state and return the updated state. Let’s implement this
in the componentDidMount function in the LinkList component - add the code below to inside the
LinkList class in the src/components/LinkList.js file:
Queries, Mutations and Subscriptions 26

1 componentDidMount() {
2 this.props.allLinksQuery.subscribeToMore({
3 document: LINKS_SUBSCRIPTION,
4 updateQuery: (prev, { subscriptionData }) => {
5 const newLinks = [
6 ...prev.allLinks,
7 subscriptionData.data.Link.node,
8 ];
9 const result = {
10 ...prev,
11 allLinks: newLinks,
12 };
13 return result;
14 },
15 });
16 }

One last thing we need to do to make this work correctly is to update the fetchPolicy to
network-only when querying for link count in the src/components/CreateShortLink.js file. Since
we are using the subscription we don’t have to refresh the page anymore to see the updates — not
refreshing the page also means that each time we execute the link count query will get the cached
number and our hashes will be incorrect. Add the fetchPolicy to the linkCountQuery variable in
the createShortLink function inside src/components/CreateShortLink.js:

1 const linkCountQuery = await this.props.client.query({


2 query: GET_LINK_COUNT_QUERY,
3 fetchPolicy: 'network-only',
4 });

Network-only option tells Apollo to not use the cached values and always re-query. We will use the
same policy once we add authentication.
Ok, let’s try this out now! Go to http://localhost:3000 and see that as soon as you add a link, the list
of links gets updated automatically.
III Serverless Functions
This chapter talks about different function types that Graphcool service supports and explains how
to use them for implementing our hashing function.
3. Creating Serverless Hashing
Function
In this chapter we are going to take the hashing function we implemented in the previous post and
put it into a Graphcool function.
Functions get defined in the Graphcool service definition file (graphcool.yml). Graphcool supports a
couple of different function types that can be invoked either as webhooks or as managed functions.

3.1 Before and After Operations


For this to work, you need to specify the operation on a model for which the function should get
invoked. For example Link.update, Link.delete or Link.create. An example of when this type
could get used is if we wanted to implement a feature that prevents certain users from creating more
than X number of links. We could use the operationBefore on Link.create and run a function that
checks how many links the user already created and either throw an error or allow the creation to
happen.

3.2 Subscriptions
Subscription requires a subscription query that determines when the function should be triggered
(e.g. when an object gets created, updated or deleted). The backing function gets executed after the
mutation happens. An example of this could be if we wanted to send a welcome email each time a
new user signs-up.

3.3 Resolvers
Resolvers are the most powerful types as they allow you to come up with a new mutation and/or
query definition. We will use resolvers in the upcoming chapter where we will create a mutation
for signing up and logging in users as well as a query that returns a logged in user.

3.4 Choosing the Function Type


We could potentially use all of the options above to implement our hashing function. In short, here’s
how we’d do it:
Creating Serverless Hashing Function 29

Option 1: Using operationBefore/operationAfter


We could use either of these two options. We would need to modify the CreateLinkMutation in
CreateShortLink.js to remove the $hash variable as well as make the hash field optional in the
schema. Next, we’d move the createHash function to the backend to either update the created link
with a hash (if we use operationAfter) or create a hash and return it for the mutation to complete if
we use operationBefore.

Option 2: Using a subscription


Just like we the above option, we need to remove the $hash variable from the CreateLinkMutation
and write a subscription query that fires when new Link is created — we already have that query, so
we could update it like this:

1 subscription NewLinkCreatedSubscription {
2 Link(filter: { mutation_in: [CREATED] })
3 node {
4 id
5 }
6 }
7 }

In the handler function, we’d get the data returned from the subscription (id), next we’d get the
count of all links, create a hash and finally update the existing Link with this mutation:

1 mutation UpdateLinkWithHash($id: String!, $hash: String!) {


2 updateLink(id: $id, hash: $hash) {
3 id
4 }
5 }

Option 3: Using a resolver


With a resolver, we would extend our existing API with a new mutation called createHashMutation.
The function that would be executed when this mutation is called would go through similar things
as if we’d use a subscription. Function would get the number of created links, generate a hash and
then return that generated hash. Then, we could call our existing CreateLinkMutation and pass in
the hash we from the createHashMutation.
Since we will use a resolver for authenticating users later in the book, let’s use a subscription to
implement this — option 2 it is!
Creating Serverless Hashing Function 30

3.5 Using Subscription to Implement the Hash


Function
Follow the steps below to create a subscription and move the hashing function to the backend.

1. Create the graphcool/src folder from your project root.


2. Create a graphcool/src/createShortLink.graphql file with the subscription defined below.
This subscription fires each time a link is created and returns its id.

1 ~~~~~~~~
2 subscription {
3 Link(filter: { mutation_in: [CREATED] }) {
4 node {
5 id
6 }
7 }
8 }
9 ~~~~~~~~

3. Create a graphcool/src/createShortLink.js file that fires off each time a new link is created.

graphcool/src/createShortLink.js

1 const { fromEvent } = require('graphcool-lib');


2
3 const createHash = itemCount => {
4 let hashDigits = [];
5 // dividend is a unique integer (in our case, number of links)
6 let dividend = itemCount + 1;
7 let remainder = 0;
8 while (dividend > 0) {
9 remainder = dividend % 62;
10 dividend = Math.floor(dividend / 62);
11 hashDigits.unshift(remainder);
12 }
13 const alphabetArray = `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456\
14 789`.split(
15 ''
16 );
17 // Convert hashDigits to base62 representation
18 let hashString = '';
19 let i = 0;
20 while (hashDigits.length > i) {
Creating Serverless Hashing Function 31

21 hashString += alphabetArray[hashDigits[i]];
22 i++;
23 }
24 return hashString;
25 };
26
27 module.exports = async event => {
28 // Get the data from the event - the data
29 // is determined by the subscription. In our case, it will look like this:
30 // event = {
31 // "data": {
32 // "Link": {
33 // "node": {
34 // "id": "LINK_ID"
35 // }
36 // }
37 // }
38 // }
39 const { id } = event.data.Link.node;
40
41 const graphcool = fromEvent(event);
42 const api = graphcool.api('simple/v1');
43
44 // 1. Get the link count.
45 const getLinkCountQuery = `
46 query GetLinkCountQuery {
47 links: _allLinksMeta {
48 count
49 }
50 }`;
51
52 const linkCountQueryResult = await api.request(getLinkCountQuery);
53 const linkCount = linkCountQueryResult.links.count;
54
55 // 2. Get the hash.
56 const hash = createHash(linkCount);
57
58 // 3. Update the link with a hash.
59 const updateLinkMutation = `
60 mutation ($id: ID!, $hash: String!) {
61 updateLink(id: $id, hash: $hash) {
62 id
63 }
Creating Serverless Hashing Function 32

64 }`;
65
66 const variables = { id, hash };
67 await api.request(updateLinkMutation, variables);
68
69 return {
70 data: {
71 success: true
72 }
73 };
74 };

1. Install the dependencies for subscription function by running the following command from the
graphcool folder where package.json file is:

1 `npm install graphcool-lib --save`

2. Update the graphcool.yml to reference the .graphql and .js file we created:

1 functions:
2 createShortLink:
3 type: subscription
4 query: src/createShortLink.graphql
5 handler:
6 code: src/createShortLink.js

3. Deploy the changes to create the subscription by running graphcool deploy from the
graphcool folder:

1 $ graphcool deploy
2 ...
3 Subscription Functions
4 createShortLink
5 + A new subscription with the name `createShortLink` is created.

There are a couple of small things we need to take care of before we try out the subscription:

1. Remove the createHash function, $hash variable from the mutation and GET_LINK_COUNT_QUERY
from src/components/CreateShortLink.js. Here’s how the updated file should look like:
Creating Serverless Hashing Function 33

src/components/CreateShortLink.js
1 import React, { Component } from 'react';
2
3 import gql from 'graphql-tag';
4 import { graphql, withApollo } from 'react-apollo';
5
6 const CREATE_SHORT_LINK_MUTATION = gql`
7 mutation CreateLinkMutation($url: String!, $description: String!) {
8 createLink(url: $url, description: $description) {
9 id
10 }
11 }
12 `;
13
14 class CreateShortLink extends Component {
15 constructor(props) {
16 super(props);
17 this.state = {
18 description: '',
19 url: ''
20 };
21 }
22
23 createShortLink = async () => {
24 const { url, description } = this.state;
25 await this.props.createShortLinkMutation({
26 variables: {
27 url,
28 description
29 }
30 });
31 };
32
33 render() {
34 return (
35 <div>
36 <input
37 id="url"
38 type="text"
39 value={this.state.url}
40 placeholder="Link URL"
41 onChange={e => this.setState({ url: e.target.value })}
42 />
Creating Serverless Hashing Function 34

43 <input
44 id="description"
45 type="text"
46 value={this.state.description}
47 placeholder="Link description"
48 onChange={e => this.setState({ description: e.target.value })}
49 />
50 <button onClick={() => this.createShortLink()}>Create</button>
51 </div>
52 );
53 }
54 }
55
56 export default graphql(CREATE_SHORT_LINK_MUTATION, {
57 name: 'createShortLinkMutation'
58 })(withApollo(CreateShortLink));

1. Make the hash field in types.graphql optional, by removing the exclamation mark:

1 type Link @model {


2 id: ID! @isUnique
3 hash: String
4 url: String!
5 description: String
6 }

2. Run the graphcool deploy command again to update the type and then you are ready to test
it out!

Open up the web site (yarn start) if you don’t have it running already and try to create a new
link — everything should work exactly like before, the only difference is that the hashing function is
now being invoked as part of a subscription on the Link. To really make sure that this works, you
can run graphcool logs to get the invocation logs from your function(s). A sample log should looks
like this:
Creating Serverless Hashing Function 35

Logs from the function

1 2019-03-23T21:40:31.746Z 1675ms SUCCESS {


2 "event": {
3 "data": {
4 "Link": {
5 "node": {
6 "id": "cjtm0p3030bdn0143aldqkfmu"
7 }
8 }
9 },
10 "context": {
11 "request": {
12 "sourceIp": "",
13 "headers": {},
14 "httpMethod": "post"
15 },
16 "auth": null,
17 "sessionCache": {},
18 "environment": {},
19 "graphcool": {
20 "rootToken": "REDACTED",
21 "endpoints": {
22 "simple": "https://api.graph.cool/simple/v1/REDACTED",
23 "relay": "https://api.graph.cool/relay/v1/REDACTED",
24 "system": "https://api.graph.cool/system",
25 "subscriptions": "wss://subscriptions.us-west-2.graph.cool/v1/REDACTED"
26 },
27 "projectId": "REDACTED",
28 "alias": null,
29 "pat": "REDACTED",
30 "serviceId": "REDACTED"
31 }
32 }
33 },
34 "logs": [],
35 "returnValue": {
36 "data": {
37 "success": true
38 }
39 }
40 }
Creating Serverless Hashing Function 36

If there are any errors that occur during function invocation, you should be able to see them from
the logs as well.

3.6 Testing the Function


Second option for looking at the logs and function invocations is to go to the http://graph.cool, click
on the Open Console button then Functions and finally click on the createShortLink function to
get all the logs.

Function logs from the Graphcool website

Another helpful command for testing the functions is the graphcool invoke-local command from
the CLI. Instead of going through the web site to create new links, you can directly send a JSON
payload to the function like this:
Creating Serverless Hashing Function 37

graphcool invoke-local --function createShortLink --json event.json

Where event.json could look like this:

1 {
2 "data": {
3 "Link": {
4 "node": {
5 "id": "LINK_ID"
6 }
7 }
8 }
9 }

This way you can test your function for bunch of scenarios that might be hard to test through the
web site — e.g. invalid payloads, non-existent link IDs, etc.
In the next chapter we will work on getting some short URL stats — number of clicks. In order to do
that, we will have to come up with a handler for links. For example if we generate hash “ABC123”, we
will need to write some code that’s going to execute when user visits “http://localhost:3000/ABC123”,
and translate that hash to an actual link and redirect the user to it. Once we have the handler we
can start collecting click stats.
4. React Router
With the way project is currently, we can list and create short URLs, but we can’t do anything with
those short links yet. In this post we will set up routes to handle short links as well as start tracking
the number of clicks and updating the link list to show number of clicks in real time. Let’s get started!

4.1 Routing Setup


The routing library we are going to use is React Router (if you want to learn more in-depth about
routing and react router, check this great (free) training).
Let’s start with installing the react router dependency first:
yarn add react-router-dom

To make our code more readable, we are going to create a new file called src/AppRouter.js where
we implement our routing logic. In this file we are going to declare which component should render,
based on the URL that was requested. For example, if you navigate to root http://localhost:3000/ we
should render the App.js component. Since App.js will serve as our main/home page, let’s rename
it to Home.js - don’t forget to also rename the class name from App to Home.
Here’s how the AppRouter.js file with simple routing logic looks like:

1 import React from 'react';


2 import { Switch, BrowserRouter, Route } from 'react-router-dom';
3
4 import Home from './Home';
5
6 const AppRouter = () => (
7 <BrowserRouter>
8 <Route exact path="/" component={Home} />
9 </BrowserRouter>
10 );
11
12 export default AppRouter;

React router has two types of routers: a BrowserRouter and a HashRouter. The main difference
between the two is in a way they generate URLs. For example, this would be a BrowserRouter URL:
http://localhost:8080/login, while a HashRouter URL would look like this http://localhost:8080/#/login.
React Router 39

Inside of the BrowserRouter we define a single Route that renders some UI (component={Home}), if lo-
cation matches the routes path (path="/"). To put it even simpler: if I go to http://localhost:3000/,
router will render the Home component. For example, path with value /home translates to to
http://localhost:3000/home URL. Since BrowserRouter only supports a single child, we are going
to use a Switch later on.
To make use of the AppRouter we created, replace the <App /> component in index.js with
AppRouter:

1 ReactDOM.render(
2 withApolloProvider(<AppRouter />),
3 document.getElementById('root'),
4 );

If you run yarn start and go to http://localhost:3000/ you should get the exact same view as
before.
In order to handle short URLs and resolve them, we need to define another Route. So, if user
visits http://localhost:3000/shorturl, we need to get the shorturl part of the URL, figure out
the expanded URL by making a GraphQL query and redirecting the user to the full URL. If we don’t
find the full URL or if there’s an error, we will show a simple error message.
Since the short URLs are going to be dynamic, we will use a variable in the URL path to capture the
short URL hash. If the router matches the URL to this route, we are going to extract the shortUrl
and render a simple component that’s going to do a lookup and redirect to the full URL. Let’s create
a file called src/components/ShortLinkRedirect.js and implement the component. Here’s the code
for that component:
src/components/ShortLinkRedirect.js

1 import React from 'react';


2 import gql from 'graphql-tag';
3 import { graphql } from 'react-apollo';
4 import PropTypes from 'prop-types';
5
6 const GET_FULL_LINK_QUERY = gql`
7 query GetFullLink($hash: String!) {
8 allLinks(filter: { hash: $hash }) {
9 url
10 }
11 }
12 `;
13
14 const ShortLinkRedirect = ({ hash, data: { loading, error, allLinks } }) => {
15 if (error) {
React Router 40

16 return <div>Error occurred</div>;


17 }
18
19 if (loading) {
20 return <div>Loading ...</div>;
21 }
22
23 if (!allLinks || allLinks.length !== 1) {
24 return <div>No redirect found for '{hash}'</div>;
25 }
26 // TODO: increase the click count here.
27 window.location = allLinks[0].url;
28 return null;
29 };
30
31 ShortLinkRedirect.propTypes = {
32 hash: PropTypes.string
33 };
34
35 export default graphql(GET_FULL_LINK_QUERY, {
36 options: ({ hash }) => ({ variables: { hash } })
37 })(ShortLinkRedirect);

Let’s explain what’s happening in the code above. First, we are creating a new GraphQL query that
takes a $hash as an input and returns us an URL that matches that hash. In the ShortLinkRedirect
component we are passing in the hash and a couple of properties from the data object (this gets
injected by the graphql container on line 31). Similarly as in previous components, we do the
following (lines 15–28):

1. Check for any errors and return an error


2. Check if the query is still loading and return a loading message
3. Check if we got any results or not
4. Set the window.location to the full URL
5. Return null as we aren’t rendering anything

A thing to note on line 36 is how we’re extracting hash variable that’s being passed to the component
from the AppRouter.js file.
That said, we should define this /:hash route in the AppRouter.js file. Update AppRouter instance
like this:
React Router 41

1 import React from 'react';


2 import { Switch, BrowserRouter, Route } from 'react-router-dom';
3 import ShortLinkRedirect from './components/ShortLinkRedirect';
4 import Home from './Home';
5
6 const AppRouter = () => (
7 <BrowserRouter>
8 <Switch>
9 <Route exact path="/" component={Home} />
10 <Route
11 path="/:hash"
12 render={props => <ShortLinkRedirect hash={props.match.params.hash} />}
13 />
14 </Switch>
15 </BrowserRouter>
16 );
17
18 export default AppRouter;

With :hash we are indicating that we want to capture the URL parameter — we read that parameter
from props.match.params object and send it as a prop to ShortLinkRedirect, where it gets used as
an input variable to the GET_FULL_LINK_QUERY.
Try going to the web site and creating either a new short link or accessing a short link you’ve created
before. Anytime you visit http://localhost:3000/[HASH] we try to figure out the long URL for the
provided hash and redirect appropriately.

4.2 Tracking Clicks


Now that we have redirects working, we can think about how to store the number of clicks each
short link gets.
The simplest solution would be to add a field called Clicks to the existing Link type and use
the updateLink mutation to increment the number of clicks each link got. Even though it’s an ok
solution, it’s not very extensible in case we later decide that we want to track more things when
short link is clicked.
We will implement this using a new type (LinkStats) and we will use a relation to define a
connection between the link stats and links.
Let’s open the graphcool/types.graphql file, add a the LinkStats type and create a relation to the
Link type:
React Router 42

1 type Link @model {


2 id: ID! @isUnique
3 hash: String
4 url: String!
5 description: String
6 stats: LinkStats @relation(name: "LinkOnLinkStats")
7 }
8
9 type LinkStats @model {
10 id: ID! @isUnique
11 clicks: Int
12 link: Link @relation(name: "LinkOnLinkStats")
13 }

We created a stats field on Link type and added the @relation directive to define a relation to the
LinkStats type. We do a similar thing on the LinkStats type and we connect the field link back to
the Link type.
Let’s deploy these changes to Graph.cool by running graphcool deploy and then we can update the
code and queries to increment the click count and display the click counts in LinkList component.
With service changes deployed, let’s update the LinkList.js component first.

1. Update the ALL_LINKS_QUERY to include the number of clicks and an ID in the results:

1 const ALL_LINKS_QUERY = gql`


2 query AllLinksQuery {
3 allLinks {
4 id
5 url
6 description
7 hash
8 stats {
9 clicks
10 }
11 }
12 }`;

2. Update the render method in Link.js to get the number of clicks and show it next to the link:
React Router 43

1 render() {
2 const clickCount =
3 (this.props.link.stats && this.props.link.stats.clicks) || 0;
4 return (
5 <div>
6 <div>
7 {this.props.link.description} (<a
8 href={this.props.link.hash}
9 >
10 {this.props.link.hash}
11 </a>) --> clicks: {clickCount}
12 </div>
13 </div>
14 );
15 }

If you refresh the web site at this point, you should see the changes we added to the UI with number
of clicks on each link being 0. On to updating the code that increments the link count! First, we
need a simple mutation that’s going to take the id of the link and update the click count. Open the
ShortLinkRedirect.js and follow the steps below:

1. Update the GET_FULL_LINK_QUERY to return the id field and the clicks field as well:

1 const GET_FULL_LINK_QUERY = gql`


2 query GetFullLink($hash: String!) {
3 allLinks(filter: { hash: $hash }) {
4 id
5 url
6 stats {
7 id
8 clicks
9 }
10 }
11 }`;

2. Create the UPDATE_CLICK_COUNT_MUTATION for updating the click count:


React Router 44

1 const UPDATE_CLICK_COUNT_MUTATION = gql`


2 mutation UpdateClickCount($id: ID!, $clicks: Int!) {
3 updateLink(id: $id, stats: { clicks: $clicks }) {
4 id
5 }
6 }`;

3. Create the CREATE_LINK_STATS_MUTATION for creating new link stats (i.e. first time someone
clicks on a short link):

1 const CREATE_LINK_STATS_MUTATION = gql`


2 mutation CreateLinkStats($linkId: ID!, $clicks: Int!) {
3 createLinkStats(linkId: $linkId, clicks: $clicks) {
4 id
5 }
6 }`;

This should look familiar to previous queries we did — we are passing in the variables and calling
updateLink mutation to update the clicks. And in the second mutation we are creating new links
stats for the provided link.

1. Add another graphql container to the export statements and wrap them with compose:

1 export default compose(


2 graphql(UPDATE_CLICK_COUNT_MUTATION, { name: 'updateClickCount' }),
3 graphql(CREATE_LINK_STATS_MUTATION, { name: 'createLinkStats' }),
4 graphql(GET_FULL_LINK_QUERY, {
5 options: ({ hash }) => ({ variables: { hash } }),
6 }),
7 )(ShortLinkRedirect);

2. Update the ShortLinkRedirect function to pass in the updateClickCount and createLinkStats


functions, increment the click count and call the mutation function to update it. Here’s the full
ShortLinkRedirect.js file:
React Router 45

src/components/ShortLinkRedirect.js
1 import React from 'react';
2 import gql from 'graphql-tag';
3 import { graphql, compose } from 'react-apollo';
4 import PropTypes from 'prop-types';
5
6 const GET_FULL_LINK_QUERY = gql`
7 query GetFullLink($hash: String!) {
8 allLinks(filter: { hash: $hash }) {
9 id
10 url
11 stats {
12 id
13 clicks
14 }
15 }
16 }
17 `;
18
19 const UPDATE_CLICK_COUNT_MUTATION = gql`
20 mutation UpdateClickCount($id: ID!, $clicks: Int!) {
21 updateLinkStats(id: $id, clicks: $clicks) {
22 id
23 }
24 }
25 `;
26
27 const CREATE_LINK_STATS_MUTATION = gql`
28 mutation CreateLinkStats($linkId: ID!, $clicks: Int!) {
29 createLinkStats(linkId: $linkId, clicks: $clicks) {
30 id
31 }
32 }
33 `;
34
35 const ShortLinkRedirect = ({
36 updateClickCount,
37 createLinkStats,
38 hash,
39 data: { loading, error, allLinks }
40 }) => {
41 if (error) {
42 return <div>Error occurred: {error}</div>;
React Router 46

43 }
44
45 if (loading) {
46 return <div>Loading ...</div>;
47 }
48
49 if (!allLinks || allLinks.length !== 1) {
50 return <div>No redirect found for '{hash}'</div>;
51 }
52
53 const linkInfo = allLinks[0];
54
55 if (!linkInfo.stats) {
56 // Create new link stats
57 createLinkStats({
58 variables: {
59 linkId: linkInfo.id,
60 clicks: 1
61 }
62 });
63 } else {
64 let currentClicks = (linkInfo.stats && linkInfo.stats.clicks) || 0;
65
66 // Increment the click count
67 currentClicks++;
68
69 // Update the click count.
70 updateClickCount({
71 variables: {
72 id: linkInfo.stats.id,
73 clicks: currentClicks
74 }
75 });
76 }
77
78 // Navigate to the full URL
79 window.location = linkInfo.url;
80 return null;
81 };
82
83 ShortLinkRedirect.propTypes = {
84 hash: PropTypes.string
85 };
React Router 47

86
87 export default compose(
88 graphql(UPDATE_CLICK_COUNT_MUTATION, { name: 'updateClickCount' }),
89 graphql(CREATE_LINK_STATS_MUTATION, { name: 'createLinkStats' }),
90 graphql(GET_FULL_LINK_QUERY, {
91 options: ({ hash }) => ({ variables: { hash } })
92 })
93 )(ShortLinkRedirect);

At this point you can open http://localhost:3000 and you will see how the counter updates each
time a link is clicked.
IV User Authentication
This part talks about implementing user authentication - login and sign-up functionality - using
React and GraphQL mutations and queries.

4.3 Login and Sign-Up React Components


Let’s start with two simple components that we are going to use to sign-up and login to the site.

Login Component
Create the src/components/Login.js file with the following contents (we will come back in a bit to
implement the login function and wrap the component with necessary containers):
src/components/Login.js

1 import React, { Component } from 'react';


2
3 class Login extends Component {
4 constructor(props) {
5 super(props);
6 this.state = {
7 email: '',
8 password: ''
9 };
10 }
11
12 login = async () => {
13 // TODO: Login code here.
14 };
15
16 render() {
17 return (
18 <div>
19 <h2>Login to Shortly</h2>
49

20 <input
21 id="email"
22 type="text"
23 value={this.state.email}
24 placeholder="Email address"
25 onChange={e => this.setState({ email: e.target.value })}
26 />
27 <br />
28 <input
29 id="password"
30 type="password"
31 value={this.state.password}
32 placeholder="Password"
33 onChange={e => this.setState({ password: e.target.value })}
34 />
35 <br />
36 <button onClick={() => this.login()}>Login</button>
37 </div>
38 );
39 }
40 }
41
42 export default Login;

This will give us a very basic looking login component as shown below:
50

Basic Login Component

Sign-Up Component
The sign-up component looks pretty much the same as login:
src/components/Signup.js

1 import React, { Component } from 'react';


2
3 class Signup extends Component {
4 constructor(props) {
5 super(props);
6 this.state = {
7 email: '',
8 password: ''
9 };
10 }
11
12 signup = async () => {
13 // TODO: Signup code here.
14 };
15
16 render() {
51

17 return (
18 <div>
19 <h2>Join Shortly</h2>
20 <input
21 id="email"
22 type="text"
23 value={this.state.email}
24 placeholder="Email address"
25 onChange={e => this.setState({ email: e.target.value })}
26 />
27 <br />
28 <input
29 id="password"
30 type="password"
31 value={this.state.password}
32 placeholder="Password"
33 onChange={e => this.setState({ password: e.target.value })}
34 />
35 <br />
36 <button onClick={() => this.signup()}>Signup</button>
37 </div>
38 );
39 }
40 }
41
42 export default Signup;

In order to render these two new components, we need to slightly modify the AppRouter to say
that whenever someone requests /login or /signup path, we are going to render the corresponding
components. After you’ve imported both Login and Signup components to the AppRouter.js, add
the following two routes below the Home component route:

1 <Route exact path="/login" component={Login} />


2 <Route exact path="/signup" component={Signup} />

Start up yarn (yarn start) and navigate to /login and /signup to see the components render.
Now that we have the components in place, let’s go to the backend and bring in the email/password
auth.
52

4.4 Adding Auth Support to Graphcool Service


Graphcool has support for templates that allow you to pull in various functionality to your projects —
 there’s a whole GitHub repo of officially supported templates that include, for example, Twilio
integration, Mailgun integration (send text messages and emails as part of a subscription, in a
resolver function, etc.) and (good news for us) email/password authentication. Other interesting
auth related templates are Facebook auth, Google auth, Github, etc.
We are going to use the email-password authentication template that consists of three functions that
are described in more details below.

Signup
For signup we define a new mutation called signupUser that has this signature:
signupUser(email: String!, password: String!): SignupUserPayload

This is the mutation we are calling when user tries to sign up for an account. What happens as part
of this mutation is defined by our function that we define (similarly as we did in previous post where
we defined a function that handled a subscription). Here’s what the signup function does:

1. Validates the email address


2. Checks if the user with that email exists or not
3. Hashes the password
4. Creates a new user
5. Generates a token we use to identify the user

In the end, it returns an ID of a newly created user and an a token we will store locally. In addition
to the things above, this would be the place where we could add things such as sending a welcome
email, or making user confirm their email address etc.

Authenticate
Authenticate is another new mutation we define and it’s very similar in signature to the signup
mutation:
authenticateUser(email: String!, password: String!): AuthenticateUserPayload

This is the mutation that gets called when user is trying to log in to our website. As part of this
mutation we are going to check if the user with the provided email exists, and if it does, we ensure
the passwords match then finally generate token and return it.
53

Logged-in User
The last part of the template is a query that we can execute and it’s going to return us user ID
if the request itself contains a valid token. The idea is that we are going to execute this query on
components that require authentication and then before rendering the component we can check if
we got any user data back or not — if we didn’t we can redirect the user to the login page, and if we
did, we can continue and render the component.

4.5 Add the template


Let’s add the template to our service by running this command from our graphcool subfolder:
graphcool add-template graphcool/templates/auth/email-password

Above command will pull down the .graphql and .ts files that represent the mutations and
the functions we explained above. We still need to manually update the types.graphql and
graphcool.yml files to include these in our service. If you open any of those two files, you will
see that some commented out declarations were added to it — this is Graphcool CLI helping us to
define the functions and/or models.
Go ahead and uncomment the signup, authenticate and logeddInUser functions in graphcool.yml
as well as the User model in the types.graphql file.
Note: we will come back to the types.graphql file shortly and add the relation between User and
Link models, so don’t worry about that at the moment.
Let’s deploy the changes and make sure all is good — run graphcool deploy and after a couple of
seconds you should get the list of resolver functions and types that were created.
Next, let’s implement sign up and login functionality and then as a last step we will hook up the
relation between Links and Users.

4.6 Sign me up!


As explained above we need to call the signupUser mutation to sign the user up and store the token
we get back from the mutation.
This is the mutation we are going to add to src/components/Signup.js and use in the Signup
component:
54

1 const SIGNUP_USER_MUTATION = gql`


2 mutation SignupUser($email: String!, $password: String!) {
3 signupUser(email: $email, password: $password) {
4 id
5 token
6 }
7 }`;

We also need to wrap the component with graphql:

1 export default graphql(SIGNUP_USER_MUTATION, { name: 'signupUserMutation' })(


2 Signup,
3 );

Finally, we need to implement the signup function. Here’s the full implementation:

1 signup = async () => {


2 const { email, password } = this.state;
3 try {
4 const result = await this.props.signupUserMutation({
5 variables: {
6 email,
7 password,
8 },
9 });
10
11 // Store the ID and token in local storage.
12 localStorage.setItem('SHORTLY_ID', result.data.signupUser.id);
13 localStorage.setItem('SHORTLY_TOKEN', result.data.signupUser.token);
14 this.props.history.push('/');
15 } catch (err) {
16 // TODO: Handle the error properly
17 }
18 };

There’s nothing too complicated in the code above — we are calling the mutation then taking the ID
and token and storing it in the local storage. Finally, we redirect the user to home page.
Note: you probably noticed the lack of proper error handling — writing errors blindly to the console is
not good — make sure you always parse the errors and show the sanitized error message , instead of
full errors that could include a call stack and possibly information that shouldn’t be ever shared. To
fix this, you could add an error property to the state and update it with a generic error such as ‘Sign
up failed — try again’.
55

We can try out the signup now and see if it works — navigate to /signup and try entering an email
and a sample password — if all is good, you should get redirected to the home page. You can also
check that the user information was stored in the Graphcool service (tip: run graphcool playground
and click on Data).

4.7 Log me in!


Now that you have your account created, let’s modify the login component in a similar way we
updated the Signup component.
Here’s the mutation you will be using in the Login component:

1 const AUTHENTICATE_USER_MUTATION = gql`


2 mutation AuthUser($email: String!, $password: String!) {
3 authenticateUser(email: $email, password: $password) {
4 id
5 token
6 }
7 }`;

Similarly, we need to wrap the component inside the graphql:

1 export default graphql(AUTHENTICATE_USER_MUTATION, {


2 name: 'authenticateUserMutation',
3 })(Login);

Finally, implement the login function that’s pretty much identical with the sign up function
(difference being the mutation we call):

1 login = async () => {


2 const { email, password } = this.state;
3 try {
4 const result = await this.props.authenticateUserMutation({
5 variables: {
6 email,
7 password,
8 },
9 });
10 // Store the ID and token in local storage.
11 localStorage.setItem('SHORTLY_ID', result.data.authenticateUser.id);
12 localStorage.setItem(
13 'SHORTLY_TOKEN',
56

14 result.data.authenticateUser.token,
15 );
16 this.props.history.push('/');
17 } catch (err) {
18 // TODO: Handle errors properly here.
19 }
20 };

Try this out by going to the /login page and using the same email/password you used to sign up. If
you get redirected to the home page, it worked!
Issue we have to solve next is the fact that regardless if you’re logged in or not, when you navigate
to the home page, you’ll always get the list of links and ability to create links. We should change
that, so if you’re not logged in, we render something that gives you links to login and sign up page,
and if you are logged in, we will show you the links.

4.8 Add Authorization Token to Apollo


Remember that third query we added earlier, the loggedInUser query? This is what we are going to
use. But first, we need to make a change that’s going to attach the user token from the local storage
with each GraphQL request we make.
There’s a pattern in Apollo that allows us to do exactly that — using ApolloLink we can inject the
authorization token to each request.
After you’ve imported ApolloLink from apollo-link, we can create a new instance of it in the
index.js file:

1 const apolloLinkWithToken = new ApolloLink((operation, forward) => {


2 const token = localStorage.getItem('SHORTLY_TOKEN');
3 const authHeader = token ? `Bearer ${token}` : null;
4 operation.setContext({
5 headers: {
6 authorization: authHeader,
7 },
8 });
9 return forward(operation);
10 });
11
12 const httpLinkWithToken = apolloLinkWithToken.concat(httpLink);
13 ....
14 const link = split(
15 ({ query }) => {
57

16 const { kind, operation } = getMainDefinition(query);


17 return kind === 'OperationDefinition' && operation === 'subscription';
18 },
19 wsLink,
20 httpLinkWithToken,
21 );

We are defining an apolloLinkWithToken in the code above (lines 1–10), then reading our SHORTLY_-
TOKEN from the local storage, creating the auth header with the token (or null if token is not there) and
setting it as an authorization header. Finally, we call the forward function and pass in the updated
operation (with the authorization header) — this continues with the chain of calls and eventually the
request goes out to the server. Lastly, we need to update the link we are using in line 20 to be the
httpLinkWithToken we defined in line 12.

At this point, if we are logged in and we try to call the loggedInUser query we will get back the ID
of the logged in user. Let’s add this functionality to the Home.js component. First, this is the query
we are going to use:

1 const LOGGED_IN_USER_QUERY = gql`


2 query CurrentUser {
3 loggedInUser {
4 id
5 }
6 }`;

Notice that we don’t need to provide any parameters to the above query, but when the function
behind the query runs, it will check if there was an authorization header attached to it and figure
out which user ID corresponds to that token and return us the ID of that user (or nothing, if there
was no or invalid auth header provided).
Let’s wrap the component:
export default graphql(LOGGED_IN_USER_QUERY, { name: 'currentUser' })(Home);

And update the render method to render different UI based on the fact if user is logged in or not
as well as implement the logout functionality. Here’s the full Home.js file with all these things
implemented:
58

src/Home.js
1 import React, { Component } from 'react';
2 import LinkList from './components/LinkList';
3 import CreateShortLink from './components/CreateShortLink';
4 import gql from 'graphql-tag';
5 import { graphql } from 'react-apollo';
6
7 const LOGGED_IN_USER_QUERY = gql`
8 query CurrentUser {
9 loggedInUser {
10 id
11 }
12 }
13 `;
14
15 class Home extends Component {
16 logout = () => {
17 localStorage.removeItem('SHORTLY_ID');
18 localStorage.removeItem('SHORTLY_TOKEN');
19 this.props.history.push('/');
20 };
21
22 render() {
23 if (this.props.currentUser && this.props.currentUser.loading) {
24 return <div>Loading ... </div>;
25 }
26
27 const userId =
28 this.props.currentUser.loggedInUser &&
29 this.props.currentUser.loggedInUser.id;
30
31 if (userId) {
32 return (
33 <div>
34 Hi user <b>{userId}</b> (
35 <button onClick={() => this.logout()}>logout</button>)
36 <br />
37 <div>
38 <h2>Create a short link</h2>
39 <CreateShortLink />
40 </div>
41 <div>
42 <h2>All links</h2>
59

43 <LinkList />
44 </div>
45 </div>
46 );
47 } else {
48 return (
49 <div>
50 Please <a href="/login">login</a> or <a href="/signup">sign up</a>!
51 </div>
52 );
53 }
54 }
55 }
56
57 export default graphql(LOGGED_IN_USER_QUERY, { name: 'currentUser' })(Home);

In the render method we wait for the query to execute and then try to get the userId from the
query (line 27). Finally, we use a simple if statement to check if we got the user id, then render the
component with the logout button and the list of links, otherwise we render a div with links to login
and sign up page. Figure below shows the view with login and sign up links as well as the view
when user is logged in.
60

Login and Sign up links


61

Logged-in user information

This looks much better (from the functional standpoint, not the design standpoint). Last thing left is
to actually add the relation between User and Link models and modify the queries a bit, so we only
display links from the logged in users.

4.9 Linking Data Models


Let’s open the types.graphql file and connect the Link and User models together with the @relation
keyword:
62

1 type Link @model {


2 ...
3 createdBy: User @relation(name: "UserLinks")
4 ...
5 }
6 type User @model {
7 ...
8 links: [Link!]! @relation(name: "UserLinks")
9 ...
10 }

The above changes are adding a createdBy field to each link that points to the User that created the
link, and a field in User model that gives us an array of all links that the user created.
Since schema changes involve new relations with required values, I am suggesting you delete all
links and users you’ve created so far testing this, then run graphcool deploy to deploy the updated
schema. Alternatively, you can run graphcool deploy --force.
We can start updating the queries now. Let’s start with src/components/CreateShortLink.js and
CREATE_SHORT_LINK_MUTATION by including the createdBy field in the query:

1 const CREATE_SHORT_LINK_MUTATION = gql`


2 mutation CreateLinkMutation(
3 $url: String!
4 $description: String!
5 $createdById: ID!) {
6 createLink(
7 url: $url
8 description: $description
9 createdById: $createdById) {
10 id
11 }
12 }`;

Next, we can pass the user ID we read from the local storage to the mutation in createShortLink
function:
63

1 await this.props.createShortLinkMutation({
2 variables: {
3 url,
4 description,
5 createdById : localStorage.getItem('SHORTLY_ID')
6 },
7 });

Note: we could have also added the loggedInUser query to the component and use the user ID
from that query to create a link. An even better solution would probably be to create a container
called LoggedInContainer and use that to wrap all components that we want to use when user is
logged in. That way, we do the loggedInUser query on the container level and not separately in each
component.

Next up are the queries ALL_LINKS_QUERY and LINKS_SUBSRIPTION in src/components/LinkList.js


file.
In the ALL_LINKS_QUERY we need to filter all links to only those that belong to the logged in user by
adding a filter like this:

1 const ALL_LINKS_QUERY = gql`


2 query AllLinksQuery($createdById: ID!) {
3 allLinks(filter: { createdBy: { id: $createdById } }) {
4 ...
5 }
6 }`;

Since we added the createdById field to the filter, we need to update the export statement to pass
the id from the local storage to the ALL_LINKS_QUERY like this:

1 export default graphql(ALL_LINKS_QUERY, {


2 name: 'allLinksQuery',
3 options: props => ({
4 variables: {
5 createdById: localStorage.getItem('SHORTLY_ID'),
6 },
7 }),
8 })(LinkList);

Also, we need to update the filter on our subscription, so it only fires when the user currently logged-
in creates or updates a link. Here’s the updated filter in LINKS_SUBSCRIPTION variable:
64

1 subscription NewLinkCreatedSubscription($createdById: ID!) {


2 Link(
3 filter: {
4 mutation_in: [CREATED, UPDATED]
5 node: { createdBy: { id: $createdById } }
6 })
7 ...

We also need to pass the createdById variable to the subscription when we call subscribeToMore
function in the componentDidMount. Here’s the line to add after the document property:
variables: { createdById: localStorage.getItem('SHORTLY_ID') }

Similarly as with previous queries, we read the user ID from the local storage and pass it as a
createdById variable to the document (GraphQL query).
At this point you can create multiple users, create links for each of them and verify that you only
see the links from the logged in user.
About The Author
Peter Jausovec is a software engineer with more than ten years of experience in the field of software
development and tech. During his career, he spent time in various roles, starting with QA then
moving to software engineering and leading tech teams. His early career was mostly focused on
developer and cloud tooling; however, in recent years he has been focusing on developing distributed
systems cloud-native solutions.
You can can contact him on Twitter.

You might also like