Working with NextAuth.js & User Generated Content

Manage user generated profile data with NextAuth.js, and GraphCMS.

Jamie Barton
Jamie Barton
NextAuth.js with GraphCMS

In the previous article, I taught how to connect NextAuth.js with GraphCMS, using the credentials provider. If you want to follow along with this tutorial, you should follow the steps in that article first.

If you'd rather skip to the good part, you can view a demo, or browse the code on GitHub.

Now that we've authenticated, let's give logged in users the ability to update their profile (such as a name, and bio). Here's what we'll be building:

Account area preview

1. Create a Protected Account PageAnchor

Inside of your project pages directory, create the file account.js (or .ts if you're using TypeScript).

Inside of here, we'll use getSession from NextAuth.js to get the session from our users request. If there's no session, we'll redirect them to the index page. Let's do this server side, as we'll next

import { getSession } from 'next-auth/react';
export async function getServerSideProps(context) {
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
return {
props: {},
};
}
export default function AccountPage() {
return (
<h1>My Account</h1>
)
}

If you attempt to visit [http://localhost:3000/account](http://localhost:3000/account) you'll be redirect to the index if you aren't logged in.

2. Fetch User DetailsAnchor

We want to pre-populate a form users can submit to update their account. We'll need to update pages/api/auth/[...nextauth].js with a callbacks configuration object, so we can store the userId on our session.

Add the following below the providers array.

callbacks: {
async session({ session, token }) {
session.userId = token.sub;
return Promise.resolve(session);
},
},

Now back inside of pages/api/account.js let's update our getServerSideProps method to fetch our account from GraphCMS.

I've gone ahead and refactored the GraphQLClient we made previously to be inside of it's own file, and imported that from the folder lib in our project.

See refactored code
// lib/graphcms.js
import { GraphQLClient } from 'graphql-request';
export const graphcmsClient = new GraphQLClient(process.env.GRAPHCMS_ENDPOINT, {
headers: {
Authorization: `Bearer ${process.env.GRAPHCMS_TOKEN}`,
},
});
import { graphcmsClient } from '../lib/graphcms';

We'll need to import gql from graphql-request and define a query to fetch a single instance of our NextAuthUser entry:

import { gql } from 'graphql-request';
const GetUserProfileById = gql`
query GetUserProfileById($id: ID!) {
user: nextAuthUser(where: { id: $id }) {
name
bio
}
}
`;

Now, inside of getServerSideProps add the following, and replace the returned props with:

const { user } = await graphcmsClient.request(GetUserProfileById, {
id: session.userId,
});
return {
props: {
user,
},
};
See full code for function
export async function getServerSideProps(context) {
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
const { user } = await graphcmsClient.request(GetUserProfileById, {
id: session.userId,
});
return {
props: {
user,
},
};
}

3. Add the User FormAnchor

Now, we've got the user fetched for our form; we need to populate a form with the data fetched from the above.

You could use useState from React to manage the values, but let's include a handy form library to do this for us.

You'll need to install react-hook-form to continue:

npm install -E react-hook-form

Then inside of our account.js file import it!

import { useForm } from 'react-hook-form';

You'll also want to grab user from the AccountPage props:

export default function AccountPage({ user }) {
// ...
}

We can then invoke useForm and pass it the defaultValues:

const { handleSubmit, register } = useForm({ defaultValues: user });

Now let's add the form inside of render:

<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="name">Name</label>
<br />
<input
type="text"
name="name"
{...register('name', { required: true })}
placeholder="name"
id="name"
/>
</div>
<div>
<label htmlFor="bio">Bio</label>
<br />
<textarea
name="bio"
{...register('bio')}
placeholder="Short bio"
id="bio"
rows={7}
/>
</div>
<div>
<button type="submit">Save profile</button>
</div>
</form>

We're almost there! If you try to load the page now, you'll notice we're missing the function onSubmit. We should define this inside of the page so our form knows what to do when submitted.

const onSubmit = async ({ name, bio }) => {
try {
const res = await fetch('/api/update-account', {
method: 'POST',
body: JSON.stringify({ name, bio }),
});
if (!res.ok) {
throw new Error(res.statusText);
}
} catch (err) {
console.log(err);
}
};

That's it for our form! If you submit this form now you'll noticed that an error is returned that /api/update-account doesn't exist.

View the full code
import { gql } from 'graphql-request';
import { useForm } from 'react-hook-form';
import { getSession } from 'next-auth/react';
import { graphcmsClient } from '../lib/graphcms';
import Header from '../components/header';
const GetUserProfileById = gql`
query GetUserProfileById($id: ID!) {
user: nextAuthUser(where: { id: $id }) {
name
email
bio
}
}
`;
export async function getServerSideProps(context) {
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
const { user } = await graphcmsClient.request(GetUserProfileById, {
id: session.userId,
});
return {
props: {
user,
},
};
}
export default function AccountPage({ user }) {
const { handleSubmit, register } = useForm({ defaultValues: user });
const onSubmit = async ({ name, bio }) => {
try {
const res = await fetch('/api/update-account', {
method: 'POST',
body: JSON.stringify({ name, bio }),
});
if (!res.ok) {
throw new Error(res.statusText);
}
} catch (err) {
console.log(err);
}
};
return (
<div>
<Header />
<h1>My Account</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="name">Name</label>
<br />
<input
type="text"
name="name"
{...register('name', { required: true })}
placeholder="name"
id="name"
/>
</div>
<div>
<label htmlFor="bio">Bio</label>
<br />
<textarea
name="bio"
{...register('bio')}
placeholder="Short bio"
id="bio"
rows={7}
/>
</div>
<div>
<button type="submit">Save profile</button>
</div>
</form>
</div>
);
}

4. Create API Route for Updating AccountAnchor

All that's left to do is create the API route that takes the submitted JSON values, and passes them onto GraphCMS to update.

Inside of the new file pages/api/update-account.js let's define a GraphQL mutation we can use to update our account by the ID of the logged in user.

import { gql } from 'graphql-request';
const UpdateNextAuthUser = gql`
mutation UpdateNextAuthUser($userId: ID!, $name: String, $bio: String) {
user: updateNextAuthUser(
data: { name: $name, bio: $bio }
where: { id: $userId }
) {
id
name
email
bio
}
}
`;

We'll next want to export an async function that inside of that we check if the user is logged in, and if not, send an error.

import { getSession } from 'next-auth/react';
export default async (req, res) => {
const session = await getSession({ req });
if (session) {
const { name, bio } = JSON.parse(req.body);
// We'll update this next
} else {
res.send({
error: 'You must be sign in to update your account.',
});
}
};

If you head back to /account and submit the form, we'll no longer have an error the page is missing, but nothing will happen. Let's fix that!

Inside of the if statement above, let's make a request to GraphCMS.

const { user } = await graphcmsClient.request(UpdateNextAuthUser, {
userId: session.userId,
name,
bio,
});
res.json(user);

You'll also want to import the graphcmsClient:

import { graphcmsClient } from '../../lib/graphcms';
View the full code
import { gql } from 'graphql-request';
import { getSession } from 'next-auth/react';
import { graphcmsClient } from '../../lib/graphcms';
const UpdateNextAuthUser = gql`
mutation UpdateNextAuthUser($userId: ID!, $name: String, $bio: String) {
user: updateNextAuthUser(
data: { name: $name, bio: $bio }
where: { id: $userId }
) {
id
name
email
bio
}
}
`;
export default async (req, res) => {
const session = await getSession({ req });
if (session) {
const { name, bio } = JSON.parse(req.body);
const { user } = await graphcmsClient.request(UpdateNextAuthUser, {
userId: session.userId,
name,
bio,
});
res.json(user);
} else {
res.send({
error: 'You must be sign in to update your account.',
});
}
};

That's it! You will now be able to login, update your account, and persist the data to your GraphCMS project.

Each time a user signs into your account they'll have their own profile.

It's Easy To Get Started

GraphCMS plans are flexibly suited to accommodate your growth. Get started for free, or request a demo to discuss larger projects with more complex needs