Get started with Next.js + Fauna GraphQL in 5 minutes
Published: 2020-12-03 17:33:58

I love the classic game show [Name That Tune](https://en.wikipedia.org/wiki/Name_That_Tune) where contestants start each round by bidding on how many notes they need to name a tune.

> I can name that tune in **three notes**.

...

> I can name that tune in **two notes**.

...and so on until the risk becomes too high for a contestant to bid any lower.

The same competitive spirits applies to software development. When starting a new project, **how fast can you go from nothing to something?**

Can you start that project in a day? An hour?

**I can start that project in 5 minutes** (and so can you).

In this tutorial, we will build a Next.js web application powered by a Fauna GraphQL API in 5 minutes. You can use this process over and over again, for any project you need.

## What you will learn

In [Part 1: Simple GraphQL with Fauna](https://seanconnolly.dev/simple-graphql-with-faunadb), we learned how to quickly construct a GraphQL API with [Fauna, the data API for modern applications](https://dashboard.fauna.com/accounts/register?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_GetStartedNextFauna_SConnolly). It’s okay if you missed part 1, you can start right here in part 2 without missing a beat.

APIs are nice, but now we need a user interface to turn our project into an **experience**. We are going to leverage [Next.js](https://nextjs.org/) and a few other tools to add a user interface to the game of rock paper scissors we started in part 1.

By the end of this tutorial, you will understand:

- How to setup a Fauna GraphQL API
- How to integrate your Fauna GraphQL API with a Next.js application
- How to use Apollo, TypeScript, and code generation to establish a statically typed interface for your GraphQL API

## Up and running in 5 minutes

In just 5 minutes, we are going to set up a Fauna database and bootstrap a Next.js project that connects to the database.

### Setup your Fauna database

1. If you do not have a Fauna account, you can [register a Fauna account](https://dashboard.fauna.com/accounts/register?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_GetStartedNextFauna_SConnolly) for free.

2. Create a new database.
 ![Create a database](https://dev-to-uploads.s3.amazonaws.com/i/hffwfb1uoc33qd6yf417.png)

3. Enter “rock-paper-scissors” as the name and click **Save**.
 ![Enter database name](https://dev-to-uploads.s3.amazonaws.com/i/mruo2e36q3t4k2k3nx7y.png)

4. In the left nav, go to **Security => New Key**.
 ![Security new key](https://dev-to-uploads.s3.amazonaws.com/i/rjhhx56fn8gom790z5m7.png)

5. Make sure the “Admin” role is selected, enter “localhost” as the key and click **Save**.
 ![Select admin role](https://dev-to-uploads.s3.amazonaws.com/i/9acren6tfvj91bq746ij.png)

You should see a secret key. **Keep this window open** while you switch to project setup.

### Setup your Next.js project

Now that our Fauna database is set up, we can create our Next.js project.

1. [Create a GitHub repository](https://github.com/new).
 ![Create GitHub repo](https://dev-to-uploads.s3.amazonaws.com/i/pvq9of7m3mzdlhfde7cx.png)

2. In a terminal, clone the repository to a local directory, i.e. `git clone <repository url>`.

3. In the created directory, run `npx @seanconnolly/gen nextjs fauna`.

4. Follow the prompts to enter the name (rock-paper-scissors) for your project.

5. When prompted for your Fauna Admin key, enter the key you kept open in step 6 of the **Setup your database step**.
 ![Generator prompt](https://dev-to-uploads.s3.amazonaws.com/i/epx6boh71hdxkn09utwq.png)

### Initialize your Fauna GraphQL API

Now that you have your Fauna database and your Next.js project setup, you are ready to initialize your database.

1. Run `npm run fauna` to bootstrap your Fauna database with the resources defined in the project generator you just ran (we will examine these more closely later).

2. Go back to the Fauna Dashboard and you should see that an example collection, index and function was created in your database. These were all created by the `npm run fauna` command you just ran.

3. Select **Security** and then **New Key** again.
 ![Security new key](https://dev-to-uploads.s3.amazonaws.com/i/yf9kgqhtb42qhkyxgxog.png)

4. In the **Role** dropdown, select the **Public** role (we will examine the importance of this role shortly). Enter "localhost" for the key name and then select **Save**.
 ![Select public role](https://dev-to-uploads.s3.amazonaws.com/i/70at2v8wjubbgumc8c3r.png)

5. Copy the secret key that was just generated and paste it in the .env.local file like so:

```bash
# Paste your secret key just after the = sign
NEXT_PUBLIC_FAUNA_CLIENT_KEY=
```

### Test your Next.js project

Open a terminal and run `npm run dev`.

Now open http://localhost:3000 in your browser and you should see this message.

🎉 Success! We were able to successfully connect with your Fauna database.

#### You did it! 💥
In just 5* minutes, you created a Next.js web application and hooked it up with a Fauna database.

\* _It's okay if you took longer than 5 minutes. I've done this a few times so 5 minutes may be on the lower end, but after going through this process once or twice, 5 minutes is totally attainable!_

## Understand the setup process

While we were able to blaze through the setup process in just a few minutes, you are probably wondering what we just did exactly. Let’s walk through the key parts of the project so you understand how everything is working.

### Next.js project setup

The `npx @seanconnolly/dev nextjs fauna` command you ran earlier is a [Hygen.io](https://www.hygen.io/) generator I created to accelerate the process of setting up new projects that use Next.js and Fauna. Running the generator first adds the boilerplate files to our project directory and then installs all necessary packages from npm including Next.js, React and [Apollo Client](https://www.apollographql.com/docs/react/).

You are free to use this generator to kickstart any one of your projects or you can learn to create your own with [Hygen’s Quick Start](https://www.hygen.io/docs/quick-start).

### Initializing Fauna

If you joined us for [Part 1: Simple GraphQL with Fauna](https://seanconnolly.dev/simple-graphql-with-faunadb), you hopefully already know about the [fauna-gql-upload](https://www.npmjs.com/package/fauna-gql-upload) package. To summarize, **fauna-gql-upload** allows us to store Fauna schema and resource definition files alongside our project files and push them to Fauna as needed. When we ran the generator earlier, several of these files were generated to provide a baseline for our Fauna database.

For example, in our GraphQL schema definition file (`fauna/schema.gql`), we defined the **Example** type:

[Schema snippet]

```graphql
type Example {
  foo: String!
}
```

When we ran `npm run fauna` earlier, that schema definition resulted in Fauna creating a **Collection** called **Example**.
![Example collection](https://dev-to-uploads.s3.amazonaws.com/i/ci5rl12isyj6207x1cwl.png)

### The Public role

Earlier, we created a Fauna API key for the **Public** role, which is a custom role that was predefined with the project generator. Our Next.js application needs this API key to access our Fauna database from a web browser. We are calling it a “Public” role because our application does not require a user to authenticate with a password or via third-party authentication like Auth0 or Okta. Full user authentication is outside the scope of this tutorial, but the **Public** role is necessary in these unauthenticated cases to protect data and resources in our Fauna database.

By default, every Fauna database comes with two roles: **Admin** and **Server**. Fauna describes these roles in their Admin UI:

- **Admin**: Used to create, destroy, or manage any database or key. Protect admin keys very carefully.
- **Server**: Can be used to create, destroy, or manage the database to which they are assigned. Protect server keys carefully.

Both of these roles have too much privilege for us to expose their API keys to a web browser. If we did expose such keys, a savvy user could load your web application, inspect the key and use it to destroy or access valuable or sensitive resources in your Fauna account.

In your `.env.local` file, the `FAUNA_ACCESS_KEY_ADMIN` variable has **Admin** privilege, which means we do **not** want to expose that key to the browser. This key is required for you, the developer, to manage your Fauna database and it is **not** for your users. This Admin key should only be stored and accessible on your machine and should never be exposed to the browser.

We also added the `NEXT_PUBLIC_FAUNA_CLIENT_KEY` variable to `.env.local`, which has **Public** privilege. Opening `fauna/roles/Public.ts`, we see this definition of the Public role:

```ts
const Public = {
  name: 'Public',
  privileges: [
    {
      resource: Collection('Example'),
      actions: {
        read: true,
        create: true,
        delete: false,
      },
    },
  ],
};
```

Since this is a [custom role](https://docs.fauna.com/fauna/current/tutorials/basics/authentication.html?lang=javascript#custom_roles), it only has privileges that are explicitly granted, in this case the ability to read and create documents in the **Example** collection.

By prefixing the environment variable with `NEXT_PUBLIC_`, Next.js automatically makes this environment variable [accessible to the browser](https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser). If you open `components/withApollo.tsx`, you can see where this is read and then passed as an **Authorization** header to our Fauna GraphQL endpoint by Apollo Client.

```ts
const { NEXT_PUBLIC_FAUNA_CLIENT_KEY } = process.env;
/* ... */
return new ApolloClient({
  uri: 'https://graphql.fauna.com/graphql',
  cache: new InMemoryCache().restore(initialState || {}),
  headers: {
    Authorization: `Bearer ${NEXT_PUBLIC_FAUNA_CLIENT_KEY}`,
  },
});
```

### GraphQL with Apollo

When we tested the project by hitting http://localhost:3000, we saw a “Success” message when the index page could successfully query the Fauna GraphQL API. If the API call were to fail, possibly due to some misconfiguration, we would have seen an error message instead. Let’s take a look at how all of this is working.

First, when running `npm run dev`, in addition to running the usual `next` dev script defined in `package.json`, we are also concurrently running a script called `gen:gql:watch`.

```json
"scripts": {
  /* ... */
  "gen:gql": "dotenv -c -- graphql-codegen",
  "gen:gql:watch": "npm run gen:gql -- --watch",
  "dev": "concurrently \"next\" \"npm run gen:gql:watch\"",
  /* ... */
}
```

This is the [graphql-codegen](https://graphql-code-generator.com/) project at work and what it does for us is _amazing_. First, graphql-codegen is configured through the `codegen.yml` file that was created via the generator.

```yaml
# codegen.yml
schema:
  - https://graphql.fauna.com/graphql:
      headers:
        Authorization: Bearer ${FAUNA_ACCESS_KEY_ADMIN}
documents: '{components,lib}/**/*.graphql'
generates:
  ./.gen/graphql.tsx:
    plugins:
      - typescript
      - typescript-operations
      - typescript-react-apollo
config:
  preResolveTypes: true
```

The `schema` points to Fauna’s GraphQL endpoint and is also configured to read the `FAUNA_ACCESS_KEY_ADMIN` environment variable that you entered when you first generated the project.

The `documents` configuration tells `graphql-codegen` to generate code from all files under the `/components` and `/lib` directories that end with `.graphql`.

The `components/Example.graphql` was created with the project setup generator and contains the content below. When Fauna received our `schema.gql` GraphQL definition file earlier, they automatically created CRUD queries and mutations for the **Example** type. `findExampleByID` is the “read” operation and allows us to get an **Example** by its ID.

```graphql
# components/Example.graphql
query FindExample($id: ID!) {
  findExampleByID(id: $id) {
    _id
  }
}
```

The last part of the `codegen.yml` file is the `generates` section, which tells `graphql-codegen` to generate a TypeScript file at `.gen/graphql.tsx` that includes Apollo React hooks. If you are not familiar with Apollo’s hooks, you can read more about those in [Apollo’s Hooks documentation](https://www.apollographql.com/docs/react/api/react/hooks/).

If you open the generated `.gen/graphql.tsx` file you will see this `useFindExampleQuery`:

```tsx
/* .gen/graphql.tsx */
export function useFindExampleQuery(baseOptions: Apollo.QueryHookOptions<FindExampleQuery, FindExampleQueryVariables>) {
  return Apollo.useQuery<FindExampleQuery, FindExampleQueryVariables>(FindExampleDocument, baseOptions);
}
```

This generated code and its associated TypeScript types are derived from the `Example.graphql` file we examined earlier.

The operation name dictates the name of the hook function:

```graphql
# components/Example.graphql
query FindExample($id: ID!) {
  #   ^^^^^^^^^^^
  findExampleByID(id: $id) {
    _id
  }
}
```

```tsx
/* .gen/graphql.tsx */
export function useFindExampleQuery(baseOptions: Apollo.QueryHookOptions<FindExampleQuery, FindExampleQueryVariables>) {
  return Apollo.useQuery<FindExampleQuery, FindExampleQueryVariables>(FindExampleDocument, baseOptions);
}
```

The query variables dictate the `FindExampleQueryVariables` type.

```graphql
# components/Example.graphql
query FindExample($id: ID!) {
  #               ^^^^^^^^
  findExampleByID(id: $id) {
    _id
  }
}
```

```tsx
/* .gen/graphql.tsx */
export type FindExampleQueryVariables = Exact<{
  id: Scalars['ID'];
}>;
```

The query body dictates the FindExampleQuery type.

```graphql
# components/Example.graphql
query FindExample($id: ID!) {
  findExampleByID(id: $id) {
    _id
    # ^
  }
}
```

```tsx
/* .gen/graphql.tsx */
export type FindExampleQuery = { __typename?: 'Query'; findExampleByID?: Maybe<{ __typename?: 'Example'; _id: string }> };
```

With our generated code, we can now consume it in a React component as demonstrated with the components/Example.tsx file.

```tsx
/* components/Example.tsx */
import { FunctionComponent } from 'react';
import { useFindExampleQuery } from '@gen/graphql';

const Example: FunctionComponent = () => {
  const { data, error } = useFindExampleQuery({ variables: { id: '123' } });

  if (error) {
    console.error(error);
    return <div>‼️ Error! Inspect the console to see what happened.</div>;
  }

  if (data) {
    return <div>🎉 Success! We were able to successfully connect with your Fauna database.</div>;
  }

  return null;
};

export default Example;
```

Don’t worry about the hard-coded “123” id. At this stage we haven’t created any documents so no results will be returned. This is just enough to prove that we can successfully execute a query from our frontend Next.js application to our backend Fauna GraphQL API.

With code generation, we are able to reduce the amount of code we need to write and maintain in our React component to just one line:

```tsx
/* components/Example.tsx */

/* Just one line! 💥 */
const { data, error } = useFindExampleQuery({ variables: { id: '123' } });
```

Without code generation, we would have needed to manually define the TypeScript types on our own and keep those types in sync with the data we are requesting via the GraphQL query. This is tedious work and error prone, so `graphql-codegen` helps us speed up development and reduce defects in our application.

## Implementing the game UI

Now that we have everything wired up and we discussed how the generated code is all working together, we can now implement our Rock Paper Scissors game UI with Next.js and Fauna.

In [Part 1: Simple GraphQL with Fauna](https://seanconnolly.dev/simple-graphql-with-faunadb), we played our game of Rock Paper Scissors in Fauna’s GraphQL Playground, which is fine for testing the API but not quite the experience we want for our users (unless they love GraphQL as much as you and I do!). In order to build UI for our own application, we are going to do 3 things:

1. Setup our backend game logic
2. Create a new GraphQL query in a .graphql file, which will generate a new React hook
3. Use one of the hooks in a React component

First, we are going to pull some of our Fauna backend from the [part 1 solution](https://github.com/seanconnollydev/fauna-rock-paper-scissors), which implements the game logic in Fauna. If you want to better understand what these are doing, check out the explanations in part 1.

Copy this into your `fauna/schema.gql` file:

```graphql
# fauna/schema.gql
type Query {
  play(selection: Selection!): Result! @resolver(name: "play")
}

enum Selection {
  Rock
  Paper
  Scissors
}

type Result @embedded {
  selection: Selection!
  opponentSelection: Selection!
  message: String!
}
```

Create `fauna/functions/Play.ts` and copy this into it:

```ts
/* fauna/functions/Play.ts */
import { query } from 'faunadb';
import { RandomString, Switch } from 'faunadb-fql-lib';
import DetermineResult from '../lib/DetermineResult';
const { Lambda, Let, Query, Var } = query;

const selectionMap = {
  R: 'Rock',
  P: 'Paper',
  S: 'Scissors',
};

const Play = {
  name: 'play',
  body: Query(
    Lambda(
      ['selection'],
      Let(
        {
          randomCharacter: RandomString(1, 'RPS'),
          opponentSelection: Switch(Var('randomCharacter'), selectionMap),
        },
        DetermineResult(Var('selection'), Var('opponentSelection')),
      ),
    ),
  ),
};

export default Play;
```

Create `fauna/lib/DetermineResult.ts` and copy this into it:

```ts
/* fauna/lib/DetermineResult.ts */
import { ExprArg, ExprVal, query } from 'faunadb';
const { And, Equals, If, Or } = query;

export default function DetermineResult(selection: ExprArg, opponentSelection: ExprArg): ExprVal {
  return {
    selection,
    opponentSelection,
    message: If(
      Equals(selection, opponentSelection),
      "It's a tie!",
      If(
        Or(
          And(Equals(selection, 'Rock'), Equals(opponentSelection, 'Scissors')),
          And(Equals(selection, 'Paper'), Equals(opponentSelection, 'Rock')),
          And(Equals(selection, 'Scissors'), Equals(opponentSelection, 'Paper')),
        ),
        'You win!',
        'You lose!',
      ),
    ),
  };
}
```

Since we are creating a new resource in Fauna (the `play` function), we need to grant the **Public** role access to that function, otherwise we will encounter authorization errors when we try to invoke that function from our application. The `fauna/roles/Public.ts` file should be updated to this:

```ts
/* fauna/roles/Public.ts */
import { query } from 'faunadb';
const { Collection, Function } = query;

const Public = {
  name: 'Public',
  privileges: [
    {
      resource: Collection('Example'),
      actions: {
        read: true,
        create: true,
        delete: false,
      },
    },
    // This is new
    {
      resource: Function('play'),
      actions: {
        call: true,
      },
    },
  ],
};

export default Public;
```

Now run `npm run fauna` in your terminal to push your API changes to Fauna. You should see success all the way down.

```bash
✔️  Successfully updated schema.

✔️  Successfully uploaded indexes

✔️  Successfully uploaded functions

✔️  Successfully uploaded roles
```

Next, we need to define the client-side GraphQL query that our UI requires to play the game.

Create `RockPaperScissors.graphql` and copy this into it:

```graphql
query Play($selection: Selection!) {
  play(selection: $selection) {
    selection
    opponentSelection
    message
  }
}
```

Since we are running `graphql-codegen` in **watch** mode, the GraphQL code is automatically regenerated. If you open .gen/graphql.tsx, you should see two new hooks are available for us:

```tsx
/* .gen/graphql.tsx */
export function usePlayQuery(baseOptions: Apollo.QueryHookOptions<PlayQuery, PlayQueryVariables>) {
  return Apollo.useQuery<PlayQuery, PlayQueryVariables>(PlayDocument, baseOptions);
}
export function usePlayLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<PlayQuery, PlayQueryVariables>) {
  return Apollo.useLazyQuery<PlayQuery, PlayQueryVariables>(PlayDocument, baseOptions);
}
```

Now we can use these hooks in a React component, so create components/RockPaperScissors.tsx and copy this:

```tsx
/* components/RockPaperScissors.tsx */
import { Selection, usePlayLazyQuery } from '@gen/graphql';
import { FunctionComponent } from 'react';

const RockPaperScissors: FunctionComponent = () => {
  const [playSelection, { data, loading }] = usePlayLazyQuery({
    fetchPolicy: 'no-cache',
  });

  const handleSelection = (selection: Selection) => {
    playSelection({
      variables: {
        selection,
      },
    });
  };

  return (
    <div className="root">
      <h1>Rock Paper Scissors</h1>
      <h3>Choose wisely</h3>
      <div className="player-selections">
        <PlayerSelection selection={Selection.Rock} onSelect={handleSelection} />
        <PlayerSelection selection={Selection.Paper} onSelect={handleSelection} />
        <PlayerSelection selection={Selection.Scissors} onSelect={handleSelection} />
      </div>
      {loading && <p>Waiting on opponent...</p>}
      {data?.play && (
        <div>
          <p>{`Opponent chose ${data.play.opponentSelection} ${emojiFor(data.play.opponentSelection)}`}</p>
          <h3>{data.play.message}</h3>
        </div>
      )}
      <style jsx>{`
        font-family: Verdana, sans-serif;
        .root {
          text-align: center;
        }

        .player-selections {
          display: flex;
          justify-content: center;
        }
      `}</style>
    </div>
  );
};

interface PlayerSelectionProps {
  selection: Selection;
  onSelect: (selection: Selection) => void;
}
const PlayerSelection: FunctionComponent<PlayerSelectionProps> = (props) => {
  const { selection, onSelect } = props;

  return (
    <button type="button" className="root" onClick={() => onSelect(selection)}>
      <div className="emoji">{emojiFor(selection)}</div>
      <div className="selection">{selection}</div>
      <style jsx>{`
        font-family: Verdana, sans-serif;

        .root {
          padding: 16px 32px;
          margin: 16px;
          text-align: center;
          cursor: pointer;
        }

        .emoji {
          font-size: 4.8rem;
          line-height: 1;
          margin-bottom: 16px;
        }

        @media only screen and (max-width: 600px) {
          .root {
            padding: 8px 16px;
            margin: 8px;
          }
          .emoji {
            font-size: 1rem;
          }
        }
      `}</style>
    </button>
  );
};

function emojiFor(selection: Selection): string {
  switch (selection) {
    case Selection.Rock:
      return '✊🏼';
    case Selection.Paper:
      return '🖐🏼';
    case Selection.Scissors:
      return '✌🏼';
    default:
      throw new Error('Unrecognized selection');
  }
}

export default RockPaperScissors;
```

I tried to keep the styling code minimal here so it isn’t the most elegant game of Rock Paper Scissors you can find on the internet, but let’s look at a few key code snippets in this component.

First, we are using a hook called `usePlayLazyQuery`, which is a bit different from usePlayQuery.

```tsx
/* components/RockPaperScissors.tsx */
const [playSelection, { data, loading }] = usePlayLazyQuery({
  fetchPolicy: 'no-cache',
});
```

The main difference in our use case is that we don’t want to initiate the GraphQL request until the user makes a selection. If we used `usePlayQuery`, the query would occur after the component _mounts_ during initial page load. That wouldn’t make sense for the sequence of our game. Keep the following guidance in mind for your own applications:

- `use*Query`: Fetch after the component mounts.
- `use*LazyQuery`: Fetch in response to user action.

We also passed `fetchPolicy: “no-cache”` to tell Apollo that we always want this request to execute fully against our Fauna GraphQL endpoint without checking the local browser cache. This prevents our game from returning the same opponent selection over and over. In most use cases, you do not need to set an explicit `fetchPolicy`. Refer to Apollo’s [Setting a fetch policy](https://www.apollographql.com/docs/react/data/queries/#setting-a-fetch-policy) docs for more information on fetch policies and caching.

Next, we have the `handleSelection` callback function:

```tsx
/* components/RockPaperScissors.tsx */
const handleSelection = (selection: Selection) => {
  playSelection({
    variables: {
      selection,
    },
  });
};
```

This function is called whenever the user makes a selection. It calls `playSelection`, another callback function defined by the result of the call to `usePlayLazyQuery` above and the function that triggers the network call to our GraphQL API.

Last, we define how we want to present responses from the GraphQL request.

```tsx
/* components/RockPaperScissors.tsx */
<div className="root">
  {/* ... */}
  {loading && <p>Waiting on opponent...</p>}
  {data?.play && (
    <div>
      <p>{`Opponent chose ${data.play.opponentSelection} ${emojiFor(data.play.opponentSelection)}`}</p>
      <h3>{data.play.message}</h3>
    </div>
  )}
  {/* ... */}
</div>
```

The `loading` boolean value will be `true` while Apollo waits for the network response to complete, so the user sees the message “Waiting on opponent…”

When `data.play` is truthy, this means the network response is complete and we know the result of the game. The user sees the selection of the computer opponent and a message with the final game verdict.

The final step is to update the `pages/index.tsx` file to use your new component rather than the default **Example** component.

```tsx
import RockPaperScissors from '@components/RockPaperScissors';
import { FunctionComponent } from 'react';

const Index: FunctionComponent = () => {
  return <RockPaperScissors />;
};

export default Index;
```

Now you should be able to refresh http://localhost:3000 in your browser and see this screen:

![Rock paper scissors initial state](https://dev-to-uploads.s3.amazonaws.com/i/wg9ev0p3ntg0mu9girgh.png)

Open up your browser dev tools and go to the Network tab. As you make each selection, you can inspect the requests hitting your Fauna GraphQL endpoint that should look like this:

![Network tab](https://dev-to-uploads.s3.amazonaws.com/i/rvvyyuvlqai752189rzi.png)

And now you have a fully functioning game complete with a Next.js frontend user interface and a backend Fauna GraphQL API. Nice work! 👏🏻

## Close

Hopefully you now see the power of this awesome pairing of Next.js and Fauna. It is the most delightful full stack development experience I have ever encountered and with Fauna I never need to worry about database maintenance, scaling, or correctness.

In the next and final part of this series, we are going to learn about Fauna’s recently released [streaming](https://docs.fauna.com/fauna/current/drivers/streaming) capability, which will allow us to turn our game of Rock Paper Scissors into a real-time multiplayer experience. Be the first to know when it’s published by subscribing to the newsletter over at [seanconnolly.dev](https://seanconnolly.dev).