This part builds upon the previous parts by demonstrating how to perform GraphQL operations with proper database permissions using Next.js App Router patterns. You’ll learn how to design database tables, configure user permissions, and implement complete CRUD operations through GraphQL queries and mutations using server components, client components, and server actions.
This is Part 4 in the Full-Stack Next.js Development with Nhost series. This part focuses on GraphQL operations, database management, and permission-based data access control using Next.js App Router with server/client component separation.

Full-Stack Next.js Development with Nhost

Prerequisites

What You’ll Build

By the end of this part, you’ll have:
  • GraphQL queries and mutations for complete CRUD operations
  • Database schema with proper relationships and constraints
  • User permissions for secure data access control
  • Next.js components using server/client patterns that interact with GraphQL endpoint
  • Server actions for secure data mutations
  • Server components for efficient data fetching

Step-by-Step Guide

1

Create the To-Dos Table

First, we’ll perform the database changes to set up the todos table with proper schema and relationships to users.In your Nhost project dashboard:
  1. Navigate to Database
  2. Click on the SQL Editor
Enter the following SQL:
CREATE TABLE public.todos (
  id uuid DEFAULT gen_random_uuid() NOT NULL,
  created_at timestamptz DEFAULT now() NOT NULL,
  updated_at timestamptz DEFAULT now() NOT NULL,
  title text NOT NULL,
  details text,
  completed bool DEFAULT false NOT NULL,
  user_id uuid NOT NULL,
  PRIMARY KEY (id),
  FOREIGN KEY (user_id) REFERENCES auth.users (id) ON UPDATE CASCADE ON DELETE CASCADE
);


CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = now();
  RETURN NEW;
END;
$$ language 'plpgsql';


CREATE TRIGGER update_todos_updated_at
  BEFORE UPDATE ON public.todos
  FOR EACH ROW
  EXECUTE FUNCTION update_updated_at_column();

Please make sure to enable Track this so that the new table todos is available through the auto-generated APIs
2

Set Up Permissions

It’s now time to set permission rules for the table you just created. With the table todos selected, click on , followed by Edit Permissions. You will set permissions for the user role and actions insert, select, update, and delete.
When inserting permissions we are only allowing users to set the title, details, and completed columns as the rest of the columns are set automatically by the backend. The user_id column is configured as a preset to the currently authenticated user’s ID using the X-Hasura-User-Id session variable. This ensures that each todo is associated with the user who created it.Insert Permissions Configuration
3

Create the Todos Page System

Now let’s implement the Next.js page system that uses the database we just configured. We’ll create a server component for the main page, a client component for the interactive todos interface, and server actions for secure data mutations.
The main todos page is a server component that fetches initial data server-side and renders the todos interface. This component runs on the server and provides the initial state to the client component.
src/app/todos/page.tsx
import { createNhostClient } from "../../lib/nhost/server";
import TodosClient from "./TodosClient";

// The interfaces below define the structure of our data
// They are not strictly necessary but help with type safety

// Represents a single todo item
export interface Todo {
  id: string;
  title: string;
  details: string | null;
  completed: boolean;
  created_at: string;
  updated_at: string;
  user_id: string;
}

// This matches the GraphQL response structure for fetching todos
// Can be used as a generic type on the request method
interface GetTodos {
  todos: Todo[];
}

export default async function TodosPage() {
  // Fetch initial todos data server-side
  const nhost = await createNhostClient();
  const session = nhost.getUserSession();

  let initialTodos: Todo[] = [];
  let error: string | null = null;

  if (session) {
    try {
      // Make GraphQL request to fetch todos using Nhost server client
      // The query automatically filters by user_id due to Hasura permissions
      const response = await nhost.graphql.request<GetTodos>({
        query: `
          query GetTodos {
            todos(order_by: { created_at: desc }) {
              id
              title
              details
              completed
              created_at
              updated_at
              user_id
            }
          }
        `,
      });

      // Check for GraphQL errors in the response body
      if (response.body.errors) {
        error = response.body.errors[0]?.message || "Failed to fetch todos";
      } else {
        // Extract todos from the GraphQL response data
        initialTodos = response.body?.data?.todos || [];
      }
    } catch (err) {
      error = err instanceof Error ? err.message : "Failed to fetch todos";
    }
  }

  return <TodosClient initialTodos={initialTodos} initialError={error} />;
}
4

Update Navigation Component

Add the todos page to your application navigation by updating the Navigation component to include a link to the todos page.
src/components/Navigation.tsx
import Link from "next/link";
import { createNhostClient } from "../lib/nhost/server";
import SignOutButton from "./SignOutButton";

export default async function Navigation() {
  const nhost = await createNhostClient();
  const session = nhost.getUserSession();

  return (
    <nav className="navigation">
      <div className="nav-container">
        <Link href="/" className="nav-logo">
          Nhost Next.js Demo
        </Link>

        <div className="nav-links">
          <Link href="/" className="nav-link">
            Home
          </Link>

          {session ? (
            <>
              <Link href="/todos" className="nav-link">
                Todos
              </Link>
              <Link href="/profile" className="nav-link">
                Profile
              </Link>
              <SignOutButton />
            </>
          ) : (
            <>
              <Link href="/signin" className="nav-link">
                Sign In
              </Link>
              <Link href="/signup" className="nav-link">
                Sign Up
              </Link>
            </>
          )}
        </div>
      </div>
    </nav>
  );
}
5

Test Your Complete Application

Run your Next.js application and test all the functionality:
npm run dev
Things to try out:
  1. Server-Side Rendering: Notice how the todos are loaded server-side on initial page load, providing faster initial rendering
  2. Authentication Integration: Try signing in and out and see how the Todos page is only available when authenticated through middleware protection
  3. CRUD Operations: Create, view, edit, complete, and delete todos. Notice how server actions handle mutations while maintaining type safety
  4. Multi-User Isolation: Open the application in another browser or incognito window, sign in with a different account and verify that you cannot see or modify todos from the first account
  5. Real-time Updates: Unlike client-only React apps, changes will be persisted immediately through server actions and reflected in the optimistic UI updates

Key Features Implemented