Photo by Carlos Muza ( ) https://unsplash.com/@kmuza Table of Contents Part I: Setup and displaying short URLs using GraphQL Part II: Creating short URLs Part III: Creating serverless function for hashing Part IV: Short URL stats (number of clicks) (this post) Part V: User authentication Welcome back! If you want to start following along with this post, you can check out the GitHub repo for the project code, or start from the first part. 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! Routing 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 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 we should render the component; since will server as our main/home page, let’s rename it to . AppRouter.js http://localhost:3000/ App.js App.js Home.js Here’s how the file with simple routing logic looks like: AppRouter.js React router has two types of routers: a and a . The main difference between the two is in a way they generate URLs. For example, this would be a URL , while URL would look like this . BrowserRouter HashRouter BrowserRouter [http://localhost:8080/login](http://localhost:8080/login) HashRouter [http://localhost:8080/#/login](http://localhost:8080/#/login.) Inside of the we define a single that renders some UI ( ) if location matches the routes path ( ). To put it even simpler: if I go to , router will render the component. For example, path with value translates to to URL. Since only supports a single child, we are going to use a later on. BrowserRouter Route component={Home} path=”/” [http://localhost:3000/](http://localhost:3000/) Home **/home** [http://localhost:3000**/home**](http://localhost:3000/home) BrowserRouter Switch Finally, we need to replace component in with : <App /> index.js AppRouter ReactDOM.render(withApolloProvider( ),document.getElementById('root'),); <AppRouter /> If you run and go to you should get the exact same view as before. yarn start [http://localhost:3000/](http://localhost:3000/) In order to handle short URLs and resolve them, we need to define another . So, if user visits , we need to get the part of the URL, figure out the expanded URL by making a GraphQL query and redirecting to the full URL. If we don’t find the full URL or if there’s an error, we show a simple error message. Route [http://localhost:3000/**shorturl**](http://localhost:3000/shorturl) **shorturl** 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 and implement the component. Here’s the code for that component: src/components/ShortLinkRedirect.js Let’s explain what’s happening in the code above. First, we are creating a new GraphQL query that takes a as an input and returns us an URL that matches that hash. In the component we are passing in the hash and a couple of properties from the data object (this gets injected by the container in line 31). Similarly as in previous components, we do the following (lines 15–28): $hash ShortLinkRedirect graphql Check for any errors and return an error Check if the query is still loading and return a loading message Check if we got any results or not Set the to the full URL window.location Return as we aren’t rendering anything null A thing to note in line 36 is how we’re extracting variable that’s being passed to the component from the AppRouter.js file: hash <Routepath="/:hash"render={props => (<ShortLinkRedirect hash={props.match.params.hash} />)}/> With we are indicating that we want to capture the URL parameter — we read that parameter from object and send it as a prop to , where it gets used as an input variable to the . :hash props.match.params ShortLinkRedirect 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 we try to figure out the long URL for the provided hash and redirect appropriately: [http://localhost:3000/[HASH]](http://localhost:3000/[HASH],) Short URL redirects in action! 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 to the existing 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. Clicks Link We will implement this using a new type ( ) and we will use a relation to define a connection between the link stats and links. LinkStats Let’s open the file, add a the type and create a relation to the type: graphcool/types.graphql LinkStats Link We created a stats field on type and added the directive to define a relation to the type. We do a similar thing on the type and we connect the field back to the type. Link @relation LinkStats LinkStats link Link Let’s deploy these changes to Graph.cool by running and then we can update the code and queries to increment the click count and display the click counts in component. graphcool deploy LinkList With service changes deployed, let’s update the ListList.js component first Update the to include the number of clicks and an ID in the results (updates are in ): ALL_LINKS_QUERY bold const ALL_LINKS_QUERY = gql`query AllLinksQuery {allLinks {idurldescriptionhash }}`; stats {clicks} 2. Update the render method in to get the number of clicks and show it next to the link: Link.js 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 and follow the steps below: ShortLinkRedirect.js Update the to return the field and the field as well (updates are in bold): GET_FULL_LINK_QUERY id clicks const GET_FULL_LINK_QUERY = gql`query GetFullLink($hash: String!) {allLinks(filter: { hash: $hash }) {idurlstats {idclicks}}}`; 2. Create the for updating the click count: UPDATE_CLICK_COUNT_MUTATION const UPDATE_CLICK_COUNT_MUTATION = gql`mutation UpdateClickCount($id: ID!, $clicks: Int!) {updateLink(id: $id, stats: { clicks: $clicks }) {id}}`; 3. Create the for creating new link stats (i.e. first time someone clicks on a short link): CREATE_LINK_STATS_MUTATION const CREATE_LINK_STATS_MUTATION = gql`mutation CreateLinkStats($linkId: ID!, $clicks: Int!) {createLinkStats(linkId: $linkId, clicks: $clicks) {id}}`; This should look familiar to previous queries we did — we are passing in the variables and calling mutation to update the clicks. And in the second mutation we are creating new links stats for the provided link. updateLink 4.. Add another container to the export statements and wrap them with : graphql compose export default , graphql(GET_FULL_LINK_QUERY, {options: ({ hash }) => ({ variables: { hash } }),}),)(ShortLinkRedirect); compose(graphql(UPDATE_CLICK_COUNT_MUTATION, { name: 'updateClickCount' }) graphql(CREATE_LINK_STATS_MUTATION, { name: 'createLinkStats' }), 4. Update the function to pass in the and functions, increment the click count and call the mutation function to update it. Here’s the full file: ShortLinkRedirect updateClickCount createLinkStats ShortLinkRedirect.js That’s it! (Well, almost). If you try and access any of the short links, you’ll notice that the click count doesn’t refresh automatically; this is because we are only subscribed to new link created mutation. Unfortunately, we have to use a workaround at this point (first one in the whole series!!), because updates on relations are not supported at the moment yet (see for the discussion on this issue). here Updating the subscription The workaround is fairly simple: we will add a field to the type and update it each time we update the click count. dummy Link Open and add a field to the Link type and deploy the changes ( ). types.graphql dummy:String graphcool deploy In , set a value to the field in : ShortLinkRedirect.js dummy UPDATE_CLICK_COUNT_MUTATION updateLink(id: $id, , stats: { clicks: $clicks }) dummy: "dummy" 3. Open the file and update the query to include the event: LinkList.js LINKS_SUBSCRIPTION UPDATED ...Link(filter: { mutation_in: [CREATED, ] }) {... UPDATED 4. Add a check to in to ensure we don’t add the links twice. Since subscription is firing now on both created and updated events, we need to update the code so it doesn’t just blindly append links to the previous list. We need to check if the item that was updated is already in the list of links (i.e. click count was updated) and just return the list of previous links without doing any merges. Here’s the snippet to add at the top of : updateQuery componentDidMount updateQuery if (prev.allLinks.find(l => l.id === subscriptionData.data.Link.node.id)) {return prev;} Yay! Let’s see this in action: Auto-refresh the click count with a workaround. Great success! If you want to get the latest code, check the . GitHub repo For the next post, I might skip the link expiry feature and just dive straight into adding the user authentication (login, signup, etc.). After that, I was thinking of going through the exercise of actually deploying this — dockerizing the web site, maybe creating some tests and CI/CD pipeline, setting up SSL etc. Let me know if this is something that would interest you! Thanks for Reading! Any feedback on these series is more than welcome! You can also follow me on and . If you liked this and want to get notified when other parts are ready, you should subscribe to ! Twitter GitHub my newsletter