Integrating Clerk with Grafbase and Grafbase Next.Js Plugin

Integrating Clerk with Grafbase and Grafbase Next.Js Plugin

In the article, I will explain how you can integrate Clerk as an authentication provider with the Next.Js app directory and Grafbase. I will also explain the steps you can take to install the Grafbase Next.js Plugin. I would also like to thank Hashnode for providing such a great platforms to write artcles.

Before getting started, I would like to take a component to explain what Grafbase, Clerk, Next.js and Grafbase Next.js Plugin is.

Grafbase: Grafbase is a cloud-based service that enables developers to build and deploy their own GraphQL APIs to the edge with complete 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.

Clerk: Clerk is a full-featured user management and authentication service targeting developers who want to add sign-up/login capabilities to their apps quickly without backend code. It provides a ready-to-use user management system including features like user registration, login, social login, passwordless authentication, multi-factor authentication, etc.

Next.Js: Next.js is a React framework for building full-stack web applications. You use React Components to build user interfaces, and Next.js for additional features and optimizations. Under the hood, Next.js also abstracts and automatically configures tooling needed for React, like bundling, compiling, and more. This allows you to focus on building your application instead of spending time with configuration.

Grafbasee Next.js Plugin: It is a tool that automatically runs Grafbase locally using the CLI when Next.js starts the development server. Also, this plugin has 2 limitations as mentioned in the official docs. Those limitations are:

  • This plugin is intended to work locally. The plugin will not do anything in a production context.

  • If there is no environment variable or the environment variable is not localhost, the plugin will skip running the CLI.

Why did I choose this project?

There are a couple of reasons for me to choose this project where I am integrating Clerk with grafbase.

  1. I hate setting up authentication, and Clerk makes it super easy.

  2. In the Github Repo examples for Grafbase the examples I saw were with the pages directory, so in my example, it uses the app directory.

  3. Apart from that, in the 2 examples I saw in their official repo. Both of them use the schema.graphql file. In my example, it uses the grafbase.config.ts file.

I believe this example/starter code will help someone a lot in the initial setup.

What is the tech stack used?

This template has been made with the following:

  • 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.

  • Clerk 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

Steps to follow to integrate:

Setting up a new Next.Js project:

  1. Create a new project with the following command npx create-next-app@latest

  2. The following prompt will show up and it depends on you what you choose, but I chose the default in all of them. Also, make sure you choose "Yes" for "Would you like to use App Router? (recommended)"

