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:
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.jsimport { 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 }) {namebio}}`;
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 /><inputtype="text"name="name"{...register('name', { required: true })}placeholder="name"id="name"/></div><div><label htmlFor="bio">Bio</label><br /><textareaname="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 }) {namebio}}`;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 /><inputtype="text"name="name"{...register('name', { required: true })}placeholder="name"id="name"/></div><div><label htmlFor="bio">Bio</label><br /><textareaname="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 }) {idnamebio}}`;
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 }) {idnamebio}}`;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.