I had no idea what fate had in store for me when I logged on to Hashnode that fateful day. It was August 7, 2023, a day that will be very hard for me to forget. A red timer that was counting down the seconds until the deadline of a hackathon caught my attention. A hackathon sponsored by Grafbase, a company that claims to make your life easier. I felt a rush of adrenaline and curiosity. What is Grafbase? What does it do? What kind of app could I build with it? How hard could it be? I clicked on the link and entered a new world of challenges and opportunities. That was the moment when Graphy was born.
What is Graphy?
Graphy is a minimalistic CRUD application to post your thoughts, made possible by harnessing the power of Grafbase.
What is it called "Graphy"?
That's the only name that popped up in my mind while I was creating the navbar, Sorry.
How to use Graphy?
You can clone the repo and fill in the env values to run in your local environment. I have explained below how you can do that. Currently, I do have a deployed link and you can visit it Graphy.
Once you visit the deployed link you should be greeted with the home page.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691839394380/6f0752c0-899b-4586-a216-9f93d8d0c702.png align="center")
Here, you can click on any post to see what is mentioned there. You can also see the user's profile without opening an account.
For the application, the following routes are protected by the middleware: "/create-post", "/edit-post", "/settings"
I am using next-auth with GitHub for authentication. Once a user sign's up, the default name and picture will be the ones associated with the GitHub account. But in Graphy the user has the option to update it too.
To update, the user needs to click on the avatar on the navigation bar, and a profile menu will appear.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691841070959/093863f4-c2c7-4d89-9431-b0087729b3c2.png align="center")
Then click on Settings. The settings form will pop up
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691841133100/4cd48840-caa0-446f-a07d-2df5037fb193.png align="center")
Here, the user can update their name, image, and description. The name chosen here is displayed on the user's profile and on their post with their image. In the whole application, the description is only shown in the user's profile. By default, the description is a random number.
Why did I choose this project?
I got motivated to create "Graphy" as my submission for Grafbase X Hashnode Hackathon because I saw an opportunity to make a positive impact by allowing people to share their thoughts or whatever they want in a simple and accessible manner. At the same time, it was an opportunity for me to learn how Grafbase works. Before taking part in this hackathon, I had never used Grafbase, and I had very little experience working with GraphQL.
I also believe that since this application has CRUD functionality, it will also help others who want to learn how to create an application using Grafbase and GraphQL. Also, I have made sure that the application is working with all the functionalities I have mentioned in this article.
Apart from those reasons, while perusing Grafbase's GitHub repository, I discovered that all the next.js examples are in the pages directory, so I planned to make my application in the app directory. Apart from that, on the official home page of Grafbase, they have a link to an application called Flexibble but unfortunately, the link mentioned in that repo was not working for me or a couple of other people that I asked. (August 14, 2023, 11:43 IST).
Disclaimer: The Flexibble application has been made by a YouTuber whom I respect and admire very much, so before beginning my journey building "Graphy" I did take a look at his YouTube video so I could have a better understanding of Grafbase, how to use it, its advantages, etc.
Now the big question comes, What is Grafbase? What does it do? How does it make the developer's life easier?
What is Grafbase?
Grafbase is a cloud-based service that enables developers to build and deploy their own GraphQL APIs to the edge with full end-to-end type safety. Grafbase allows developers to integrate any data source using resolvers and connectors, configure edge caching, set authentication and permission rules, enable serverless search and more. Grafbase also provides a web-based dashboard for managing your data and a CLI for local development.
What does it do?
Grafbase simplifies the process of creating and deploying GraphQL APIs by providing a seamless developer experience and modern tooling. Grafbase handles the infrastructure, scaling, security, and performance of your GraphQL API, so you can focus on building your application logic and user interface. Grafbase also supports code-first or schema-first configuration, Git-based workflow, previews for each branch, CI/CD built-in, and works with your favorite frontend frameworks.
How does it make the developer’s life easier?
Grafbase makes the developer’s life easier by reducing the time and effort required to build and ship data APIs. With Grafbase, you can go from idea to GraphQL API in seconds without spending time on infrastructure. You can also work locally using the Grafbase CLI and test your changes in real-time. You can also leverage the power of GraphQL to query and manipulate your data flexibly and efficiently. Grafbase also provides templates, guides, and examples to help you get started with various frameworks, libraries, APIs, and more. With their guides and examples, they have also mentioned the GitHub repo if applicable.
Grafbase also offers a tool called Pathfinder, which is a true blessing. With Pathfinder, we can explore the project's GraphQL API, execute GraphQL operations, and more. In the section "Behind the Scenes: Understanding the Code of Graphy and Setting up" I will talk more about Pathfinder.
Apart from those mentioned above, Grafbase also offers a Next.js plugin.
The Grafbase Next.js Plugin is a tool that helps developers run Grafbase locally when building Next.js applications. It requires wrapping your next.config.js configuration using the withGrafbase function and can be installed directly from NPM. The plugin is intended for local use only and will not do anything in a production context.
What is the tech stack used?
When building an application, it’s important to carefully choose the technologies and tools that will be used in its development. In this section, I will take a closer look at the tech stack used to build "Graphy" and explore how each technology contributes to the functionality and user experience of the app:
-
Next.js 13.4.12 /app directory as the React-based framework for building the application.
-
Grafbase as the database.
-
Grafbase SDK to create and configure the GraphQl APIs.
-
Next-Auth for authentication.
-
TypeScript for adding static typing to JavaScript.
-
Tailwind CSS as the utility-first CSS framework for styling the application.
-
Radix UI (shadcn/ui) for components.
-
Lucide React for adding SVG icons.
-
Next Themes for adding light and dark mode support
-
Hashnode for providing this opportunity and platform to share and write great articles.
-
Zod with react-hook-form to validate.
-
Cloudinary to store and access images. ( For my application, I have used the next-cloudinary package instead of the one being used in the Flexibble project. I just prefer the next-cloudinary package since it makes the job way simpler)
Did I face any challenges while building the app?
Embarking on the journey of building an app is always an adventure filled with challenges and rewards. As I developed Graphy for the Grafbase X Hashnode Hackathon, I encountered numerous obstacles that tested my abilities and spurred me to expand my knowledge and skills as a developer.
From mastering the use of Garfbase to integrate with web apps to understanding GraphQl on a deeper level, each step presented its own unique hurdles.
Yet, with unwavering determination and steadfast perseverance, I triumphed over these challenges and crafted an application of which I am immensely proud. Before this project, I had no experience with Grafbase, but the thrill of learning how to incorporate it was exhilarating.
The knowledge I gained has left me confident in my ability to utilize Grafbase in future projects, particularly because of how easy it is to use.
One major problem that I faced while working on this project was when I was following along with the Flexibble video, so the application was working fine in my local environment, but when I pushed it to production, the build was successful, but I got this error: "Application error: a client-side exception has occurred (see the browser console for more information)." When I tried to go to the deployed link.
At that moment, I faced a dilemma about whether I should just quit, let imposter syndrome completely make me doubt my capabilities, or continue with this application, understand the root cause of my problem, and keep participating in the hackathon. Now that I am writing this article, I am really happy to say that I am proud that I chose the latter and did not let imposter syndrome win. Small wins like this act as a source of motivation for me in the future. So thanks again Grafbase and Hashnode for this opportunity.
Note: The question of quitting, as I have mentioned above came up for two reasons:
-
Since taking part in this hackathon is a time-sensitive matter, I learned about it just 8 days before the due date, which is my fault for not checking Hashnode religiously. Also, based on my personal experience, it takes me around 2–3 days to write an article with details, so I need to consider that within the 8-day time period too.
-
I don't have much experience with GraphQL, so it always takes some time for me to understand when I am exploring new concepts and technologies. But Grafbase has exceptionally good resources and documentation, which have immensely helped me along the way.
Apart from that, another problem I faced was while submitting a post on the production site. It was happening completely due to a mistake I made while adding the public URL to my environment variables. I added the "/" at the end, because of which it was not able to make a call to the auth API to get the token to create a post.
After completing the CRUD functionality for the Post part, I added a settings route and created a form for updating my name, description, and image. I created a mutation called updateUserMutation
and a function called updateUser
to update the User. The function calls makeGraphQLRequest
with the updateUserMutation
mutation and an object containing input data for the mutation. Despite receiving a status code of 200, I encountered an error toast.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691766379249/00f9f593-36e2-4b3f-8b6b-eb09a1e1516b.jpeg align="center")
So after spending some time actually a lot of time—figuring out where I made the mistake, I realized I completely forgot to update the auth rule in my Grafbase schema. Once I added "rules.private().update()" everything was working fine. Also for a good time, it did not pop up in my mind to actually console.log the error. Once I did that I was able to solve the issue within a minute. Great work Grafbase Team, the error that got printed was to the point.
Setting Up the Graphy App in Your Local Environment: A Step-by-Step Guide
-
Fork the repository on GitHub to create a copy of the project in your own account.
-
Clone the forked repository to your local machine using git clone
.
-
Navigate to the project directory and run npm i
to install all the necessary dependencies.
-
Set up the environment variables for Grafbase, next-auth secret by copying the .env.example
file to a new file named .env
and filling in the values. Below is a section called "Setting up your Grafbase API url and API key" where I will be explaining how you can set up your NEXT_PUBLIC_GRAFBASE_API_URL and NEXT_PUBLIC_GRAFBASE_API_KEY.
-
Uploading photos in the application is a very optional thing, so I won't be going into detail on how to get the Cloudinary cloud name. But in bried go to cloudinaty.com, log in or sign up, and look at the button left side of the screen there should be some random words. That's the value for "NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME"
-
In another section called "Social Login with Github with next-auth," I have explained the steps required to follow to get the "GITHUB_ID" and "GITHUB_CLIENT_SECRET".
-
Once the dependencies are installed and the environment variables are set up, run npm run dev
to start the development server.
The project should now be running on your local machine, and you can experiment with it or, better yet, learn from the code.
Behind the Scenes: Understanding the Code for Graphy and setting it up
The app was built using Next.js 13.4.12 and React 18.2.0. I integrated Passage for authentication to handle user authentication. For the user interface, I used TailwindCSS 3.3.2 for styling, along with shadcn/ui for UI components. I also incorporated lucide react for Icons. The code was written in TypeScript 5.1.3. For the database, I am using Grafbase.
How is Grafbase being used in this application?
In this application, I have used Grafbase as the database and also used their SDK to generate the Grafbase configuration file, where I have defined the model schema for the application.
Setting up Grafbase
So after creating the application, I had to set up Grafbase to define my schema. For that I have used the @grafbase/sdk package. The steps I took are:
-
npm i @grafbase/sdk --save-dev
-
Initializing the project with "npx grafbase init --config-format typescript"
-
A folder got generated called "grafbase" at the root of my project. Inside that folder, two files got generated. They are ".env" and "grafbase.config.ts".
-
Inside the 'grafbase.config.ts' file a general schema was provided. But for my application, I have made some changes to fit my needs.
-
After initializing the project and defining my schema I connected it with the Grafbase dashboard to get the values of NEXT_PUBLIC_GRAFBASE_API_URL and NEXT_PUBLIC_GRAFBASE_API_KEY.
Understanding the schema defined in grafbase.config.ts
import { g, config, auth } from "@grafbase/sdk";
// @ts-ignore
const User = g
.model("User", {
name: g.string().length({ min: 2, max: 100 }),
email: g.string().unique(),
avatarUrl: g.url(),
description: g.string().length({ min: 2, max: 1000 }).optional(),
posts: g
.relation(() => Post)
.list()
.optional(),
})
.auth((rules) => {
rules.public().read();
rules.private().update();
});
// @ts-ignore
const Post = g
.model("Post", {
title: g.string().length({ min: 4 }),
description: g.string().optional(),
image: g.url(),
category: g.string().search(),
createdBy: g.relation(() => User),
})
.auth((rules) => {
rules.public().read();
rules.private().create().delete().update();
});
const jwt = auth.JWT({
issuer: "grafbase",
secret: g.env("NEXTAUTH_SECRET"),
});
export default config({
schema: g,
auth: {
providers: [jwt],
rules: (rules) => rules.private(),
},
});
I have defined two two models, User
and Post
, using the g.model
method from the @grafbase/sdk
package. The User
model has fields for name
, email
, avatarUrl
, description
, and posts
. The Post
model has fields for title
, description
, image
, category
, and createdBy
.
I have also set up some validation rules for the fields, such as minimum and maximum length for the name
and description
fields of the User
model, and a minimum length for the title
field of the Post
model. The code also specifies that the email field of the User model should be unique.
I have also sets up authorization rules for the models using the .auth
method. For example, I have specified that anyone can read data from the User model, but only authenticated users can update data. Similarly, anyone can read data from the Post model, but only authenticated users can create, delete, or update data.
I have also set up a JWT authentication provider using the auth.JWT method from the @grafbase/sdk
package. The JWT provider is configured with an issuer and a secret, which are obtained from environment variables.
Finally, I have exported a configuration object using the config
method from the @grafbase/sdk
package. The configuration object specifies the schema, authentication providers, and authorization rules for the project. This configuration object is used by Grafbase to generate everything needed to deploy your GraphQL Edge Gateway.
After defining the schema I added the NEXT_SECRET to the .env file which was generated inside the Grafbase folder.
Setting up Grafbase API URL and API key
-
I went to https://grafbase.com and logged in. If you do not have an account, create one.
-
Once you are logged in click on "Create Project".
-
You will have the option to connect your GitHub repo to it, do also there will be an option to add Environment Variable id added the NEXT_SECRET out there. Then clicked on deploy.
-
Now you should see a "Connect" button, click on it. It will provide you the values for NEXT_PUBLIC_GRAFBASE_API_URL and NEXT_PUBLIC_GRAFBASE_API_KEY.
Once everything is set up, return to the terminal, one terminal will run the server for the application. In another terminal, run the following command: npx grafbase dev
to start the server locally at http://localhost:4000
For more information, check the official docs for Grafbase CLI
Pathfinder
To access Pathfinder in local development mode, go to http://localhost:4000
Once your project is running in production mode, you can check it directly from the dashboard by following the steps mentioned below.
Once our project is connected, we can click on the "View Pathfinder" button
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692032392849/0efd97f0-059c-4918-938a-bb631efa3ba3.png align="center")
You should see something similar to this based on the schema you defined on the grafbase.config.ts file
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692032548740/e3d266fb-4b12-4203-b790-213c8ab8ef2a.png align="center")
In the above screenshot, I am querying to retrieve the first 10 posts with the information mentioned in the query. Pathfinder is a very valuable tool that can be used to create GraphQL queries and mutations.
Setting up our GraphQL queries and mutations
<mark>Query:</mark> A request to read or fetch data from a server.
<mark>Mutation</mark>: A request to modify or change data on the server.
For my application, I have created the queries and mutations in the index.ts file inside the GraphQL folder situated at the root of the project.
The different queries and mutations are followed:
export const createPostMutation = `
mutation CreatePost($input: PostCreateInput!) {
postCreate(input: $input) {
post {
id
title
description
createdBy {
email
name
}
}
}
}
`;
export const updatePostMutation = `
mutation UpdatePost($id: ID!, $input: PostUpdateInput!) {
postUpdate(by: { id: $id }, input: $input) {
post {
id
title
description
createdBy {
email
name
}
}
}
}
`;
export const deletePostMutation = `
mutation DeletePost($id: ID!) {
postDelete(by: { id: $id }) {
deletedId
}
}
`;
export const createUserMutation = `
mutation CreateUser($input: UserCreateInput!) {
userCreate(input: $input) {
user {
name
email
avatarUrl
description
id
}
}
}
`;
export const updateUserMutation = `
mutation UpdateUser($id: ID!, $input: UserUpdateInput!) {
userUpdate(by: { id: $id }, input: $input) {
user {
name
description
avatarUrl
id
}
}
}
`;
export const postsQuery = `
query getPosts {
postSearch(first: 10) {
edges {
node {
title
description
id
image
category
createdBy {
id
email
name
avatarUrl
}
}
}
}
}
`;
export const getPostByIdQuery = `
query GetPostById($id: ID!) {
post(by: { id: $id }) {
id
title
description
image
category
createdBy {
id
name
email
avatarUrl
}
}
}
`;
export const getUserQuery = `
query GetUser($email: String!) {
user(by: { email: $email }) {
id
name
email
avatarUrl
description
}
}
`;
export const getPostsOfUserQuery = `
query getUserPosts($id: ID!, $last: Int = 4) {
user(by: { id: $id }) {
id
name
email
avatarUrl
description
posts(last: $last) {
edges {
node {
id
title
image
}
}
}
}
}
`;
Explanantion: All the queries and mutations are defined using template literals, which are enclosed in backticks (`).
-
createPostMutation
: A mutation for creating a new post. It takes an input object of type PostCreateInput
and returns the created post object with its id
, title
, description
, and createdBy
fields.
-
updatePostMutation
: A mutation for updating an existing post. It takes an id
argument of type ID
and an input object of type PostUpdateInput
, and returns the updated post object with its id
, title
, description
, and createdBy
fields.
-
deletePostMutation
: A mutation for deleting an existing post. It takes an id
argument of type ID
and returns the deleted post’s id.
-
createUserMutation
: A mutation for creating a new user. It takes an input object of type UserCreateInput
and returns the created user object with its name
, email
, avatarUrl
, description
, and id
fields.
-
updateUserMutation
: A mutation for updating an existing user. It takes an id
argument of type ID
and an input object of type UserUpdateInput
, and returns the updated user object with its name
, description
, avatarUrl
, and id
fields.
-
postsQuery
: A query for retrieving a list of posts. It returns the first 10 posts with their respective fields such as title, description, id, image, category, createdBy, etc.
-
getPostByIdQuery
: A query for retrieving a single post by its id. It takes an id argument of type ID and returns the post object with its respective fields such as id, title, description, image, category, createdBy, etc.
-
getUserQuery
: A query for retrieving a single user by their email. It takes an email argument of type String and returns the user object with its respective fields such as id, name, email, avatarUrl, description, etc.
-
getPostsOfUserQuery
: A query for retrieving the posts of a single user by their id. It takes an id argument of type ID and an optional last argument of type Int (default value is 4) and returns the user object with its respective fields such as id, name, email, avatarUrl, description, etc., along with their last n posts (where n is the value passed to the last argument).
Functions to communicate with the GraphQL APIs
Defining the functions that will use the queries and mutations to interact with the API. I have created those functions in the actions.ts file located inside the libs folder. The libs folder is located at the root of the project.
The code for the actions.ts file is as follows:
import { GraphQLClient } from "graphql-request";
import {
createPostMutation,
createUserMutation,
deletePostMutation,
updatePostMutation,
getPostByIdQuery,
getPostsOfUserQuery,
getUserQuery,
postsQuery,
updateUserMutation,
} from "@/graphql";
import { PostForm, UserForm } from "@/common.types";
const isProduction = process.env.NODE_ENV === "production";
const apiUrl = isProduction
? process.env.NEXT_PUBLIC_GRAFBASE_API_URL!
: "http://127.0.0.1:4000/graphql";
const apiKey = isProduction
? process.env.NEXT_PUBLIC_GRAFBASE_API_KEY!
: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTE0ODUxNTcsImlzcyI6ImdyYWZiYXNlIiwiYXVkIjoiMDFIN0E1WUU2QkdTVkdTQlJEUlkwQUFGWVYiLCJqdGkiOiIwMUg3QTVZRTkwSlQ0ME4xRFhWM1hQMUhWQyIsImVudiI6InByb2R1Y3Rpb24iLCJwdXJwb3NlIjoicHJvamVjdC1hcGkta2V5In0.HDa36FQMMXy_mH3OIYgtM4PU-0De9p34fFFgv2sLtQs";
const serverUrl = isProduction
? process.env.NEXT_PUBLIC_SERVER_URL
: "http://localhost:3000";
const client = new GraphQLClient(apiUrl);
export const fetchToken = async () => {
try {
const response = await fetch(`${serverUrl}/api/auth/token`);
return response.json();
} catch (err) {
throw err;
}
};
const makeGraphQLRequest = async (query: string, variables = {}) => {
try {
return await client.request(query, variables);
} catch (err) {
throw err;
}
};
export const fetchAllPosts = () => {
client.setHeader("x-api-key", apiKey);
return makeGraphQLRequest(postsQuery);
};
export const createNewPost = async (
form: PostForm,
creatorId: string,
token: string
) => {
client.setHeader("Authorization", `Bearer ${token}`);
const variables = {
input: {
...form,
createdBy: {
link: creatorId,
},
},
};
return makeGraphQLRequest(createPostMutation, variables);
};
export const updatePost = async (
form: PostForm,
postId: string,
token: string
) => {
let updatedForm = { ...form };
client.setHeader("Authorization", `Bearer ${token}`);
const variables = {
id: postId,
input: updatedForm,
};
return makeGraphQLRequest(updatePostMutation, variables);
};
export const deletePost = (id: string, token: string) => {
client.setHeader("Authorization", `Bearer ${token}`);
return makeGraphQLRequest(deletePostMutation, { id });
};
export const getPostDetails = (id: string) => {
client.setHeader("x-api-key", apiKey);
return makeGraphQLRequest(getPostByIdQuery, { id });
};
export const createUser = (
name: string,
email: string,
avatarUrl: string,
description: string
) => {
client.setHeader("x-api-key", apiKey);
const variables = {
input: {
name: name,
email: email,
avatarUrl: avatarUrl,
description: description,
},
};
return makeGraphQLRequest(createUserMutation, variables);
};
export const updateUser = async (
form: UserForm,
userId: string,
token: string
) => {
let updatedUser = { ...form };
client.setHeader("Authorization", `Bearer ${token}`);
const variables = {
id: userId,
input: updatedUser,
};
return makeGraphQLRequest(updateUserMutation, variables);
};
export const getUserPosts = (id: string, last?: number) => {
client.setHeader("x-api-key", apiKey);
return makeGraphQLRequest(getPostsOfUserQuery, { id, last });
};
export const getUser = (email: string) => {
client.setHeader("x-api-key", apiKey);
return makeGraphQLRequest(getUserQuery, { email });
};
First I have set up the required constants, including apiUrl
, apiKey
, and serverUrl
, which are used to configure the GraphQLClient
and make requests to the API. The values of these constants depend on whether the code is running in production or not, as determined by the value of the NODE_ENV
environment variable.
Explanation of the functions I created:
-
fetchToken
: A function that fetches an authentication token from the server.
-
makeGraphQLRequest
: A helper function that takes a query
string and an optional variables
object as arguments, and sends a request to the GraphQL API using the client
object. The function returns the response from the API or throws an error if the request fails.
-
fetchAllPosts
: A function that fetches all posts from the GraphQL API. It sets the x-api-key
header on the client
object and calls the makeGraphQLRequest
function with the postsQuery
query.
-
createNewPost
: A function that creates a new post on the GraphQL API. It takes a form
object, a creatorId
string, and a token
string as arguments. The function sets the Authorization
header on the client
object and calls the makeGraphQLRequest
function with the createPostMutation
mutation and an object containing the input data for the mutation.
-
updatePost
: A function that updates an existing post on the GraphQL API. It takes a form
object, a postId
string, and a token
string as arguments. The function sets the Authorization
header on the client object and calls makeGraphQLRequest with updatePostMutation mutation and an object containing input data for mutation.
-
deletePost
: A function that deletes an existing post on GraphQL API. It takes id and token as arguments, sets Authorization header on client object, and calls makeGraphQLRequest with deletePostMutation mutation and an object containing id of post to delete.
-
getPostDetails:
A function that retrieves details about single post from GraphQL API. It takes id as argument, sets x-api-key header on client object, and calls makeGraphQLRequest with getPostByIdQuery query and an object containing id of post to retrieve.
-
createUser
: A function that creates new user on GraphQL API. It takes name, email, avatarUrl, description as arguments, sets x-api-key header on client object, and calls makeGraphQLRequest with createUserMutation mutation and an object containing input data for mutation.
-
updateUser
: A function that updates existing user on GraphQL API. It takes form, userId, token as arguments, sets Authorization header on client object, and calls makeGraphQLRequest with updateUserMutation mutation and an object containing input data for mutation.
-
getUserPosts
: A function that retrieves posts of single user from GraphQL API. It takes id and last as optional arguments, sets x-api-key header on client object, and calls makeGraphQLRequest with getPostsOfUserQuery query and an object containing id of user whose posts are to be retrieved.
-
getUser
: A function that retrieves details about single user from GraphQL API. It takes email as argument, sets x-api-key header on client object, and calls makeGraphQLRequest with getUserQuery query and an object containing email of user whose details are to be retrieved.
Social Login with Github with next-auth
For local environment:
Github Environment Variables
Go to your github account --> settings --> Developer settings --> Oauth Apps
Then create a new app by clicking on "New OAuth App"
A should see an application form. On "Homepage URL" and "Authorization callback URL" field put http://localhost:3000/
![Image](https://faq-codewithantonio.vercel.app/_next/image?url=%2Fimages%2Fguides%2Fgithubregister.jpg&w=1920&q=75 align="left")
Note: Make sure it is "http" in both URLs. Using "https" might cause errors.
Click on "Register application".
GITHUB_ID
Now you should see the Client Id, copy that code and put it in your .env file. This is the value for "GITHUB_ID".
GITHUB_SECRET
Below the Client ID there is a button called "Generate a new client secret". Click on it, a key should generate. Copy the code and paste it as the value for GITHUB_SECRET.
![Image](https://faq-codewithantonio.vercel.app/_next/image?url=%2Fimages%2Fguides%2Fgithubsecret.jpg&w=1920&q=75 align="left")
Once you have those values, log-in with Github should be working in your local environment.
For production site:
Go back to your github account --> Settings --> Developer Settings --> OAuth App --> Choose the app --> (Application Form should show up) --> Change Homepage URL and Authorization callback URL to your deployed Url --> Click on "Update Application"
Once you update the value, it should be working on the deployed site.
Creating a Post
In Graphy to create a post a user needs to be signed in first, in this application I have also added a middleware.ts file to protect '/create-post', '/edit-post', and '/settings' routes.
So once a user is logged in he/she/they will notice a Button that says "Post" on the right side of the Navbar.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691764036147/9037dbdc-8c00-4c2e-9c42-2cdb675c68f9.png align="center")
Once the user clicks on it a form that is made using react-hook-form and Zod for client-side validation will appear.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691764438837/a5378670-5bef-4b9a-b092-2d588f714e70.png align="center")
I am creating a post with the following values
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691839914592/5dccbc60-159b-4f8b-aa57-3f04c083c47b.png align="center")
Once the user clicks on the "+ Publish Post" button, the post will be submitted. A will be displayed on the home page.
Now it is visible on the home page
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691839991696/a1118f54-859e-4119-ae88-ca35d11dcd82.png align="center")
Explanation of what is happening when the user submits the form
So when a user clicks on "+ Publish Post" the onSubmit
function is called.
The onSubmit
function takes a values
object as an argument, which contains the data entered into the form by the user. It first tries to fetch an authentication token by calling the fetchToken
function. If successful, it sets the loading
state to true
, calls the createNewPost
function with the form values, the user’s id, and the token, and then redirects the user to the homepage using the router.push
method. It also displays a toast notification to inform the user that their post was successfully submitted.
If an error occurs during this process, such as if fetching the token or creating a new post fails, the function catches the error and logs it to the console. It also displays a toast notification to inform the user that their submission failed and provides a brief explanation of why it may have failed.
Updating a Post
Now to update the post, you need to click on it, it will take you to "/post/post_id
" route.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1691840131233/433b78fd-1abe-4f6a-92dc-1bd35d26c83b.png align="center")
If you are the user who created the post the 2 buttons to edit and delete the post will appear. Once you click on the edit button, I will take you to the edit form, where you can update it with the desired values.
Explanation of what is happening when the user submits the form to update the post
So when a user clicks on "+ Update Post" the onSubmit
function is called.
The onSubmit
function takes a values
object as an argument, which contains the data entered into the form by the user. It first tries to fetch an authentication token by calling the fetchToken
function. If successful, it sets the loading
state to true
, calls the updatePost
function with the form values, the post id, and the token, and then redirects the user to the homepage using the router.push
method. It also displays a toast notification to inform the user that their post was successfully updated.
If an error occurs during this process, such as if fetching the token or updating the post fails, the function catches the error and logs it to the console. It also displays a toast notification to inform the user that their submission failed and provides a brief explanation of why it may have failed.
Similarly, if the user clicks on the delete button first, an alert will pop up, and if the user clicks on confirm, the post will be deleted from the database, considering Article 17, of the GDPR, which outlines the specific circumstances under which the right to be forgotten applies.
A brief on styling and responsiveness of the application
In the application, I used Tailwind CSS to do all the styling. I have tried to make the application as responsive as possible. But there might be some parts I missed.
The application also has a light and dark mode, which was used with the class strategy and the next-themes library.
And the cherry on top
Device Desktop:
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692045934872/acd5de4f-8d91-40b2-ab73-128de7081a4d.png align="center")
Device Mobile:
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1692046062999/0ed4a357-c167-45f9-a8ba-1bc67d6c84e2.png align="center")
Public Repo Link
Github Repo: https://github.com/trace2798/grafbase_fullstack
Current Demo Deployment: Graphy - Home (https://grafbase-fullstack.vercel.app/) (I might un-deploy it in the future so I have provided the demo video.)
Demo Video Link
Youtube Link: https://youtu.be/8e7_SlBGsq8
I hope this article helped you. If you have any questions, feel free to leave a comment, and I will respond to it as soon as possible.
Happy Hacking !!!