What is your project named? my-app
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like to use `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to customize the default import alias? No / Yes
What import alias would you like configured? @/*
  1. Then cd my-app you should be inside the newly created project.

Setting up Grafbase:

So after creating the application, now we will need to setup Grafbase. For that, we will use the @grafbase/sdk package. The steps I took are:

  1. In the terminal run the following command npm i @grafbase/sdk --save-dev

  2. Initializing the project with npx grafbase init --config-format typescript

  3. A folder will get generated called "grafbase" at the root of my project. Inside that folder, two files are generated. They are ".env" and "grafbase.config.ts".

  4. Inside the 'grafbase.config.ts' file, a general schema should be provided. Here you can change the schema based on your requirements.
    The default content I got:

import { g, auth, config } from '@grafbase/sdk'

// Welcome to Grafbase!
// Define your data models, integrate auth, permission rules, custom resolvers, search, and more with Grafbase.
// Integrate Auth
// https://grafbase.com/docs/auth
//
// const authProvider = auth.OpenIDConnect({
//   issuer: process.env.ISSUER_URL ?? ''
// })
//
// Define Data Models
// https://grafbase.com/docs/database

const post = g.model('Post', {
  title: g.string(),
  slug: g.string().unique(),
  content: g.string().optional(),
  publishedAt: g.datetime().optional(),
  comments: g.relation(() => comment).optional().list().optional(),
  likes: g.int().default(0),
  tags: g.string().optional().list().length({ max: 5 }),
  author: g.relation(() => user).optional()
}).search()

const comment = g.model('Comment', {
  post: g.relation(post),
  body: g.string(),
  likes: g.int().default(0),
  author: g.relation(() => user).optional()
})

const user = g.model('User', {
  name: g.string(),
  email: g.email().optional(),
  posts: g.relation(post).optional().list(),
  comments: g.relation(comment).optional().list()

  // Extend models with resolvers
  // https://grafbase.com/docs/edge-gateway/resolvers
  // gravatar: g.url().resolver('user/gravatar')
})

export default config({
  schema: g
  // Integrate Auth
  // https://grafbase.com/docs/auth
  // auth: {
  //   providers: [authProvider],
  //   rules: (rules) => {
  //     rules.private()
  //   }
  // }
})

Setting up Grafbase API URL and API key

  1. I went to grafbase.com and logged in. If you do not have an account, create one.

  2. Once you are logged in click on "Create Project".

  3. 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 CLERK_ISSUER_URL out there. Then clicked on deploy. Below in the section "Setting up Clerk", I have explained how to get this value.

  4. 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

Since I am using Grafbase Next.js Plugin it will automatically start the Grafbase server when the next.js development server starts. In my github repo I have a folder called libs and inside it, there is a file called actions.ts. I have implemented the logic to use the values for NEXT_PUBLIC_GRAFBASE_API_URL and NEXT_PUBLIC_GRAFBASE_API_KEY only for production.

To make you life easier here is the logic mentioned in the actions.ts file:


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!
  : "cbsjhsbcbkscjbsckjbkjcssjcbsjcskjksjcbksjbsjcjs";

This code is checking if the current environment is production or not in order to determine which API URL and API key to use.

Specifically:

  • const isProduction = process.env.NODE_ENV === "production"; checks if the NODE_ENV environment variable is set to "production". This will be true when the code is run in a production environment.

  • const apiUrl = isProduction ? process.env.NEXT_PUBLIC_GRAFBASE_API_URL! : "http://127.0.0.1:4000/graphql"; is a ternary that sets the apiUrl variable to either:

    • The GRAFBASE_API_URL environment variable if in production

    • A local host URL if in development

  • Similarly, const apiKey = isProduction ? process.env.NEXT_PUBLIC_GRAFBASE_API_KEY! : "cbsjhsbcbkscjbsckjbkjcssjcbsjcskjksjcbksjbsjcjs"; sets the apiKey to either:

    • The GRAFBASE_API_KEY environment variable in production

    • A fake development key locally

This allows using real credentials in production while falling back to local/fake ones in development. The API URL also points to the real production API in prod and a local version in dev.

Setting up Clerk

  1. First log in to clerk.dev and after log-in you should be inside the dashboard.

  2. Create a new application by clicking on "+ Add Application"

  3. Name the application and choose what you want your users to log in with in my case I am just using email and Google and calling the application grafbase_example.

  4. Click on "Create Application"

  5. You should now see something like this

  6. In the terminal, run the following command npm install @clerk/nextjs

  7. In your root directory, create an .env file inside it we need to put the value for NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_****************************************XYk CLERK_SECRET_KEY=sk_test_****************************************

  8. Now we need to update the root layout to include the <ClerkProvider> wrapper. For that, go to your root layout.tsx and wrap the whole thing with ClerkProvider

     // app/layout.tsx
     import './globals.css'
     import { Inter } from 'next/font/google'
     import { ClerkProvider } from '@clerk/nextjs'
    
     const inter = Inter({ subsets: ['latin'] })
    
     export const metadata = {
       title: 'Create Next App',
       description: 'Generated by create next app',
     }
    
     export default function RootLayout({
       children,
     }: {
       children: React.ReactNode
     }) {
       return (
         <ClerkProvider>
           <html lang="en">
             <body className={inter.className}>{children}</body>
           </html>
         </ClerkProvider>
       )
     }
    
    1. Protecting the application with middleware In the root create a file called middleware.ts and put this code inside. This code will protect the whole application. So if an unauthorized user tries to access any route the user will be redirected to the sign-in page
import { authMiddleware } from "@clerk/nextjs";

// This example protects all routes including api/trpc routes
// Please edit this to allow other routes to be public as needed.
// See https://clerk.com/docs/nextjs/middleware for more information about configuring your middleware
export default authMiddleware({});

export const config = {
  matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};

For my case, I am keeping the "/" rote to public access so my middleware looks like this:

import { authMiddleware } from "@clerk/nextjs";

export default authMiddleware({
  publicRoutes: ["/", "/api/webhook"],
});

export const config = {
  matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};
  1. To build the Sign-in and Sign-up page just follow the steps from the official documentation

  2. Add environment variables for the signIn , signUp and afterSignUp , afterSignIn paths in the .env file:

NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/explore
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/expore
  1. In my application, I created a route called explore to verify if Clerk is working successfully. You can use this as the code for the explore route page.tsx file.
import { UserButton } from "@clerk/nextjs";

export default function Home() {
  return (
    <div>
      <UserButton afterSignOutUrl="/"/>
    </div>
  )
}

Since we are done with the setup for Clerk. Now we will connect Grafbase and Clerk.

Connecting Grafbase with Clerk

Now we need to go back to grafbase.config.ts file. When the file got generated there is this part:

export default config({
  schema: g
  // Integrate Auth
  // https://grafbase.com/docs/auth
  // auth: {
  //   providers: [authProvider],
  //   rules: (rules) => {
  //     rules.private()
  //   }
  // }
})

Now our updated code with Clerk will look like this:

import { g, auth, config } from "@grafbase/sdk";

// Welcome to Grafbase!
// Define your data models, integrate auth, permission rules, custom resolvers, search, and more with Grafbase.
// Integrate Auth
// https://grafbase.com/docs/auth
//
// const authProvider = auth.OpenIDConnect({
//   issuer: process.env.ISSUER_URL ?? ''
// })
//
// Define Data Models
// https://grafbase.com/docs/database

const post = g
  .model("Post", {
    title: g.string(),
    slug: g.string().unique(),
    content: g.string().optional(),
    publishedAt: g.datetime().optional(),
    comments: g
      .relation(() => comment)
      .optional()
      .list()
      .optional(),
    likes: g.int().default(0),
    tags: g.string().optional().list().length({ max: 5 }),
    author: g.relation(() => user).optional(),
  })
  .search();

const comment = g.model("Comment", {
  post: g.relation(post),
  body: g.string(),
  likes: g.int().default(0),
  author: g.relation(() => user).optional(),
});

const user = g.model("User", {
  name: g.string(),
  email: g.email().optional(),
  posts: g.relation(post).optional().list(),
  comments: g.relation(comment).optional().list(),

  // Extend models with resolvers
  // https://grafbase.com/docs/edge-gateway/resolvers
  // gravatar: g.url().resolver('user/gravatar')
});

const clerk = auth.OpenIDConnect({
  issuer: g.env("CLERK_ISSUER_URL"),
});

export default config({
  schema: g,
  auth: {
    providers: [clerk],
    rules: (rules) => rules.private(),
  },
});

Github Repo

Now all we need is the CLERK_ISSUER_URL.

  1. Click on API keys in the sidebar

  2. Click on Advance

  3. So the CLERK_ISSUER_URL is the part before "/.well-known/" so in my case
    CLERK_ISSUER_URL=joint-rabbit-90.clerk.accounts.dev

  1. Go to the.env file that got generated inside the grafbase folder and add this value
    CLERK_ISSUER_URL=https://joint-rabbit-90.clerk.accounts.dev

Setting up Grafbase Next.Js Plugin

  1. Go to your terminal and run this command: npm install -D grafbase @grafbase/nextjs-plugin

  2. Go to next.config.ts file and update it for me the final next.config.ts is as follows:

     /** @type {import('next').NextConfig} */
     const { withGrafbase } = require("@grafbase/nextjs-plugin");
    
     const nextConfig = {
       images: {
         domains: [
           "avatars.githubusercontent.com",
         ],
       },
       experimental: {
         serverComponentsExternalPackages: ["cloudinary", "graphql-request"],
       },
    
       withGrafbase: {
         reactStrictMode: true,
         swcMinify: true,
       },
     };
    
     module.exports = nextConfig;
    
  3. You are done.

Verifying everything is working

  1. Go to your terminal and start the project by running the following command: npm run dev

  2. Since we have the plugin installed, grafbase should automatically run when the nxt.js development server starts.

  3. Now if we try to go to /explore route, which is a middleware-protected route, we should be redirected to the Sign-in page. Once you sign in, you should be redirected to /explore page.
    Once you are inside the explore page you should see the UserButton

    If you click on the UserButton you have the option to sign out and a lot of stuff that Clerk's offers.

That's how you integrate Clerk with Grafbase in a Next.js app directory application.

Github Repo: https://github.com/trace2798/nextjs_clerk_appdir/

Youtube Link: https://youtu.be/nxkMpgaKnkE

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 !!!