Next.js - The Data Story

Nader Dabit - Oct 22 '20 - - Dev Community

In my previous post 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

Overview

When making requests to an API you often need to deal with security - managing ID tokens, access tokens, and refresh tokens 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 covered in the last post, 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):

import { API } from 'aws-amplify';
import { listPosts } from './graphql/queries';

const data = await API.graphql({
  query: listPosts
});
Enter fullscreen mode Exit fullscreen mode

Specifying a custom authorization mode (client-side):

import { API } from 'aws-amplify';
import { listPosts } from './graphql/queries'

const data = await API.graphql({
  query: listPosts,
  authMode: "AMAZON_COGNITO_USER_POOLS"
});
Enter fullscreen mode Exit fullscreen mode

Making authenticated request with authorization headers (SSR):

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
}
Enter fullscreen mode Exit fullscreen mode

Making authenticated request with authorization headers (API routes):

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
}
Enter fullscreen mode Exit fullscreen mode

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.

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

1. Clone the repo

git clone https://github.com/dabit3/next.js-authentication-aws.git
Enter fullscreen mode Exit fullscreen mode

2. Change into the directory and install the dependencies

cd next.js-authentication-aws

npm install
Enter fullscreen mode Exit fullscreen mode

3. Initialize the Amplify project

amplify init
Enter fullscreen mode Exit fullscreen mode

4. Deploy the authentication service

amplify push --y
Enter fullscreen mode Exit fullscreen mode

5. Run the app locally

npm run dev
Enter fullscreen mode Exit fullscreen mode

Creating the API

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

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
Enter fullscreen mode Exit fullscreen mode

When prompted, use the following GraphQL schema.

amplify/backend/api/nextapi/schema.graphql

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 library of Amplify to generate queries and mutations for create, read, update, delete, and list operations for Posts and Comments 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:

amplify push --y
Enter fullscreen mode Exit fullscreen mode

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.

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.

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.

pages/_app.js

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

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.

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 as well as getStaticProps 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 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.

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.

Testing it out

We should now be able to test it out.

npm run dev
Enter fullscreen mode Exit fullscreen mode

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.

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.

Testing it out

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

Dev mode

npm run dev
Enter fullscreen mode Exit fullscreen mode

Run a build

npm run build

npm start
Enter fullscreen mode Exit fullscreen mode

Deploying to AWS

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

myNextApp:
  component: "@sls-next/serverless-component@1.17.0" 
Enter fullscreen mode Exit fullscreen mode

Then run the following command:

npx serverless
Enter fullscreen mode Exit fullscreen mode

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:

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .