This blog post originally posted on my blog site and you can find it here.
Let’s create a folder called middleware
and add the isAuth.ts
file and blow code to it.
import { RedditDbContext } from "../types";
import { MiddlewareFn } from "type-graphql";
export const isAuth: MiddlewareFn<RedditDbContext> = ({ context }, next) => {
if (!context.req.session.userId) {
throw new Error("not authenticated");
}
return next();
};
Then we can use this middleware in our post
resolver. This @UseMiddleware
is coming from type-graphql
@Mutation(() => Post)
@UseMiddleware(isAuth)
async createPost(
// ... remaining code
Now we are moving to the front-end app and going to add Post
form. Let’s create create-post.tsx
file in pages folder.
Let’s add this code to it.
const CreatePost: React.FC<{}> = ({}) => {
return (
<Wrapper variant="small">
<Formik
initialValues={{ title: "", text: "" }}
onSubmit={async (values) => {}}
>
{({ isSubmitting }) => (
<Form>
<InputField name="title" placeholder="title" label="Title" />
<Box mt={4}>
<InputField name="text" placeholder="text..." label="Body" />
</Box>
<Button
isLoading={isSubmitting}
mt={4}
type="submit"
colorScheme="teal"
>
Create Post
</Button>
</Form>
)}
</Formik>
</Wrapper>
);
};
export default CreatePost;
It is better to have a textarea
for body field. We can change the InputField
component. We can accept a prop
called as textarea
and define it as boolean.
... // InputFieldProps
textarea?: boolean
Then check it in the InputField
component.
let InputOrTextarea = Input
if (textarea) {
InputOrTextarea = Textarea
}
...
<InputOrTextarea
{...props}
{...field}
id={field.name}
placeholder={props.placeholder}
/>
Now we need to add the mutation. Create a file called createPost.graphql
and add the below code. Then run yarn gen
mutation CreatePost($input: PostInput!) {
createPost(input: $input){
title
id
createdAt
creatorId
updatedAt
text
points
}
}
Then we can use useCreatePostMutation
and process the request to create a post. Also, in this create post page, we need to add the NavBar to this page. Let’s create a common component for it. Create a component called Layout.tsx
and add this code block.
import { NavBar } from "./NavBar";
import { Wrapper, WrapperVariant } from "./Wrapper";
interface LayoutProps {
// added WrapperVariant type
variant?: WrapperVariant;
}
export const Layout: React.FC<LayoutProps> = ({ children, variant }) => {
return (
<>
<NavBar />
<Wrapper variant={variant}>{children}</Wrapper>
</>
);
};
Because we used Wrapper inside here. So we need to set the variant in the Layout. Without duplicating code we can create a type in there.
// Wrapper.tsx
export type WrapperVariant = "small" | "regular";
// change inside the Wrapper.tsx
interface WrapperProps {
variant?: WrapperVariant;
}
Then replace it all over the places that we used variant. Then we can replace the Wrapper
with Layout in the create-post.tsx
page.
<Layout variant="small">...</Layout>
Now we can make this navbar sticky. Add the below code to make it sticky.
// NavBar.tsx
<Flex zIndex={1} position="sticky" top={0} bg="tomato" p={4}>
...
Now at this point, once a user creates a post without login into the system, this will throw an unauthenticated error. We can simply manage it on onSubmit
function.
// create-post.tsx
onSubmit={async (values) => {
const { error } = await creatPost({ input: values });
if (error?.message.includes("not authenticated")) {
router.push("/login")
} else {
router.push("/");
}
}}
The issue in this approach we need to check in every graphql
query that, the user is authenticated or not. We can create a global error handler for this. Add below code to createUrqlClient.ts
and add this exchanges
.
import { pipe, tap } from "wonka";
const errorExchange: Exchange = ({ forward }) => (ops$) => {
return pipe(
forward(ops$),
tap(({ error }) => {
if (error?.message.includes("not authenticated")) {
Router.replace("/login");
}
})
);
};
// ...
// below in this file
exchanges: [
// ...
errorExchange,
ssrExchange,
fetchExchange,
];
Once a user tries to do something without login, this will navigate to the login page.
We can make this better by checking the user is logged in by checking me
query. To do that we can create a hook. Inside the utils
folder add a file called useIsAuth.ts
and add the below code.
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useMeQuery } from "../generated/graphql";
export const useIsAuth = () => {
const [{ data, fetching }] = useMeQuery();
const router = useRouter();
useEffect(() => {
if (!fetching && !data?.me) {
router.replace("/login?next=" + router.pathname);
}
}, [fetching, data, router]);
};
In here we are adding router.pathname
as query parameter. This will route back to the previous page that the user, where were before. Let’s user this in create-post.tsx
page.
const CreatePost: React.FC<{}> = ({}) => {
const router = useRouter();
useIsAuth();
// ...
Then in the login
page when there is a success login with next query parameter navigate to that page.
// ...
// inside the onSubmit method
else if (response.data?.login.user) {
if (typeof router.query.next === "string") {
router.push(router.query.next);
} else {
router.push("/");
}
}
If there is no error, navigate to the home page.
// inside the onSubmit method
const { error } = await creatPost({ input: values });
if (!error) {
router.push("/");
}
Also, we can modify the token-passing mechanism in the [token].tsx
page, because the token also can access as a query value. So, let’s change that code also.
const ChangePassword: NextPage<{ token: string }> = () => {
...
// previously we took token as initial prop.
// const ChangePassword: NextPage<{ token: string }> = ({ token })
// ...
// get the token from the query parameter
const response = await changePassword({
newPassword: values.newPassword,
token:
typeof router.query.token === "string" ? router.query.token : "",
});
// then remove getInitialProps
// delete below code from the file
ChangePassword.getInitialProps = ({ query }) => {
return {
token: query.token as string,
};
};
Because we removed the getInitialProps
, Next.JS
is optimize this page to render it as a static page.
Thanks for reading this. If you have anything to ask regarding this please leave a comment here. Also, I wrote this according to my understanding. So if any point is wrong, don’t hesitate to correct me. I really appreciate you.
That’s for today friends. See you soon. Thank you.
References:
This article series based on the Ben Award - Fullstack React GraphQL TypeScript Tutorial. This is an amazing tutorial and I highly recommend you to check that out.
Main image credit
Top comments (0)