Next.js - The Data Story
Tags: nextjs, serverless, webdev, javascript
Published: 2020-10-22 14:12:06

---
title: Next.js - The Data Story
published: true
description: In this post you'll learn how to make both public and authenticated API calls in a Next.js app in client and static pages as well as server and API routes.
tags: nextjs, serverless, webdev, javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/trwx62xlur669n2m42mn.jpg
series: Building Scalable Next.js Apps on AWS
---

In my [previous post](https://dev.to/dabit3/the-complete-guide-to-next-js-authentication-2aco) I covered how to implement authentication in depth using Next.js with AWS.

In this post, we'll take the next steps to talk about the data story, how it fits in to the picture, and how to implement various access patterns with and without authentication and authorization.

During this post you'll be building a blogging app that will enable both public and private data access – fetching data on both the client as well as server and API routes.

> The final code for this app is located [here](https://github.com/dabit3/nextjs-data-story)

## Overview

When making requests to an API you often need to deal with security - managing [ID tokens](https://nhsconnect.github.io/national-authentication/TechOverview_Artefacts.html#id-token), [access tokens](https://nhsconnect.github.io/national-authentication/TechOverview_Artefacts.html#access-token), and [refresh tokens](https://nhsconnect.github.io/national-authentication/TechOverview_Artefacts.html#refresh-token) as well as maintaining application and UI state based on the user session (or lack thereof). You also often need to have a combination of public and private API access for your data layer.

Combining access control, authentication, and authorization is often difficult to get right and do so in a secure manner.

Understanding how to enable and mix authorization modes allows you to have flexibility when building modern applications – most of which require multiple authorization modes as well as data access patterns.

When working with Next.js, you will typically be making a combination of API calls from client, server, and API routes. With the recent release of [SSR Support](https://aws.amazon.com/blogs/mobile/ssr-support-for-aws-amplify-javascript-libraries/) covered in the [last post](https://dev.to/dabit3/the-complete-guide-to-next-js-authentication-2aco), one of the things that Amplify now enables is the seamless integration of all of these mechanisms on both the client and the server.

When making API calls via REST or GraphQL, Amplify now automatically configures and sends the proper authorization headers on both the client and the server (when necessary) when SSR mode is enabled.

This tutorial is meant to show how all of this works and provide a step-by-step guide for implementing data fetching for the following use cases:

1. Making a public client-side API call
2. Making an authenticated client-side API call
3. Hydrating a statically generated page with a public API call (via `getStaticPaths` and `getStaticProps`)
4. Making an authenticated API call from an SSR or API route
5. Creating an API route for a public API endpoint into your data layer

## Amplify data fetching

When creating or configuring an AppSync GraphQL API using Amplify, you have the ability to enable multiple authorization modes (a default mode as well as additional modes). This allows your app to incorporate private, public, or combined public and private access. In this tutorial we'll be covering how to implement a combination of public and private access using a single GraphQL API.

Once the API is created, you can make calls to the API using either the default authorization mode or by specifying the authorization mode.

### Here are a few examples

Public API calls using default authorization mode (client-side, static, SSR, & API routes):
```js
import { API } from 'aws-amplify';
import { listPosts } from './graphql/queries';

const data = await API.graphql({
  query: listPosts
});
```

Specifying a custom authorization mode (client-side):
```js
import { API } from 'aws-amplify';
import { listPosts } from './graphql/queries'

const data = await API.graphql({
  query: listPosts,
  authMode: "AMAZON_COGNITO_USER_POOLS"
});
```

Making authenticated request with authorization headers (SSR):
```js
import { withSSRContext } from 'aws-amplify';
import { listPosts } from './graphql/queries'

export async function getServerSideProps(context) {
  const { API } = withSSRContext(context);
  const data = await API.graphql({
    query: listPosts,
    authMode: "AMAZON_COGNITO_USER_POOLS"
  });
  // do stuff with data
}
```

Making authenticated request with authorization headers (API routes):
```js
import { withSSRContext } from 'aws-amplify';
import { listPosts } from './graphql/queries'

export default function handler(req, res) {
  const { API } = withSSRContext({ req });
  const data = await API.graphql({
    query: listPosts,
    authMode: "AMAZON_COGNITO_USER_POOLS"
  });
  // do stuff with data
}
``` 

### About the app

In this tutorial we'll be building a basic blogging app. Users will be able to sign up, create posts, and comment on posts. Users who are not signed in will only be able to view posts.

To demonstrate public and private access, we'll only allow users who are signed in to be able to create or view post comments.

## Getting started

If you've already completed building the app from part 1, continue to [creating the API](#creating-the-api).

If not, follow these steps to deploy the Next app with authentication enabled:

1\. Clone the repo

```sh
git clone https://github.com/dabit3/next.js-authentication-aws.git
```

2\. Change into the directory and install the dependencies

```sh
cd next.js-authentication-aws

npm install
```

3\. Initialize the Amplify project

```sh
amplify init
```

4\. Deploy the authentication service

```sh
amplify push --y
```

5\. Run the app locally

```sh
npm run dev
```

## Creating the API

Next, create a new GraphQL API using the `api` category:

```sh
amplify add api

? Please select from one of the below mentioned services: GraphQL
? Provide API name: nextapi
? Choose the default authorization type for the API: API key
? Enter a description for the API key: public
? After how many days from now the API key should expire: 365
? Do you want to configure advanced settings for the GraphQL API: Yes
? Configure additional auth types? Yes
? Choose the additional authorization types you want to configure for the API: Amazon Cognito User Pool
? Configure conflict detection? No
? Do you have an annotated GraphQL schema? N
? Choose a schema template: Single object with fields
? Do you want to edit the schema now? Y
```

When prompted, use the following GraphQL schema.

__amplify/backend/api/nextapi/schema.graphql__

{% gist https://gist.github.com/dabit3/31edec32294575594b054bbe6df00c4b %}

The API created from this schema will allow us to save and query for two different types of data:

1. __Posts__ that can be viewed publicly but only edited or deleted by the creator of the post.
2. __Comments__ that can be viewed publicly but only edited or deleted by the creator of the comment.

The schema leverages the [GraphQL Transform](https://docs.amplify.aws/cli/graphql-transformer/directives) library of Amplify to generate queries and mutations for __create__, __read__, __update__, __delete__, and __list__ operations for `Post`s and `Comment`s as well as creating subscriptions for each mutation and a database for each type (DynamoDB).

We also specify a custom data access pattern that allows us to query comments by post ID (`commentsByPostId`).

To deploy the API, run the `push` command:

```sh
amplify push --y
```

Once your API is deployed you should now be able to use it in your app.

## Creating the blog app

The first thing we'll do is create a reusable Amplify configuration that enables SSR (Note - this is only needed for some SSR or API routes, not client routes). Create a file at the root of the app called __configureAmplify.js__.

{% gist https://gist.github.com/dabit3/9b9dc001b65d505e95ee3ad95a7ddd8e %}

We can now just import this wherever we need to configure Amplify.

### helpers/checkUser.js

Next we'll create a reusable React hook that will allow us to easily manage user state across all of the components and pages.

Create a folder called __helpers__ in the root of the project and create file called __checkUser.js__ within the new folder.

{% gist https://gist.github.com/dabit3/1b9f5b72096a06d83a14b8e7d1085fcc %}

This hook will automatically keep track of signed in users and allow us to manage our UI based on this user state (to show and hide UI).

### pages/index.js

Now we'll update the main entry-point of the app to show the list of posts fetched from the API.

This page will be making a client-side API call to fetch the posts from the GraphQL backend and rendering them when the component loads using public API data access. We use the `Link` component from `next/link` and the ID of the post to enable navigation to the route `/posts/${post.id}`.

Update __pages/index.js__ with the following code.

{% gist https://gist.github.com/dabit3/ae2bb13575d272b0b9c938147f23764d %}

### pages/_app.js

Next let's update the navigation with the new configuration that we'd like to use.

{% gist https://gist.github.com/dabit3/d4921b5fcda3b2821cfbcea6ecf0c54e %}

The new navigation will use the `user` state to show and hide the link for creating a new post (`/create-post`), as only signed in users should be able to do so.

### pages/posts/[id].js

Next, we'll need to have a way to render each individual post using a [dynamic route](https://nextjs.org/docs/routing/dynamic-routes).

To do so, create a new folder called __pages/posts__ and create file called __[id].js__ within the new folder.

This page will be taking advantage of [`getStaticPaths`](https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation) as well as [`getStaticProps`](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) to fetch data at build time and programmatically build the pages in our app based on the posts.

We'll also be using the [`fallback`](https://nextjs.org/docs/basic-features/data-fetching#fallback-true) flag to enable the pre-rendering of the available paths at build time while still allowing dynamic creation of pages on the fly as they are created by users at runtime.

{% gist https://gist.github.com/dabit3/3639c45cf5b47d361333b2c35b4f0a02 %}

### pages/create-post.js

Finally, we'll create a new file called __create-post.js__ in the __pages__ directory that will allow a signed in user to create new posts.

Once a new post is created, the component will programmatically navigate to the new route. 

{% gist https://gist.github.com/dabit3/abc0c7ed992f786e5b6acd97976b9996 %}

## Testing it out

We should now be able to test it out.

```sh
npm run dev
```

You should be able to create posts, view posts, and view the list of posts.

## Adding comments

### components/Comments.js

Next, let's enable the ability to add comments.

To do so, create a new folder called __components__ at the root of the project and a file in that directory called __Comments.js__ with the following code.

{% gist https://gist.github.com/dabit3/85cd24efa5d489157c04651e1e3f02ea %}

This component will render the list of associated comments and let users comment on a post.

### pages/posts/[id].js

Next, we'll update the post component to render the `Comments` component if the user is authenticated.

{% gist https://gist.github.com/dabit3/e438cf8d60eb69c5b5ca52a15bb5e485 %}

## Testing it out

We should now be able to test out the new comment functionality.

### Dev mode

```sh
npm run dev
```

### Run a build

```sh
npm run build

npm start
```

### Deploying to AWS

Be sure you have created a `serverless.yml` file at the root of your project with the following configuration:

```yaml
myNextApp:
  component: "@sls-next/serverless-component@1.17.0" 
```

Then run the following command:

```sh
npx serverless
```

> Note - It can often take up to a couple of minutes for your URL to be live because of how Cloudfront works

## Exposing a public API

Let's see how we might enable a public API to allow other developers to consume via their apps. To do so, we'll create a new API route at __pages/api/posts.js__ with the following code:

{% gist https://gist.github.com/dabit3/331460795c0022308bc61ed4b0421c03 %}

You should now be able to navigate to `http://localhost:3000/api/posts` and see a JSON response with your list of posts.

> The final code for this app is located [here](https://github.com/dabit3/nextjs-data-story)